Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 5 additions & 8 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -67,20 +67,17 @@ jobs:
strategy:
fail-fast: false
matrix:
os: [ubuntu-latest, macos-13, macos-latest]
python-version: ['3.9', '3.10', '3.11', '3.12', '3.13']
os: [ubuntu-latest, macos-15-intel, macos-latest]
python-version: ['3.10', '3.11', '3.12', '3.13', '3.14']
exclude:
# Python 3.9 is not available on macOS 14
- os: macos-13
- os: macos-15-intel
python-version: '3.10'
- os: macos-13
- os: macos-15-intel
python-version: '3.11'
- os: macos-13
- os: macos-15-intel
python-version: '3.12'
- os: macos-latest
python-version: '3.13'
- os: macos-latest
python-version: '3.9'

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

Expand Down
8 changes: 4 additions & 4 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ line-length = 120
extend-select = ["Q", "RUF100", "UP", "I"]
flake8-quotes = {inline-quotes = "single", multiline-quotes = "double"}
format.quote-style="single"
target-version = "py39"
target-version = "py310"

[tool.pyright]
include = ["src/python-fastui/fastui"]
Expand Down
6 changes: 3 additions & 3 deletions src/python-fastui/fastui/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,9 +30,9 @@ def coerce_to_list(cls, v):
def prebuilt_html(
*,
title: str = '',
api_root_url: _t.Union[str, None] = None,
api_path_mode: _t.Union[_t.Literal['append', 'query'], None] = None,
api_path_strip: _t.Union[str, None] = None,
api_root_url: str | None = None,
api_path_mode: _t.Literal['append', 'query'] | None = None,
api_path_strip: str | None = None,
) -> str:
"""
Returns a simple HTML page which includes the FastUI react frontend, loaded from https://www.jsdelivr.com/.
Expand Down
44 changes: 22 additions & 22 deletions src/python-fastui/fastui/auth/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from contextlib import asynccontextmanager
from dataclasses import dataclass
from datetime import datetime, timedelta, timezone
from typing import TYPE_CHECKING, Union, cast
from typing import TYPE_CHECKING, cast
from urllib.parse import urlencode

from pydantic import BaseModel, SecretStr, TypeAdapter, field_validator
Expand All @@ -19,7 +19,7 @@
@dataclass
class GitHubExchangeError:
error: str
error_description: Union[str, None] = None
error_description: str | None = None


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


github_exchange_type = TypeAdapter(Union[GitHubExchange, GitHubExchangeError])
github_exchange_type = TypeAdapter(GitHubExchange | GitHubExchangeError)


class GithubUser(BaseModel):
login: str
name: Union[str, None]
email: Union[str, None]
name: str | None
email: str | None
avatar_url: str
created_at: datetime
updated_at: datetime
public_repos: int
public_gists: int
followers: int
following: int
company: Union[str, None]
blog: Union[str, None]
location: Union[str, None]
hireable: Union[bool, None]
bio: Union[str, None]
twitter_username: Union[str, None] = None
company: str | None
blog: str | None
location: str | None
hireable: bool | None
bio: str | None
twitter_username: str | None = None


class GitHubEmail(BaseModel):
email: str
primary: bool
verified: bool
visibility: Union[str, None]
visibility: str | None


github_emails_ta = TypeAdapter(list[GitHubEmail])
Expand All @@ -76,10 +76,10 @@ def __init__(
github_client_id: str,
github_client_secret: SecretStr,
*,
redirect_uri: Union[str, None] = None,
scopes: Union[list[str], None] = None,
state_provider: Union['StateProvider', bool] = True,
exchange_cache_age: Union[timedelta, None] = timedelta(seconds=30),
redirect_uri: str | None = None,
scopes: list[str] | None = None,
state_provider: 'StateProvider | bool' = True,
exchange_cache_age: timedelta | None = timedelta(seconds=30),
):
"""
Arguments:
Expand Down Expand Up @@ -114,9 +114,9 @@ async def create(
client_id: str,
client_secret: SecretStr,
*,
redirect_uri: Union[str, None] = None,
state_provider: Union['StateProvider', bool] = True,
exchange_cache_age: Union[timedelta, None] = timedelta(seconds=10),
redirect_uri: str | None = None,
state_provider: 'StateProvider | bool' = True,
exchange_cache_age: timedelta | None = timedelta(seconds=10),
) -> AsyncIterator['GitHubAuthProvider']:
"""
Async context manager to create a GitHubAuth instance with a new `httpx.AsyncClient`.
Expand Down Expand Up @@ -146,7 +146,7 @@ async def authorization_url(self) -> str:
params['state'] = await self._state_provider.new_state()
return f'https://github.com/login/oauth/authorize?{urlencode(params)}'

async def exchange_code(self, code: str, state: Union[str, None] = None) -> GitHubExchange:
async def exchange_code(self, code: str, state: str | None = None) -> GitHubExchange:
"""
Exchange a code for an access token.

Expand All @@ -164,7 +164,7 @@ async def exchange_code(self, code: str, state: Union[str, None] = None) -> GitH
else:
return await self._exchange_code(code, state)

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

def get(self, key: str, max_age: timedelta) -> Union[GitHubExchange, None]:
def get(self, key: str, max_age: timedelta) -> GitHubExchange | None:
self._purge(max_age)
if v := self._data.get(key):
return v[1]
Expand Down
4 changes: 2 additions & 2 deletions src/python-fastui/fastui/auth/shared.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import json
from abc import ABC, abstractmethod
from typing import TYPE_CHECKING, Union
from typing import TYPE_CHECKING

from .. import AnyComponent, FastUI, events
from .. import components as c
Expand Down Expand Up @@ -36,7 +36,7 @@ class AuthRedirect(AuthException):
FastUI components to redirect the user to a new page.
"""

def __init__(self, path: str, message: Union[str, None] = None):
def __init__(self, path: str, message: str | None = None):
super().__init__(f'Auth redirect to `{path}`' + (f': {message}' if message else ''))
self.path = path
self.message = message
Expand Down
6 changes: 3 additions & 3 deletions src/python-fastui/fastui/class_name.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,11 @@
# could be renamed to something general if there's more to add
from typing import Annotated, Literal, Union
from typing import Annotated, Literal

from pydantic import Field
from typing_extensions import TypeAliasType

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

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