10 Commits
Author SHA1 Message Date
woggioni b434b97e70 Document that mixin setup/shutdown loops may not be running
Under RSGI __rsgi_init__ is called before the loop starts, so
asyncio.get_running_loop() raises RuntimeError inside setup(). Spell out
that mixins must use the loop they are given, since the ASGI lifespan
incidentally runs with a live loop.
2026-09-18 02:33:01 +00:00
woggioni df0e789f56 Refactor forwarded header handling into opt-in kaya-forwarded package with trusted CIDRs 2026-09-05 03:18:24 +00:00
woggioni b48ac3df38 Add kaya-cors check and publish steps to CI pipeline 2026-09-04 08:28:48 +00:00
woggioni 6c50db2ee7 Add kaya-cors package for CORS support 2026-09-04 07:19:49 +00:00
woggioni 217dce3546 Resolve client address from Forwarded and X-Forwarded-* headers 2026-09-04 06:36:22 +00:00
woggioni 338c6bd600 Add kaya-openapi package for automatic OpenAPI spec generation
CI / Build Pip package (push) Successful in 2m49s
- New packages/kaya-openapi with OpenAPIMixin, @operation decorator,
  and generate_spec() that walks the routing tree
- Enables kaya-core's Tree.register to expose the original handler
  callback as an instance attribute for metadata introspection
- Registers GET /openapi.json and GET /docs (Swagger UI) routes
- Supports  and  path parameters, docstring
  descriptions, @operation metadata, and excludes wildcard/WS routes
- Adds example/openapi.py, updates CI, README, and requirements
2026-07-25 09:20:19 +00:00
woggioni 1e1e031a56 Fix key-loop in Tree.add to reuse existing children for literal segments after a matcher boundary
CI / Build Pip package (push) Successful in 1m57s
The key-loop unconditionally called self.parse() and overwrote
result.children[key] for literal segments after the walk-down loop
broke at a parameter. This destroyed pre-existing subtrees (with their
method children and handlers) when a second route (e.g. POST) shared
the same literal sub-segments after a parameter.

Added a children.get(key) reuse check before parse(), mirroring the
walk-down loop's child-reuse logic but extended past the boundary
where parameters live in path_matchers, not children.
2026-07-24 12:54:07 +00:00
woggioni 9a314f9bd3 Allow nested routes sharing the same path parameter matcher
CI / Build Pip package (push) Successful in 2m32s
When two routes share the same parameter at the same node position
(e.g. GET /restaurants/${id} and GET /restaurants/${id}/menu),
reuse the existing equivalent matcher instead of raising a conflict.
Non-equivalent matchers (different names, kinds, or glob patterns)
at the same node for the same method still raise ValueError.
2026-07-24 06:46:07 +00:00
woggioni 2dcad41ba1 Rename kaya.session_redis → kaya.session.redis
CI / Build Pip package (push) Successful in 1m56s
- Move src/kaya/session_redis/ → src/kaya/session/redis/
- Update pyproject.toml version_file path
- Update all import references (tests, READMEs, root README)
2026-07-23 16:02:56 +00:00
woggioni c8c8c6052a Rename kaya.session_memcache → kaya.session.memcache
CI / Build Pip package (push) Failing after 1m11s
- Move src/kaya/session_memcache/ → src/kaya/session/memcache/
- Add pkgutil.extend_path to kaya.session for subpackage namespace support
- Update pyproject.toml version_file path
- Update all import references (tests, READMEs, root README)
2026-07-23 15:51:42 +00:00
+15 -2
View File
@@ -29,9 +29,22 @@ class KayaMixin(ABC):
pass pass
def setup(self, loop: AbstractEventLoop) -> None: def setup(self, loop: AbstractEventLoop) -> None:
"""Called on lifespan startup (default: no-op).""" """Called on lifespan startup (default: no-op).
Under RSGI (granian) the loop is NOT yet running when this is
called: schedule work on the passed ``loop`` (e.g.
``loop.create_task``) instead of calling
``asyncio.get_running_loop()``, which raises ``RuntimeError``
there. Under ASGI the lifespan runs inside a coroutine, so a
running loop happens to be available — do not rely on that.
"""
pass pass
def shutdown(self, loop: AbstractEventLoop) -> None: def shutdown(self, loop: AbstractEventLoop) -> None:
"""Called on lifespan shutdown (default: no-op).""" """Called on lifespan shutdown (default: no-op).
As with :meth:`setup`, use the passed ``loop``; it may already be
stopped under RSGI, so ``asyncio.get_running_loop()`` is not
reliable here either.
"""
pass pass