cullinan.application¶
cullinan.application is the semantic package for application definition and
advanced public application composition. Regular applications should prefer the shorter
top-level cullinan startup API, but maintainers and advanced integrations
still land here when they need the fuller application model:
Advanced-but-public module: this page documents a real semantic layer, not the default first-read path. Prefer Getting Started and the top-level
cullinanAPI for new applications.
@applicationdeclares the default entry method@configure(...)attaches startup settings to that method- calling the entry method starts through the curated top-level API
@moduledeclares an advanced boundary when you need module ownership, reload, and hot-pluggable runtime behavior- top-level
run()/get_asgi_app()are the shortest public startup path
The bootstrap contract also depends on the framework semantics documented in Framework Semantics: component discovery is import-executed, automatic scanning only guarantees module-top-level decorated components, and structural registration freezes after refresh().
In practice, most application code starts from business decorators and an entry method.
@module is not a manual app-registration center; it is the advanced boundary
that keeps ownership, reload, draining, and runtime switching explicit and stable.
Recommended bootstrap¶
from cullinan import application, configure, controller, get_api, service
@service
class GreetingService:
def greet(self) -> str:
return "hello"
@controller(url="/api")
class GreetingController:
greeting_service: GreetingService # ← 构造注入
@get_api(url="/whoami")
def whoami(self):
return {"message": self.greeting_service.greet()}
@configure(user_packages=["myapp"])
@application
def main(): ...
if __name__ == "__main__":
main()
When to use @module¶
Do not start with @module for a normal single-package application. The entry method is the default path.
Reach for @module when you need one or more of these:
- explicit package ownership boundaries
- multiple business domains with clear runtime separation
- hot-pluggable modules or plugin-style assembly
- stricter reload / draining / ownership semantics than the default startup path
Module ownership and boundaries¶
@module uses Python package ownership to discover components. When a component
matches more than one module package, startup fails fast. Resolve intentional
overlap with ownership_overrides.
@module(
imports=[SharedModule, OrdersModule],
ownership_overrides={"myapp.shared": SharedModule},
)
class RootModule:
pass
Runtime switching¶
Application.reload() builds a fresh candidate runtime, validates and warms it,
then atomically switches the active application. The previous runtime enters
draining state, and Application.current() continues to resolve the
request-bound snapshot until the in-flight request ends.
Maintainer / advanced note¶
ApplicationContext remains the low-level container/runtime primitive, and
Application remains available from cullinan.application for advanced
runtime-aware composition. New application setup should still start from
business decorators plus the top-level entry-method path.