Skip to content

Commit ee8c538

Browse files
authored
Add support for Python 3.14, drop 3.9, update dependencies (#383)
1 parent 3f2b9c1 commit ee8c538

File tree

22 files changed

+264
-261
lines changed

22 files changed

+264
-261
lines changed

.github/workflows/ci.yml

Lines changed: 5 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -67,20 +67,17 @@ jobs:
6767
strategy:
6868
fail-fast: false
6969
matrix:
70-
os: [ubuntu-latest, macos-13, macos-latest]
71-
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
70+
os: [ubuntu-latest, macos-15-intel, macos-latest]
71+
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
7272
exclude:
73-
# Python 3.9 is not available on macOS 14
74-
- os: macos-13
73+
- os: macos-15-intel
7574
python-version: '3.10'
76-
- os: macos-13
75+
- os: macos-15-intel
7776
python-version: '3.11'
78-
- os: macos-13
77+
- os: macos-15-intel
7978
python-version: '3.12'
8079
- os: macos-latest
8180
python-version: '3.13'
82-
- os: macos-latest
83-
python-version: '3.9'
8481

8582
runs-on: ${{ matrix.os }}
8683

package-lock.json

Lines changed: 4 additions & 4 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ line-length = 120
66
extend-select = ["Q", "RUF100", "UP", "I"]
77
flake8-quotes = {inline-quotes = "single", multiline-quotes = "double"}
88
format.quote-style="single"
9-
target-version = "py39"
9+
target-version = "py310"
1010

1111
[tool.pyright]
1212
include = ["src/python-fastui/fastui"]

src/python-fastui/fastui/__init__.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,9 +30,9 @@ def coerce_to_list(cls, v):
3030
def prebuilt_html(
3131
*,
3232
title: str = '',
33-
api_root_url: _t.Union[str, None] = None,
34-
api_path_mode: _t.Union[_t.Literal['append', 'query'], None] = None,
35-
api_path_strip: _t.Union[str, None] = None,
33+
api_root_url: str | None = None,
34+
api_path_mode: _t.Literal['append', 'query'] | None = None,
35+
api_path_strip: str | None = None,
3636
) -> str:
3737
"""
3838
Returns a simple HTML page which includes the FastUI react frontend, loaded from https://www.jsdelivr.com/.

src/python-fastui/fastui/auth/github.py

Lines changed: 22 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22
from contextlib import asynccontextmanager
33
from dataclasses import dataclass
44
from datetime import datetime, timedelta, timezone
5-
from typing import TYPE_CHECKING, Union, cast
5+
from typing import TYPE_CHECKING, cast
66
from urllib.parse import urlencode
77

88
from pydantic import BaseModel, SecretStr, TypeAdapter, field_validator
@@ -19,7 +19,7 @@
1919
@dataclass
2020
class GitHubExchangeError:
2121
error: str
22-
error_description: Union[str, None] = None
22+
error_description: str | None = None
2323

2424

2525
@dataclass
@@ -33,33 +33,33 @@ def check_scope(cls, v: str) -> list[str]:
3333
return [s for s in v.split(',') if s]
3434

3535

36-
github_exchange_type = TypeAdapter(Union[GitHubExchange, GitHubExchangeError])
36+
github_exchange_type = TypeAdapter(GitHubExchange | GitHubExchangeError)
3737

3838

3939
class GithubUser(BaseModel):
4040
login: str
41-
name: Union[str, None]
42-
email: Union[str, None]
41+
name: str | None
42+
email: str | None
4343
avatar_url: str
4444
created_at: datetime
4545
updated_at: datetime
4646
public_repos: int
4747
public_gists: int
4848
followers: int
4949
following: int
50-
company: Union[str, None]
51-
blog: Union[str, None]
52-
location: Union[str, None]
53-
hireable: Union[bool, None]
54-
bio: Union[str, None]
55-
twitter_username: Union[str, None] = None
50+
company: str | None
51+
blog: str | None
52+
location: str | None
53+
hireable: bool | None
54+
bio: str | None
55+
twitter_username: str | None = None
5656

5757

5858
class GitHubEmail(BaseModel):
5959
email: str
6060
primary: bool
6161
verified: bool
62-
visibility: Union[str, None]
62+
visibility: str | None
6363

6464

6565
github_emails_ta = TypeAdapter(list[GitHubEmail])
@@ -76,10 +76,10 @@ def __init__(
7676
github_client_id: str,
7777
github_client_secret: SecretStr,
7878
*,
79-
redirect_uri: Union[str, None] = None,
80-
scopes: Union[list[str], None] = None,
81-
state_provider: Union['StateProvider', bool] = True,
82-
exchange_cache_age: Union[timedelta, None] = timedelta(seconds=30),
79+
redirect_uri: str | None = None,
80+
scopes: list[str] | None = None,
81+
state_provider: 'StateProvider | bool' = True,
82+
exchange_cache_age: timedelta | None = timedelta(seconds=30),
8383
):
8484
"""
8585
Arguments:
@@ -114,9 +114,9 @@ async def create(
114114
client_id: str,
115115
client_secret: SecretStr,
116116
*,
117-
redirect_uri: Union[str, None] = None,
118-
state_provider: Union['StateProvider', bool] = True,
119-
exchange_cache_age: Union[timedelta, None] = timedelta(seconds=10),
117+
redirect_uri: str | None = None,
118+
state_provider: 'StateProvider | bool' = True,
119+
exchange_cache_age: timedelta | None = timedelta(seconds=10),
120120
) -> AsyncIterator['GitHubAuthProvider']:
121121
"""
122122
Async context manager to create a GitHubAuth instance with a new `httpx.AsyncClient`.
@@ -146,7 +146,7 @@ async def authorization_url(self) -> str:
146146
params['state'] = await self._state_provider.new_state()
147147
return f'https://github.com/login/oauth/authorize?{urlencode(params)}'
148148

149-
async def exchange_code(self, code: str, state: Union[str, None] = None) -> GitHubExchange:
149+
async def exchange_code(self, code: str, state: str | None = None) -> GitHubExchange:
150150
"""
151151
Exchange a code for an access token.
152152
@@ -164,7 +164,7 @@ async def exchange_code(self, code: str, state: Union[str, None] = None) -> GitH
164164
else:
165165
return await self._exchange_code(code, state)
166166

167-
async def _exchange_code(self, code: str, state: Union[str, None] = None) -> GitHubExchange:
167+
async def _exchange_code(self, code: str, state: str | None = None) -> GitHubExchange:
168168
if self._state_provider:
169169
if state is None:
170170
raise AuthError('Missing GitHub auth state', code='missing_state')
@@ -224,7 +224,7 @@ class ExchangeCache:
224224
def __init__(self):
225225
self._data: dict[str, tuple[datetime, GitHubExchange]] = {}
226226

227-
def get(self, key: str, max_age: timedelta) -> Union[GitHubExchange, None]:
227+
def get(self, key: str, max_age: timedelta) -> GitHubExchange | None:
228228
self._purge(max_age)
229229
if v := self._data.get(key):
230230
return v[1]

src/python-fastui/fastui/auth/shared.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
import json
22
from abc import ABC, abstractmethod
3-
from typing import TYPE_CHECKING, Union
3+
from typing import TYPE_CHECKING
44

55
from .. import AnyComponent, FastUI, events
66
from .. import components as c
@@ -36,7 +36,7 @@ class AuthRedirect(AuthException):
3636
FastUI components to redirect the user to a new page.
3737
"""
3838

39-
def __init__(self, path: str, message: Union[str, None] = None):
39+
def __init__(self, path: str, message: str | None = None):
4040
super().__init__(f'Auth redirect to `{path}`' + (f': {message}' if message else ''))
4141
self.path = path
4242
self.message = message
Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,11 @@
11
# could be renamed to something general if there's more to add
2-
from typing import Annotated, Literal, Union
2+
from typing import Annotated, Literal
33

44
from pydantic import Field
55
from typing_extensions import TypeAliasType
66

7-
ClassName = TypeAliasType('ClassName', Union[str, list['ClassName'], dict[str, Union[bool, None]], None])
7+
ClassName = TypeAliasType('ClassName', str | list['ClassName'] | dict[str, bool | None] | None)
88
ClassNameField = Annotated[ClassName, Field(serialization_alias='className')]
99

10-
NamedStyle = TypeAliasType('NamedStyle', Union[Literal['primary', 'secondary', 'warning'], None])
10+
NamedStyle = TypeAliasType('NamedStyle', Literal['primary', 'secondary', 'warning'] | None)
1111
NamedStyleField = Annotated[NamedStyle, Field(serialization_alias='namedStyle')]

0 commit comments

Comments
 (0)