Implemented modular code structure

Refactored repository into kaya-core and kaya-rsgi packages
This commit is contained in:
2026-07-15 22:18:45 +08:00
parent eca6da3dc3
commit fa3ff32f66
33 changed files with 238 additions and 387 deletions
+8 -5
View File
@@ -26,14 +26,17 @@ jobs:
.venv/bin/pip install -r requirements-dev.txt .venv/bin/pip install -r requirements-dev.txt
- name: Unit tests - name: Unit tests
run: | run: |
.venv/bin/pip install . .venv/bin/pip install packages/kaya-core packages/kaya-rsgi
.venv/bin/python -m mypy -p src .venv/bin/python -m mypy -p kaya.core
.venv/bin/python -m unittest discover -s tests .venv/bin/python -m mypy -p kaya.rsgi
.venv/bin/python -m unittest discover -s packages/kaya-core/tests
.venv/bin/python -m unittest discover -s packages/kaya-rsgi/tests
- name: Publish artifacts - name: Publish artifacts
env: env:
TWINE_REPOSITORY_URL: ${{ vars.PYPI_REGISTRY_URL }} TWINE_REPOSITORY_URL: ${{ vars.PYPI_REGISTRY_URL }}
TWINE_USERNAME: ${{ vars.PUBLISHER_USERNAME }} TWINE_USERNAME: ${{ vars.PUBLISHER_USERNAME }}
TWINE_PASSWORD: ${{ secrets.PUBLISHER_TOKEN }} TWINE_PASSWORD: ${{ secrets.PUBLISHER_TOKEN }}
run: | run: |
.venv/bin/pyproject-build .venv/bin/pyproject-build packages/kaya-core
.venv/bin/twine upload --repository gitea dist/*.whl dist/*.tar.gz .venv/bin/pyproject-build packages/kaya-rsgi
.venv/bin/twine upload --repository gitea packages/kaya-core/dist/*.whl packages/kaya-core/dist/*.tar.gz packages/kaya-rsgi/dist/*.whl packages/kaya-rsgi/dist/*.tar.gz
+2 -2
View File
@@ -4,5 +4,5 @@ __pycache__
.mypy_cache .mypy_cache
_version.py _version.py
*.egg-info *.egg-info
/build build/
/dist dist/
+23 -5
View File
@@ -1,6 +1,15 @@
# kaya # kaya
A lightweight ASGI/RSGI web framework with method-aware routing, path matching, and recursive wildcard support. A lightweight ASGI web framework with method-aware routing, path matching, and recursive wildcard support.
## Packages
This repository is a monorepo for the Kaya framework. The code is split into independent packages under `packages/`:
- **kaya-core** — core routing, HTTP/WS abstractions, and ASGI adapter (`packages/kaya-core/`)
- **kaya-rsgi** — RSGI/Granian integration (`packages/kaya-rsgi/`)
Additional `kaya-*` packages can be added as new directories under `packages/`.
## Build & run locally ## Build & run locally
@@ -10,10 +19,10 @@ Install dev dependencies:
pip install --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra-index-url https://pypi.org/simple -r requirements-dev.txt pip install --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --extra-index-url https://pypi.org/simple -r requirements-dev.txt
``` ```
Build the package: Install the packages in development mode:
```bash ```bash
python -m build pip install -e packages/kaya-core -e packages/kaya-rsgi
``` ```
Run the example: Run the example:
@@ -25,11 +34,20 @@ python example/hello.py
## Tests ## Tests
```bash ```bash
PYTHONPATH=src python -m unittest discover tests python -m unittest discover -s packages/kaya-core/tests
python -m unittest discover -s packages/kaya-rsgi/tests
``` ```
## Static analysis ## Static analysis
```bash ```bash
mypy src/kaya mypy -p kaya.core
mypy -p kaya.rsgi
```
## Building packages
```bash
python -m build packages/kaya-core
python -m build packages/kaya-rsgi
``` ```
+2 -11
View File
@@ -1,15 +1,6 @@
from kaya import KayaApp, HttpContext, HttpMethod, WebSocket from kaya.core import KayaApp, HttpContext, HttpMethod, WebSocket
from typing import List from typing import List
class Hello(KayaApp):
pass
# async def handle_request(self, ctx: HttpContext) -> None:
# async for chunk in ctx.request_body:
# print(chunk.decode())
# await ctx.send_str(200, 'Hello World')
app = KayaApp() app = KayaApp()
@@ -53,4 +44,4 @@ async def echo(ws: WebSocket) -> None:
elif msg.kind == 'binary': elif msg.kind == 'binary':
data = msg.data data = msg.data
assert isinstance(data, bytes) assert isinstance(data, bytes)
await ws.send_bytes(data) await ws.send_bytes(data)
+5
View File
@@ -0,0 +1,5 @@
# kaya-core
Core package of the Kaya web framework.
Provides method-aware routing, path matching, HTTP/WebSocket abstractions, and an ASGI adapter.
+57
View File
@@ -0,0 +1,57 @@
[build-system]
requires = ["setuptools>=61.0", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta"
[project]
name = "kaya-core"
dynamic = ["version"]
authors = [
{ name="Walter Oggioni", email="oggioni.walter@gmail.com" },
]
description = "Core package of the Kaya lightweight ASGI web framework"
readme = "README.md"
requires-python = ">=3.10"
license = "MIT"
classifiers = [
'Development Status :: 3 - Alpha',
'Topic :: Utilities',
'Intended Audience :: System Administrators',
'Intended Audience :: Developers',
'Environment :: Console',
'Programming Language :: Python :: 3',
]
dependencies = [
"pwo",
"typing-extensions",
]
[project.optional-dependencies]
dev = [
"build", "mypy", "ipdb", "twine", "httpx"
]
[project.urls]
"Homepage" = "https://github.com/woggioni/kaya"
"Bug Tracker" = "https://github.com/woggioni/kaya/issues"
[tool.setuptools.packages.find]
where = ["src"]
namespaces = true
[tool.mypy]
python_version = "3.12"
disallow_untyped_defs = true
show_error_codes = true
no_implicit_optional = true
warn_return_any = true
warn_unused_ignores = true
exclude = ["scripts", "docs", "test"]
strict = true
[tool.setuptools_scm]
root = "../.."
version_file = "src/kaya/core/_version.py"
[tool.setuptools_scm.tag]
prefix = "release/"
@@ -1,17 +1,19 @@
from ._app import KayaApp from ._app import AbstractKayaApp, KayaApp
from ._http_method import HttpMethod from ._http_method import HttpMethod
from ._http_context import HttpContext from ._http_context import HttpContext
from ._tree import Tree, PathIterator from ._tree import Tree, PathIterator
from ._path_handler import PathHandler from ._path_handler import PathHandler, Matches
from ._websocket import WebSocket, WebSocketMessage from ._websocket import WebSocket, WebSocketMessage
__all__ = [ __all__ = [
'AbstractKayaApp',
'HttpMethod', 'HttpMethod',
'KayaApp', 'KayaApp',
'HttpContext', 'HttpContext',
'Tree', 'Tree',
'PathHandler', 'PathHandler',
'Matches',
'PathIterator', 'PathIterator',
'WebSocket', 'WebSocket',
'WebSocketMessage' 'WebSocketMessage'
@@ -2,31 +2,13 @@ from abc import ABC, abstractmethod
from asyncio import Queue, AbstractEventLoop from asyncio import Queue, AbstractEventLoop
from asyncio import get_running_loop from asyncio import get_running_loop
from logging import getLogger from logging import getLogger
from typing import Callable, Awaitable, Any, Mapping, Sequence, Optional, Unpack, Tuple, TYPE_CHECKING, cast from typing import Callable, Awaitable, Any, Mapping, Sequence, Optional, Unpack, Tuple, cast
from pathlib import Path, PurePath
from pwo import Maybe, AsyncQueueIterator from pwo import Maybe, AsyncQueueIterator
from hashlib import md5
from ._http_context import HttpContext from ._http_context import HttpContext
from ._http_method import HttpMethod from ._http_method import HttpMethod
from ._path_handler import Context from ._path_handler import Context
from ._types import StrOrStrings from ._types import StrOrStrings
from ._websocket import WebSocket from ._websocket import WebSocket
from base64 import b64encode, b64decode
from mimetypes import guess_type
if TYPE_CHECKING:
from _typeshed import StrOrBytesPath
try:
from ._rsgi import RsgiContext, RsgiWebSocket
from granian._granian import ( # type: ignore
RSGIHTTPProtocol,
RSGIHTTPScope,
RSGIWebsocketProtocol,
RSGIWebsocketScope as RSGIWebSocketScope,
)
except ImportError:
pass
from ._asgi import AsgiContext, AsgiWebSocket from ._asgi import AsgiContext, AsgiWebSocket
from ._tree import Tree from ._tree import Tree
from ._types.asgi import LifespanScope, HTTPScope as ASGIHTTPScope, WebSocketScope as ASGIWebSocketScope from ._types.asgi import LifespanScope, HTTPScope as ASGIHTTPScope, WebSocketScope as ASGIWebSocketScope
@@ -87,20 +69,6 @@ class AbstractKayaApp(ABC):
async def handle_websocket(self, ws: WebSocket) -> None: async def handle_websocket(self, ws: WebSocket) -> None:
raise NotImplementedError() raise NotImplementedError()
def __rsgi_init__(self, loop: AbstractEventLoop) -> None:
self.setup(loop)
def __rsgi_del__(self, loop: AbstractEventLoop) -> None:
self.shutdown(loop)
async def __rsgi__(self, scope: RSGIHTTPScope | RSGIWebSocketScope, protocol: RSGIHTTPProtocol | RSGIWebsocketProtocol) -> None:
if scope.proto == 'ws':
ws = RsgiWebSocket(scope, protocol) # type: ignore[arg-type]
await self.handle_websocket(ws)
else:
ctx = RsgiContext(scope, protocol) # type: ignore[arg-type]
await self.handle_request(ctx)
class KayaApp(AbstractKayaApp): class KayaApp(AbstractKayaApp):
_tree: Tree _tree: Tree
@@ -115,7 +83,6 @@ class KayaApp(AbstractKayaApp):
await handler.handle_request(ctx, captured) await handler.handle_request(ctx, captured)
else: else:
await ctx.send_empty(404) await ctx.send_empty(404)
pass
async def handle_websocket(self, ws: WebSocket) -> None: async def handle_websocket(self, ws: WebSocket) -> None:
result = self._tree.get_handler(ws.path, HttpMethod.WS) result = self._tree.get_handler(ws.path, HttpMethod.WS)
@@ -124,7 +91,6 @@ class KayaApp(AbstractKayaApp):
await handler.handle_request(ws, captured) await handler.handle_request(ws, captured)
else: else:
await ws.close(1000) await ws.close(1000)
pass
def route(self, def route(self,
paths: StrOrStrings, paths: StrOrStrings,
@@ -179,4 +145,3 @@ class KayaApp(AbstractKayaApp):
def PATCH(self, path: str, recursive: bool = False) -> Callable[[HttpHandler], HttpHandler]: def PATCH(self, path: str, recursive: bool = False) -> Callable[[HttpHandler], HttpHandler]:
return self.route(path, (HttpMethod.PATCH,), recursive) return self.route(path, (HttpMethod.PATCH,), recursive)
@@ -0,0 +1,31 @@
from typing import (
TypedDict,
Literal,
Iterable,
Tuple,
Optional,
NotRequired,
Dict,
Any,
Union,
Mapping,
Sequence
)
from .base import StrOrStrings, PathMatcherResult
from .asgi import ASGIVersions, HTTPScope, WebSocketScope, LifespanScope
from .._http_method import HttpMethod
type NodeType = (str | HttpMethod)
__all__ = [
'HttpMethod',
'HTTPScope',
'LifespanScope',
'ASGIVersions',
'WebSocketScope',
'NodeType',
'StrOrStrings',
'PathMatcherResult'
]
@@ -2,7 +2,7 @@ import unittest
import json import json
import httpx import httpx
from pwo import async_test from pwo import async_test
from kaya import KayaApp, HttpContext, HttpMethod from kaya.core import KayaApp, HttpContext, HttpMethod
from typing import Sequence, List from typing import Sequence, List
@@ -1,7 +1,6 @@
from typing import Sequence, Tuple, Optional, List from typing import Sequence, Tuple, Optional, List
from kaya import Tree, PathHandler, HttpContext, HttpMethod, PathIterator from kaya.core import Tree, PathHandler, HttpContext, HttpMethod, PathIterator
from kaya import HttpMethod
from pwo import Maybe from pwo import Maybe
import unittest import unittest
@@ -1,7 +1,7 @@
import unittest import unittest
from typing import Any, Callable, Awaitable, List, Mapping, Optional from typing import Any, Callable, Awaitable, List, Mapping, Optional
from pwo import async_test from pwo import async_test
from kaya import KayaApp, WebSocket, WebSocketMessage from kaya.core import KayaApp, WebSocket, WebSocketMessage
def websocket_scope(path: str = '/ws') -> Mapping[str, Any]: def websocket_scope(path: str = '/ws') -> Mapping[str, Any]:
@@ -151,25 +151,3 @@ class WebSocketTest(unittest.TestCase):
self.assertEqual(sent_messages[0]['type'], 'websocket.accept') self.assertEqual(sent_messages[0]['type'], 'websocket.accept')
self.assertEqual(len(sent_messages), 1) self.assertEqual(len(sent_messages), 1)
class RsgiWebSocketTest(unittest.TestCase):
def test_misconfigured_granian(self):
from kaya._rsgi import RsgiWebSocket
class FakeScope:
scheme = 'ws'
path = '/ws'
query_string = ''
headers = {}
client = '127.0.0.1:12345'
server = '127.0.0.1:80'
class FakeProtocol:
pass
with self.assertRaises(RuntimeError) as ctx:
RsgiWebSocket(FakeScope(), FakeProtocol()) # type: ignore[arg-type]
self.assertIn('Granian was not configured for websockets', str(ctx.exception))
+5
View File
@@ -0,0 +1,5 @@
# kaya-rsgi
RSGI/Granian integration for the Kaya web framework.
Provides `RsgiContext`, `RsgiWebSocket`, and `RsgiApplication` to run Kaya apps on Granian's RSGI protocol.
@@ -3,42 +3,43 @@ requires = ["setuptools>=61.0", "setuptools-scm>=8"]
build-backend = "setuptools.build_meta" build-backend = "setuptools.build_meta"
[project] [project]
name = "kaya" name = "kaya-rsgi"
dynamic = ["version"] dynamic = ["version"]
authors = [ authors = [
{ name="Walter Oggioni", email="oggioni.walter@gmail.com" }, { name="Walter Oggioni", email="oggioni.walter@gmail.com" },
] ]
description = "A lightweight ASGI/RSGI web framework" description = "RSGI/Granian integration for the Kaya web framework"
readme = "README.md" readme = "README.md"
requires-python = ">=3.10" requires-python = ">=3.10"
license = "MIT"
classifiers = [ classifiers = [
'Development Status :: 3 - Alpha', 'Development Status :: 3 - Alpha',
'Topic :: Utilities', 'Topic :: Utilities',
'License :: OSI Approved :: MIT License',
'Intended Audience :: System Administrators', 'Intended Audience :: System Administrators',
'Intended Audience :: Developers', 'Intended Audience :: Developers',
'Environment :: Console', 'Environment :: Console',
'License :: OSI Approved :: MIT License',
'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3',
] ]
dependencies = [ dependencies = [
"kaya-core",
"granian>=2.0",
"pwo", "pwo",
] ]
[project.optional-dependencies] [project.optional-dependencies]
dev = [ dev = [
"build", "mypy", "ipdb", "twine", "granian", "httpx" "build", "mypy", "ipdb", "twine"
]
rsgi = [
"granian"
] ]
[project.urls] [project.urls]
"Homepage" = "https://github.com/woggioni/kaya" "Homepage" = "https://github.com/woggioni/kaya"
"Bug Tracker" = "https://github.com/woggioni/kaya/issues" "Bug Tracker" = "https://github.com/woggioni/kaya/issues"
[tool.setuptools.packages.find]
where = ["src"]
namespaces = true
[tool.mypy] [tool.mypy]
python_version = "3.12" python_version = "3.12"
disallow_untyped_defs = true disallow_untyped_defs = true
@@ -50,7 +51,8 @@ exclude = ["scripts", "docs", "test"]
strict = true strict = true
[tool.setuptools_scm] [tool.setuptools_scm]
version_file = "src/kaya/_version.py" root = "../.."
version_file = "src/kaya/rsgi/_version.py"
[tool.setuptools_scm.tag] [tool.setuptools_scm.tag]
prefix = "release/" prefix = "release/"
@@ -0,0 +1,11 @@
from ._rsgi import RsgiApplication, RsgiContext, RsgiWebSocket
from ._types import HTTPScope, WebSocketScope
__all__ = [
'RsgiApplication',
'RsgiContext',
'RsgiWebSocket',
'HTTPScope',
'WebSocketScope',
]
@@ -14,13 +14,17 @@ from typing import (
cast cast
) )
from granian._granian import RSGIHTTPProtocol, RSGIHTTPScope, RSGIWebsocketProtocol, RSGIWebsocketScope, RSGIWebsocketTransport # type: ignore[attr-defined] from granian._granian import ( # type: ignore[attr-defined]
RSGIHTTPProtocol,
RSGIHTTPScope,
RSGIWebsocketProtocol,
RSGIWebsocketScope,
RSGIWebsocketTransport,
)
from pwo import Maybe from pwo import Maybe
from ._types import StrOrStrings from kaya.core import AbstractKayaApp, HttpContext, HttpMethod, WebSocket, WebSocketMessage
from ._http_context import HttpContext from kaya.core._types import StrOrStrings
from ._http_method import HttpMethod
from ._websocket import WebSocket, WebSocketMessage
class RsgiContext(HttpContext): class RsgiContext(HttpContext):
@@ -56,12 +60,6 @@ class RsgiContext(HttpContext):
self.request_body = cast(AsyncIterator[bytes], protocol) self.request_body = cast(AsyncIterator[bytes], protocol)
self.protocol = protocol self.protocol = protocol
# @staticmethod
# def _rearrange_headers(headers: Mapping[str, Sequence[str]]) -> List[Tuple[str, str]]:
# return list(
# ((key, value) for key, values in headers.items() for value in values)
# )
@staticmethod @staticmethod
def _rearrange_headers(headers: Mapping[str, StrOrStrings]) -> List[Tuple[str, str]]: def _rearrange_headers(headers: Mapping[str, StrOrStrings]) -> List[Tuple[str, str]]:
result = [] result = []
@@ -177,3 +175,26 @@ class RsgiWebSocket(WebSocket):
if message.kind == 'close': if message.kind == 'close':
raise StopAsyncIteration raise StopAsyncIteration
return message return message
class RsgiApplication:
_app: AbstractKayaApp
def __init__(self, app: AbstractKayaApp) -> None:
self._app = app
def __rsgi_init__(self, loop: Any) -> None:
self._app.setup(loop)
def __rsgi_del__(self, loop: Any) -> None:
self._app.shutdown(loop)
async def __rsgi__(self,
scope: RSGIHTTPScope | RSGIWebsocketScope,
protocol: RSGIHTTPProtocol | RSGIWebsocketProtocol) -> None:
if scope.proto == 'ws':
ws = RsgiWebSocket(scope, protocol) # type: ignore[arg-type]
await self._app.handle_websocket(ws)
else:
ctx = RsgiContext(scope, protocol) # type: ignore[arg-type]
await self._app.handle_request(ctx)
@@ -1,17 +1,11 @@
from typing import ( from typing import (
Sequence,
TypedDict, TypedDict,
Literal, Literal,
Iterable,
Tuple,
Optional, Optional,
NotRequired,
Dict,
Any,
Union,
Mapping, Mapping,
) )
class HTTPScope(TypedDict): class HTTPScope(TypedDict):
proto: Literal['http'] proto: Literal['http']
rsgi_version: str rsgi_version: str
@@ -37,4 +31,10 @@ class WebSocketScope(TypedDict):
path: str path: str
query_string: str query_string: str
headers: Mapping[str, str] headers: Mapping[str, str]
authority: Optional[str] authority: Optional[str]
__all__ = [
'HTTPScope',
'WebSocketScope',
]
+22
View File
@@ -0,0 +1,22 @@
import unittest
from kaya.rsgi import RsgiWebSocket
class RsgiWebSocketTest(unittest.TestCase):
def test_misconfigured_granian(self):
class FakeScope:
scheme = 'ws'
path = '/ws'
query_string = ''
headers = {}
client = '127.0.0.1:12345'
server = '127.0.0.1:80'
class FakeProtocol:
pass
with self.assertRaises(RuntimeError) as ctx:
RsgiWebSocket(FakeScope(), FakeProtocol()) # type: ignore[arg-type]
self.assertIn('Granian was not configured for websockets', str(ctx.exception))
+11 -149
View File
@@ -1,154 +1,16 @@
# #
# This file is autogenerated by pip-compile with Python 3.14 # Development dependencies for the Kaya monorepo.
# by the following command: # Install with:
# # pip install -r requirements-dev.txt
# pip-compile --allow-unsafe --extra=dev --extra=rsgi --output-file=requirements-dev.txt pyproject.toml
# #
--index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple --index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
--extra-index-url https://pypi.org/simple --extra-index-url https://pypi.org/simple
anyio==4.14.1 build
# via httpx mypy
ast-serialize==0.5.0 ipdb
# via mypy twine
asttokens==3.0.1 httpx
# via stack-data granian
build==1.5.0 pwo
# via kaya (pyproject.toml) typing-extensions
certifi==2026.6.17
# via
# httpcore
# httpx
# requests
cffi==2.0.0
# via cryptography
charset-normalizer==3.4.7
# via requests
click==8.4.2
# via granian
cryptography==49.0.0
# via secretstorage
decorator==5.3.1
# via
# ipdb
# ipython
docutils==0.23
# via readme-renderer
executing==2.2.1
# via stack-data
granian==2.7.7
# via kaya (pyproject.toml)
h11==0.16.0
# via httpcore
httpcore==1.0.9
# via httpx
httpx==0.28.1
# via kaya (pyproject.toml)
id==1.6.1
# via twine
idna==3.18
# via
# anyio
# httpx
# requests
ipdb==0.13.13
# via kaya (pyproject.toml)
ipython==9.15.0
# via ipdb
ipython-pygments-lexers==1.1.1
# via ipython
jaraco-classes==3.4.0
# via keyring
jaraco-context==6.1.2
# via keyring
jaraco-functools==4.5.0
# via keyring
jedi==0.20.0
# via ipython
jeepney==0.9.0
# via
# keyring
# secretstorage
keyring==25.7.0
# via twine
librt==0.11.0
# via mypy
markdown-it-py==4.2.0
# via rich
matplotlib-inline==0.2.2
# via ipython
mdurl==0.1.2
# via markdown-it-py
more-itertools==11.1.0
# via
# jaraco-classes
# jaraco-functools
mypy==2.1.0
# via kaya (pyproject.toml)
mypy-extensions==1.1.0
# via mypy
nh3==0.3.6
# via readme-renderer
packaging==26.2
# via
# build
# twine
parso==0.8.7
# via jedi
pathspec==1.1.1
# via mypy
pexpect==4.9.0
# via ipython
prompt-toolkit==3.0.52
# via ipython
psutil==7.2.2
# via ipython
ptyprocess==0.7.0
# via pexpect
pure-eval==0.2.3
# via stack-data
pwo==0.1.2
# via kaya (pyproject.toml)
pycparser==3.0
# via cffi
pygments==2.20.0
# via
# ipython
# ipython-pygments-lexers
# readme-renderer
# rich
pyproject-hooks==1.2.0
# via build
readme-renderer==45.0
# via twine
requests==2.34.2
# via
# requests-toolbelt
# twine
requests-toolbelt==1.0.0
# via twine
rfc3986==2.0.0
# via twine
rich==15.0.0
# via twine
secretstorage==3.5.0
# via keyring
stack-data==0.6.3
# via ipython
traitlets==5.15.1
# via
# ipython
# matplotlib-inline
twine==6.2.0
# via kaya (pyproject.toml)
typing-extensions==4.15.0
# via
# mypy
# pwo
urllib3==2.7.0
# via
# id
# requests
# twine
wcwidth==0.8.1
# via prompt-toolkit
-17
View File
@@ -1,17 +0,0 @@
#
# This file is autogenerated by pip-compile with Python 3.14
# by the following command:
#
# pip-compile --allow-unsafe --extra=rsgi --output-file=requirements.txt pyproject.toml
#
--index-url https://gitea.woggioni.net/api/packages/woggioni/pypi/simple
--extra-index-url https://pypi.org/simple
click==8.4.2
# via granian
granian==2.7.7
# via kaya (pyproject.toml)
pwo==0.1.2
# via kaya (pyproject.toml)
typing-extensions==4.15.0
# via pwo
-12
View File
@@ -1,12 +0,0 @@
from dataclasses import dataclass
from typing import (
Optional,
Dict,
List,
)
from ._types import NodeType
from ._path_handler import PathHandler
from ._path_matcher import PathMatcher
-92
View File
@@ -1,92 +0,0 @@
from typing import (
TypedDict,
Literal,
Iterable,
Tuple,
Optional,
NotRequired,
Dict,
Any,
Union,
Mapping,
Sequence
)
from .base import StrOrStrings, PathMatcherResult
from kaya._http_method import HttpMethod
type NodeType = (str | HttpMethod)
class ASGIVersions(TypedDict):
spec_version: str
version: Union[Literal["2.0"], Literal["3.0"]]
class HTTPScope(TypedDict):
type: Literal["http"]
asgi: ASGIVersions
http_version: str
method: str
scheme: str
path: str
raw_path: bytes
query_string: bytes
root_path: str
headers: Iterable[Tuple[bytes, bytes]]
client: Optional[Tuple[str, int]]
server: Optional[Tuple[str, Optional[int]]]
state: NotRequired[Dict[str, Any]]
extensions: Optional[Dict[str, Dict[object, object]]]
class WebSocketScope(TypedDict):
type: Literal["websocket"]
asgi: ASGIVersions
http_version: str
scheme: str
path: str
raw_path: bytes
query_string: bytes
root_path: str
headers: Iterable[Tuple[bytes, bytes]]
client: Optional[Tuple[str, int]]
server: Optional[Tuple[str, Optional[int]]]
subprotocols: Iterable[str]
state: NotRequired[Dict[str, Any]]
extensions: Optional[Dict[str, Dict[object, object]]]
class LifespanScope(TypedDict):
type: Literal["lifespan"]
asgi: ASGIVersions
state: NotRequired[Dict[str, Any]]
class RSGI:
class Scope(TypedDict):
proto: Literal['http'] # = 'http'
rsgi_version: str
http_version: str
server: str
client: str
scheme: str
method: str
path: str
query_string: str
headers: Mapping[str, str]
authority: Optional[str]
__all__ = [
'HttpMethod',
'HTTPScope',
'LifespanScope',
'RSGI',
'ASGIVersions',
'WebSocketScope',
'NodeType',
'StrOrStrings',
'PathMatcherResult'
]