Skip to main content

kyro

CI

Symfony-style attribute routing for Django: declare routes with decorators directly on your views and controllers instead of maintaining a long urls.py.

Status: pre-alpha, under active development.

Installation

pip install kyro-routing   # or: uv add kyro-routing

The package is imported as kyro. Add "kyro" to INSTALLED_APPS for the management commands and the system checks.

Quick start

# blog/views.py
from django.http import HttpResponse
from kyro import route

from blog.models import Post


@route("/hello/{name}", name="hello")
def hello(request, name):
    return HttpResponse(f"Hello {name}")


@route("/blog", name="blog_")
class BlogController:
    @route("", name="index", methods="GET")
    def index(self, request): ...

    @route("", name="create", methods="POST")
    def create(self, request): ...

    @route(r"/page/{page<\d+>?1}", name="list")  # before /{slug}, which would catch /page
    def list(self, request, page: int): ...

    @route("/{slug}", name="show")
    def show(self, request, post: Post):  # fetched by slug, 404 if missing
        ...
# project/urls.py
import kyro

urlpatterns = kyro.discover()  # imports the `views` module of every installed app

Routes are plain Django URL patterns: reverse("blog_show", kwargs={"slug": "hello"}) and {% url "blog_index" %} work as usual, and so do include() and namespaces.

Paths

Both syntaxes are accepted, even in the same path:

Syntax Meaning
<name>, <int:id> Django parameter and converter
{name} parameter (anything but /)
{id<\d+>} parameter with a regex requirement
{page?1} optional parameter with default "1"
{page?} optional parameter with default None

Trailing parameters with a default (inline or in defaults=) are optional: /blog/{page?1} matches /blog and /blog/2.

@route arguments

Argument
path the path, relative to the class prefix if any
name route name, appended to the class name prefix; generated when omitted
methods "GET" or ["GET", "POST"]; others get a 405. GET also allows HEAD
requirements {"id": r"\d+"}
defaults defaults of optional parameters and extra kwargs passed to the view
priority higher priority routes are matched first; otherwise declaration order

Several routes may share a path with different methods: kyro dispatches each request to the right view. @route can be stacked to give a view several routes.

What can be routed

  • Functions, sync or async. Put @route above or below other decorators.
  • Methods of any class: the class is instantiated for each request. A @route on the class is a prefix (path, name, requirements, defaults, methods) for its methods. Classes defined inside a function must be decorated (@route() for no prefix).
  • A class with __call__ and no routed method, or a Django View: the class route is the route of the class itself.

Parameter conversion

Annotated view arguments are converted from the URL:

  • int, float, Decimal, bool, UUID, date, datetime, Enum: an invalid value is a 404.

  • Django models, fetched like Symfony's entity resolver:

    • {post} for post: Post: lookup by primary key;
    • {slug} for post: Post: lookup by fields named like URL parameters (the parameter is only passed to the view if its signature accepts it);
    • post: Annotated[Post, MapModel(slug="post_slug")]: explicit mapping.

    A missing object is a 404, or None when the annotation is optional (Post | None).

Debugging routes

With "kyro" in INSTALLED_APPS, manage.py debug_router lists every route of the project, kyro or not, in matching order:

$ python manage.py debug_router
 -------------- ---------- ------------------
  Name           Method     Path
 -------------- ---------- ------------------
  blog_index     GET|HEAD   /blog
  blog_create    POST       /blog
  blog_show      ANY        /blog/{slug}
 -------------- ---------- ------------------
  • debug_router blog_show shows one route in detail: Django routes, requirements, defaults, priority, view and the file and line where it is declared. A partial name shows the detail when it matches one route, the list of matches otherwise.
  • --show-views adds the view of each route to the list, --format json outputs JSON and --urlconf inspects another URLconf than ROOT_URLCONF.

manage.py router_match tells which route serves a request, method dispatch included:

$ python manage.py router_match /blog --method POST
 ------------- ---------- ------- --------------------
  Name          Method     Path    Result
 ------------- ---------- ------- --------------------
  blog_index    GET|HEAD   /blog   method not allowed
  blog_create   POST       /blog   match
 ------------- ---------- ------- --------------------

Route 'blog_create' matches.

It then shows the route in detail with the kwargs its view receives. Routes matching the path but not the method, or hidden by an earlier route (shadowed), are listed; -v 2 lists every route. The command fails when no route serves the request (404 or 405). --format json and --urlconf work as for debug_router.

System checks

With "kyro" in INSTALLED_APPS, manage.py check (and runserver) warns about:

Id Problem
kyro.W001 a route without parameters is served by an earlier route, for all or some of its methods (/blog/{slug} declared before /blog/new)
kyro.W002 a route name is shared with other routes, so reverse() only returns one of them
kyro.W003 routes declared with @route are not in the URLconf (kyro.discover() forgotten?)
kyro.W004 a parameter has a default but is not trailing, so it cannot be omitted

Silence one with SILENCED_SYSTEM_CHECKS = ["kyro.W003"].

Settings

  • KYRO_DISCOVER (default ["views"]): app submodules imported by kyro.discover().

kyro.discover(["blog"]) imports the given modules (for a package, its KYRO_DISCOVER submodules too) and returns only their routes, to mount them under a prefix: path("blog/", include(kyro.discover(["blog"]))).

Development

uv sync              # create .venv and install dependencies
uv run pytest        # run the test suite
uv run ruff check .  # lint
uv run ruff format . # format
uv run python -m mypy # type check (strict)

Releasing

  1. Set the version in src/kyro/__init__.py (__version__), and in CHANGELOG.md rename [Unreleased] to [x.y.z] - YYYY-MM-DD and update the links at the bottom.
  2. Optionally, try the release out on TestPyPI: Actions → Release → Run workflow.
  3. Commit, tag and push: git tag vx.y.z && git push origin main vx.y.z. The Release workflow publishes to TestPyPI, then PyPI.

Metadata

Release files for kyro-routing 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for kyro-routing 0.1.0
File Size Uploaded Provenance Actions
kyro_routing-0.1.0.tar.gz 70.0 kB With provenance Details for kyro_routing-0.1.0.tar.gz

Built distribution (wheel)

Table of built distributions (wheels) for kyro-routing 0.1.0
File Interpreter ABI Platform Actions
kyro_routing-0.1.0-py3-none-any.whl Python 3 none any Details for kyro_routing-0.1.0-py3-none-any.whl

Total release size: 97.0 kB

Release files / kyro_routing-0.1.0.tar.gz

Download URL kyro_routing-0.1.0.tar.gz
Size 70.0 kB
Tags Source
SHA-256 checksum
How to use checksums
f71064ee285a850d44cfc4d43bc85300e5bd8dcb65755aedae527ea3d160e1bc
BLAKE2b-256 checksum
How to use checksums
aa87d98b8cae065304f8aa8969283cecce48cd0a46042cd2e4077451477ecc07
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

Release files / kyro_routing-0.1.0-py3-none-any.whl

Download URL kyro_routing-0.1.0-py3-none-any.whl
Size 27.0 kB
Tags Python 3
SHA-256 checksum
How to use checksums
b1e4ce77bd9d4a47f3e77040079d1988cc5e599bec581b972b9cd151728bc597
BLAKE2b-256 checksum
How to use checksums
44185f702a17211cecc1479d587f544ac77d784483089a9e50b4361b856dd536
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via twine/7.0.0 CPython/3.13.16

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

Security for kyro-routing 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

Full provenance

All 2 files in this release have been uploaded with provenance metadata.

Select from 2 files

Attestation for kyro_routing-0.1.0.tar.gz

PyPI Publish Attestation

PyPI verified that this artifact, at this checksum, originated from the publisher listed below.

Signed by GitHub Actions, verified by PyPI on Nov 1, 2026.

Transparency log

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files Full provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page