Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for Python 3.13 #3850

Merged
merged 40 commits into from
Jan 8, 2025
Merged
Show file tree
Hide file tree
Changes from 38 commits
Commits
Show all changes
40 commits
Select commit Hold shift + click to select a range
69c7c84
fix(picologging): updates to handle unsupported environments (python …
cofin Dec 30, 2024
ecbfc8f
chore(deps): bump astral-sh/setup-uv from 4 to 5 (#3911)
dependabot[bot] Dec 30, 2024
3e15df0
feat: enable Python 3.13 support
cofin Dec 30, 2024
7174004
fix(sqlalchemy): updates to documentation based on upstream changes
cofin Dec 30, 2024
f870e2c
chore: bump AA version
cofin Dec 30, 2024
99e8b74
fix(picologging): updates to handle unsupported environments (python …
cofin Dec 30, 2024
fffcdfb
fix: add ignores
cofin Dec 30, 2024
180350d
fix: trim whitespace
cofin Dec 30, 2024
2844564
Merge branch 'logging-13' into msgspec-13
cofin Dec 30, 2024
7193f62
Merge branch 'main' into msgspec-13
cofin Jan 3, 2025
6f73ff6
fix: add ignores
cofin Dec 30, 2024
4c6958e
fix: trim whitespace
cofin Dec 30, 2024
288a91d
fix: add ignores
cofin Dec 30, 2024
ef1e72e
fix: trim whitespace
cofin Dec 30, 2024
ceb7a05
fix: add ignores
cofin Dec 30, 2024
24159d4
fix: trim whitespace
cofin Dec 30, 2024
fc002a5
fix(channels): use `SQL` function for psycopg calls
cofin Dec 30, 2024
62ce785
fix: revert change here
cofin Dec 30, 2024
8c38db3
feat: re-enable safe quote
cofin Dec 30, 2024
bd1c62c
Merge branch 'psycopg-logging' into msgspec-13
cofin Jan 4, 2025
d45896f
chore: locked deps
cofin Jan 4, 2025
1ad3682
fix: another formatting change revert
cofin Jan 4, 2025
f3aaf9f
Merge branch 'main' into msgspec-13
cofin Jan 4, 2025
9384b1d
Merge branch 'main' into msgspec-13
cofin Jan 5, 2025
9ff0608
Merge branch 'main' into msgspec-13
cofin Jan 5, 2025
6bacc31
Merge branch 'main' into msgspec-13
cofin Jan 5, 2025
4c179c5
fix: test change for 3.13 support
cofin Jan 5, 2025
69123e9
fix: logging capsys update
cofin Jan 5, 2025
a754a62
fix: sleep a bit longer for flaky tests
cofin Jan 5, 2025
9d88e97
feat: use updated pytest-asyncio
cofin Jan 5, 2025
5439d49
fix some resource handling
provinzkraut Jan 5, 2025
96828b0
properly close compression facade in all cases
provinzkraut Jan 5, 2025
0202e17
Merge branch 'main' into msgspec-13
cofin Jan 5, 2025
2f7a3e8
fix: remove toml linting changes
cofin Jan 5, 2025
2a834f2
fix: revert default installed version
cofin Jan 5, 2025
8991fd0
fix: simplify anyio dep
cofin Jan 5, 2025
8e73bbe
fix: simplify msgspec dep
cofin Jan 5, 2025
7eca82e
fix: remove new feature for now
cofin Jan 5, 2025
c01af15
fix: update `pytest-asyncio`
cofin Jan 8, 2025
592ad1e
feat: explicitly pin min versions
cofin Jan 8, 2025
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
11 changes: 10 additions & 1 deletion .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ jobs:
strategy:
fail-fast: true
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12", "3.13"]
uses: ./.github/workflows/test.yml
with:
coverage: ${{ (matrix.python-version == '3.12' || matrix.python-version == '3.8') }}
Expand All @@ -123,6 +123,9 @@ jobs:
version: "0.5.4"
enable-cache: true

- name: Install Build Dependencies
run: sudo apt-get install build-essential libpq-dev python3-dev -y

- name: Install dependencies
run: |
uv sync
Expand Down Expand Up @@ -190,6 +193,9 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4

- name: Install Build Dependencies
run: sudo apt-get install build-essential libpq-dev python3-dev -y

- name: Set up Python
uses: actions/setup-python@v5
with:
Expand Down Expand Up @@ -260,6 +266,9 @@ jobs:
- name: Check out repository
uses: actions/checkout@v4

- name: Install Build Dependencies
run: sudo apt-get install build-essential libpq-dev python3-dev -y

- name: Set up Python
uses: actions/setup-python@v5
with:
Expand Down
3 changes: 3 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ jobs:
with:
python-version: ${{ inputs.python-version }}

- name: Install Build Dependencies
run: sudo apt-get install build-essential libpq-dev python3-dev -y

- name: Install uv
uses: astral-sh/setup-uv@v5
with:
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
default_language_version:
python: "3"
python: "3.12"
cofin marked this conversation as resolved.
Show resolved Hide resolved
repos:
- repo: https://github.com/compilerla/conventional-pre-commit
rev: v3.6.0
Expand Down
1 change: 1 addition & 0 deletions litestar/channels/backends/psycopg.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ async def unsubscribe(self, channels: Iterable[str]) -> None:
for channel in channels:
await self._listener_conn.execute(SQL("UNLISTEN {channel}").format(channel=Identifier(channel)))
await self._listener_conn.commit()

self._subscribed_channels = self._subscribed_channels - set(channels)

async def stream_events(self) -> AsyncGenerator[tuple[str, bytes], None]:
Expand Down
23 changes: 17 additions & 6 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ classifiers = [
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
"Programming Language :: Python :: 3.13",
"Programming Language :: Python",
"Topic :: Internet :: WWW/HTTP",
"Topic :: Software Development :: Libraries",
Expand Down Expand Up @@ -46,7 +47,7 @@ dependencies = [
"rich-click",
"multipart>=1.2.0",
# default litestar plugins
"litestar-htmx>=0.4.0"
"litestar-htmx>=0.4.0"
]
description = "Litestar - A production-ready, highly performant, extensible ASGI API Framework"
keywords = ["api", "rest", "asgi", "litestar", "starlite"]
Expand Down Expand Up @@ -83,7 +84,8 @@ brotli = ["brotli"]
cli = ["jsbeautifier", "uvicorn[standard]", "uvloop>=0.18.0; sys_platform != 'win32'"]
cryptography = ["cryptography"]
full = [
"litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,picologging,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]",
"litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,picologging,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version < \"3.13\"",
"litestar[annotated-types,attrs,brotli,cli,cryptography,jinja,jwt,mako,minijinja,opentelemetry,piccolo,prometheus,pydantic,redis,sqlalchemy,standard,structlog,valkey]; python_version >= \"3.13\"",
]
jinja = ["jinja2>=3.1.2"]
jwt = [
Expand All @@ -94,9 +96,14 @@ mako = ["mako>=1.2.4"]
minijinja = ["minijinja>=1.0.0"]
opentelemetry = ["opentelemetry-instrumentation-asgi"]
piccolo = ["piccolo"]
picologging = ["picologging"]
picologging = ["picologging; python_version < \"3.13\""]
prometheus = ["prometheus-client"]
pydantic = ["pydantic", "email-validator", "pydantic-extra-types"]
pydantic = [
"pydantic",
"email-validator",
"pydantic-extra-types!=2.9.0; python_version < \"3.9\"",
cofin marked this conversation as resolved.
Show resolved Hide resolved
"pydantic-extra-types; python_version >= \"3.9\"",
]
redis = ["redis[hiredis]>=4.4.4"]
valkey = ["valkey[libvalkey]>=6.0.2"]
sqlalchemy = ["advanced-alchemy>=0.2.2"]
Expand Down Expand Up @@ -130,7 +137,8 @@ dev = [
"trio",
"aiosqlite",
"asyncpg>=0.29.0",
"psycopg[pool,binary]>=3.1.10,<3.2",
"psycopg[pool,binary]>=3.1.10,<3.2; python_version < \"3.13\"",
"psycopg[pool,c]; python_version >= \"3.13\"",
cofin marked this conversation as resolved.
Show resolved Hide resolved
"psycopg2-binary",
"psutil>=5.9.8",
"hypercorn>=0.16.0",
Expand Down Expand Up @@ -167,7 +175,8 @@ linting = [
test = [
"covdefaults",
"pytest",
"pytest-asyncio",
"pytest-asyncio; python_version < \"3.13\"",
"pytest-asyncio @ git+https://github.com/provinzkraut/pytest-asyncio.git@shutdown-asyncgens; python_version >= \"3.13\"",
cofin marked this conversation as resolved.
Show resolved Hide resolved
"pytest-cov",
"pytest-lazy-fixtures",
"pytest-mock",
Expand Down Expand Up @@ -285,6 +294,8 @@ module = [
"pytimeparse.*",
"importlib_resources",
"exceptiongroup",
"picologging",
"picologging.*",
]

[[tool.mypy.overrides]]
Expand Down
2 changes: 1 addition & 1 deletion tests/unit/test_channels/test_plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -170,7 +170,7 @@ def test_create_ws_route_handlers_arbitrary_channels_allowed(channels_backend: C
channels_plugin.publish("something", "foo")
assert ws.receive_text(timeout=2) == "something"

time.sleep(0.1)
time.sleep(0.4)

with client.websocket_connect("/ws/bar") as ws:
channels_plugin.publish("something else", "bar")
Expand Down
14 changes: 7 additions & 7 deletions tests/unit/test_kwargs/test_validations.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,19 +46,19 @@ def handler(my_key: str = Parameter(**{param_field: "my_key"})) -> None: # type
@pytest.mark.parametrize("reserved_kwarg", sorted(RESERVED_KWARGS))
def test_raises_when_reserved_kwargs_are_misused(reserved_kwarg: str) -> None:
decorator = post if reserved_kwarg != "socket" else websocket

exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass")
handler_with_path_param = decorator("/{" + reserved_kwarg + ":int}")(locals()["test_fn"])
local = dict(locals(), **globals())
cofin marked this conversation as resolved.
Show resolved Hide resolved
exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass", local, local)
handler_with_path_param = decorator("/{" + reserved_kwarg + ":int}")(local["test_fn"])
with pytest.raises(ImproperlyConfiguredException):
Litestar(route_handlers=[handler_with_path_param])

exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass")
handler_with_dependency = decorator("/", dependencies={reserved_kwarg: Provide(my_dependency)})(locals()["test_fn"])
exec(f"async def test_fn({reserved_kwarg}: int) -> None: pass", local, local)
handler_with_dependency = decorator("/", dependencies={reserved_kwarg: Provide(my_dependency)})(local["test_fn"])
with pytest.raises(ImproperlyConfiguredException):
Litestar(route_handlers=[handler_with_dependency])

exec(f"async def test_fn({reserved_kwarg}: int = Parameter(query='my_param')) -> None: pass")
handler_with_aliased_param = decorator("/")(locals()["test_fn"])
exec(f"async def test_fn({reserved_kwarg}: int = Parameter(query='my_param')) -> None: pass", local, local)
handler_with_aliased_param = decorator("/")(local["test_fn"])
with pytest.raises(ImproperlyConfiguredException):
Litestar(route_handlers=[handler_with_aliased_param])

Expand Down
20 changes: 15 additions & 5 deletions tests/unit/test_middleware/test_middleware_handling.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import logging
import sys
from dataclasses import dataclass
from typing import TYPE_CHECKING, Any, Awaitable, Callable, List, cast

import pytest
from _pytest.capture import CaptureFixture
from _pytest.logging import LogCaptureFixture
from starlette.middleware.base import BaseHTTPMiddleware, RequestResponseEndpoint

from litestar import Controller, Request, Response, Router, get, post
Expand All @@ -13,8 +16,6 @@
if TYPE_CHECKING:
from typing import Type

from _pytest.logging import LogCaptureFixture

from litestar.types import ASGIApp, Receive, Scope, Send

logger = logging.getLogger(__name__)
Expand Down Expand Up @@ -91,7 +92,7 @@ def handler() -> None: ...
assert middleware_instance.arg == 1


def test_request_body_logging_middleware(caplog: "LogCaptureFixture") -> None:
def test_request_body_logging_middleware(caplog: LogCaptureFixture, capsys: "CaptureFixture[str]") -> None:
@dataclass
class JSONRequest:
name: str
Expand All @@ -102,13 +103,22 @@ class JSONRequest:
def post_handler(data: JSONRequest) -> JSONRequest:
return data

with caplog.at_level(logging.INFO):
if sys.version_info < (3, 13):
with caplog.at_level(logging.INFO):
client = create_test_client(
route_handlers=[post_handler], middleware=[MiddlewareProtocolRequestLoggingMiddleware]
)
response = client.post("/", json={"name": "moishe zuchmir", "age": 40, "programmer": True})
assert response.status_code == 201
assert "test logging" in caplog.text
else:
client = create_test_client(
route_handlers=[post_handler], middleware=[MiddlewareProtocolRequestLoggingMiddleware]
)
response = client.post("/", json={"name": "moishe zuchmir", "age": 40, "programmer": True})
assert response.status_code == 201
assert "test logging" in caplog.text
log = capsys.readouterr()
assert "test logging" in log.err
cofin marked this conversation as resolved.
Show resolved Hide resolved


def test_middleware_call_order() -> None:
Expand Down
Loading
Loading