Skip to content

feat(application)!: compose an application in Application, build Django URLs from it - #343

Open
mofr wants to merge 3 commits into
v34from
feat/application
Open

mofr wants to merge 3 commits into
v34from
feat/application

Conversation

@mofr

@mofr mofr commented Oct 24, 2026 •

Copy link
Copy Markdown
Collaborator

Building an application was spread over three places: find_package_routes in urls.py, the setup() functions of winter.web, winter.django and winter.openapi, and set_injector. Their order was kept by hand: setup() saw only the classes imported by then, so a @problem exception could silently get no handler.

winter.application.Application collects this into one object that adapters build from:

import service_a
import winter.django
from winter.application import Application

app = Application(modules=[service_a], injector=injector)  # several applications may share one injector
urlpatterns = winter.django.create_urls(app)
  • Scan on construction. Every module of the given packages is imported, recursively. A directory without __init__.py is not a package and is skipped. A module that fails to import fails the constructor, with a note saying why it was imported. By default the modules are those of the directory of the module that creates the Application. Under python -m the creating module is not imported a second time.
  • Definitions. The classes and functions defined in the scanned modules (not imported into them), plus whatever app.add() adds, such as generated code or a class defined elsewhere: app.add(winter.web.problem(status=404)(NotFoundException)).
  • winter.django.create_urls(app) initialises winter.web and the Django adapter once per process, registers the handlers of the application's @problem exceptions, and returns URL patterns relative to where they are included.
  • OpenAPI. generate_openapi(app, title=..., version=...) returns the spec as a dict, with no endpoint, and checks the @raises annotations of the application. serve_spec(app, spec, url=...) and serve_swagger_ui(app, spec_url=..., url=...) add the routes that serve them; the docs routes are not part of the spec. The served spec gets servers prefixed with the path the application is mounted at, computed per request, so it is correct under include('service_b/', ...).
  • Isolation test (formerly "north-star"): rewritten on observable behaviour. A @problem handler of one application must not handle that exception in another. It stays xfail while handler registries are process-global.

Breaking: WinterApplication, find_package_routes, create_django_urls_from_routes and all setup() functions are removed; README has an "Upgrading to Winter 34" section. Other behaviour changes to check when migrating:

  • Routes within one module are now ordered as written in the file, not alphabetically by class name. This matters only for overlapping URL templates in one module.
  • create_urls installs the Django throttling identity on its first call, so a custom resolver goes after it.

This PR targets v34. The 34.0 work, moving the behavioural registries into Application, lands there in small PRs and is released once, when v34 merges into master; until then the consumer can try it from the branch. winter.wsgi and the getting-started switch come after.

🤖 Generated with Claude Code

…go URLs from it

Building an application was spread over find_package_routes in urls.py, the setup() functions
of winter.web, winter.django and winter.openapi, and set_injector. Their order was manual:
setup() saw only the classes imported by then, so a @problem exception could silently get no
handler.

Application imports every module of the given packages on construction (by default, those of
the directory of the module that creates it) and holds their classes and functions as its
definitions, plus whatever is added with app.add(). Several applications may share one injector.
winter.django.create_urls(app) builds the URL patterns from it, and winter.openapi gets
generate_openapi(app, ...), serve_spec and serve_swagger_ui; the served spec and Swagger UI
work under whatever prefix the application is included at.

The behavioural registries are still process-global: the isolation test is rewritten on
observable behaviour and stays xfail.

BREAKING CHANGE: WinterApplication, find_package_routes, create_django_urls_from_routes and
the setup() functions of winter.web, winter.django and winter.openapi are removed. A @problem
exception gets its handler only when it belongs to an application. generate_openapi takes an
Application instead of routes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mofr
mofr requested a review from andrey-berenda October 24, 2026 13:18
The adapters initialise winter.web, the Django adapter and winter.openapi once per process. The
architecture document now lists this as a deviation and describes the target: each Application owns
these registries in a child injector, adapters add their entries to that application only, and the
initialize functions disappear. Each of them points to that deviation.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@mofr
mofr changed the base branch from master to v34 October 24, 2026 14:17
…lic API

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant