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

Add free-threaded Python support #494

Merged
merged 4 commits into from
Feb 2, 2025
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
14 changes: 7 additions & 7 deletions .github/workflows/lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -13,18 +13,18 @@ jobs:
lint:
runs-on: ubuntu-latest

env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ env.PYTHON_VERSION }}
uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ env.PYTHON_VERSION }}
enable-cache: false
- name: Install
run: |
python -m venv .venv
source .venv/bin/activate
pip install maturin
maturin develop --extras=lint
uv python install ${{ env.UV_PYTHON }}
uv venv .venv
uv sync --group lint
- name: Lint
run: |
source .venv/bin/activate
Expand Down
58 changes: 30 additions & 28 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,24 +21,25 @@ jobs:
- '3.11'
- '3.12'
- '3.13'
- '3.13t'

env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
enable-cache: false
- name: Install
run: |
python -m venv .venv
source .venv/bin/activate
pip install maturin
maturin develop --extras=test
uv python install ${{ env.UV_PYTHON }}
uv venv .venv
uv sync --group all
uv run --no-sync maturin develop --uv
- name: Test
run: |
source .venv/bin/activate
py.test -v tests
make test

macos:
runs-on: macos-latest
Expand All @@ -51,24 +52,25 @@ jobs:
- '3.11'
- '3.12'
- '3.13'
- '3.13t'

env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
enable-cache: false
- name: Install
run: |
python -m venv .venv
source .venv/bin/activate
pip install maturin
maturin develop --extras=test
uv python install ${{ env.UV_PYTHON }}
uv venv .venv
uv sync --group all
uv run --no-sync maturin develop --uv
- name: Test
run: |
source .venv/bin/activate
py.test -v tests
make test

windows:
runs-on: windows-latest
Expand All @@ -81,21 +83,21 @@ jobs:
- '3.11'
- '3.12'
- '3.13'
- '3.13t'

env:
UV_PYTHON: ${{ matrix.python-version }}
steps:
- uses: actions/checkout@v4
- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v5
- uses: astral-sh/setup-uv@v5
with:
python-version: ${{ matrix.python-version }}
allow-prereleases: true
enable-cache: false
- name: Install
run: |
python -m venv venv
venv/Scripts/Activate.ps1
pip install maturin
maturin develop --extras=test
uv python install ${{ env.UV_PYTHON }}
uv venv .venv
uv sync --group all
uv run --no-sync maturin develop --uv
- name: Test
run: |
venv/Scripts/Activate.ps1
py.test -v tests
uv run --no-sync pytest -v tests
3 changes: 2 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,8 @@ pysources = granian tests
.PHONY: build-dev
build-dev:
@rm -f granian/*.so
maturin develop --extras lint,test
uv sync --group all
maturin develop

.PHONY: format
format:
Expand Down
2 changes: 1 addition & 1 deletion granian/__init__.py
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
from ._granian import __version__ # noqa: F401
from .server import Granian as Granian
from .server import Server as Granian # noqa: F401
1 change: 1 addition & 0 deletions granian/_granian.pyi
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ from ._types import WebsocketMessage
from .http import HTTP1Settings, HTTP2Settings

__version__: str
BUILD_GIL: bool

class RSGIHeaders:
def __contains__(self, key: str) -> bool: ...
Expand Down
2 changes: 1 addition & 1 deletion granian/_internal.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ def load_module(module_name: str, raise_on_failure: bool = True) -> Optional[Mod
except ImportError:
if sys.exc_info()[-1].tb_next:
raise RuntimeError(
f"While importing '{module_name}', an ImportError was raised:" f'\n\n{traceback.format_exc()}'
f"While importing '{module_name}', an ImportError was raised:\n\n{traceback.format_exc()}"
)
elif raise_on_failure:
raise RuntimeError(f"Could not import '{module_name}'.")
Expand Down
6 changes: 2 additions & 4 deletions granian/_loops.py
Original file line number Diff line number Diff line change
Expand Up @@ -77,16 +77,14 @@ def build_asyncio_loop():

@loops.register('uvloop', packages=['uvloop'])
def build_uv_loop(uvloop):
asyncio.set_event_loop_policy(uvloop.EventLoopPolicy())
loop = asyncio.new_event_loop()
loop = uvloop.new_event_loop()
asyncio.set_event_loop(loop)
return loop


@loops.register('rloop', packages=['rloop'])
def build_rloop(rloop):
asyncio.set_event_loop_policy(rloop.EventLoopPolicy())
loop = asyncio.new_event_loop()
loop = rloop.new_event_loop()
asyncio.set_event_loop(loop)
return loop

Expand Down
11 changes: 9 additions & 2 deletions granian/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
from .errors import FatalError
from .http import HTTP1Settings, HTTP2Settings
from .log import LogLevels
from .server import Granian
from .server import Server


_AnyCallable = Callable[..., Any]
Expand Down Expand Up @@ -70,6 +70,11 @@ def option(*param_decls: str, cls: Optional[Type[click.Option]] = None, **attrs:
type=click.IntRange(1),
help='Number of blocking threads (per worker)',
)
@option(
'--io-blocking-threads',
type=click.IntRange(1),
help='Number of I/O blocking threads (per worker)',
)
@option(
'--threading-mode',
type=EnumType(ThreadModes),
Expand Down Expand Up @@ -265,6 +270,7 @@ def cli(
workers: int,
threads: int,
blocking_threads: Optional[int],
io_blocking_threads: Optional[int],
threading_mode: ThreadModes,
loop: Loops,
task_impl: TaskImpl,
Expand Down Expand Up @@ -313,13 +319,14 @@ def cli(
print('Unable to parse provided logging config.')
raise click.exceptions.Exit(1)

server = Granian(
server = Server(
app,
address=host,
port=port,
interface=interface,
workers=workers,
threads=threads,
io_blocking_threads=io_blocking_threads,
blocking_threads=blocking_threads,
threading_mode=threading_mode,
loop=loop,
Expand Down
7 changes: 7 additions & 0 deletions granian/server/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
from .._granian import BUILD_GIL


if BUILD_GIL:
from .mp import MPServer as Server
else:
from .mt import MTServer as Server # noqa: F401
Loading
Loading