Skip to main content

Cullinan — A pluggable IoC/DI web framework

Project description

Python version PyPI version PyPI downloads Ask DeepWiki GitHub stars License

   _____      _ _ _                      
  / ____|    | | (_)                     
 | |    _   _| | |_ _ __   __ _ _ __     
 | |   | | | | | | | '_ \ / _` | '_ \    
 | |___| |_| | | | | | | | (_| | | | |   
 \_____\__, _|_|_|_|_| |_|\__,_|_| |_|  

Cullinan

A lightweight, modular Python web framework with built-in IoC/DI

Cullinan is built on Tornado (HTTP/WebSocket) and focuses on:

  • A unified registry model for controllers, services, handlers
  • Built-in IoC/DI with request scope and service lifecycle hooks
  • Production-friendly startup/shutdown flow on a default port 4080

✨ Features

Core Framework

  • Simple decorator-based routing (@controller, @get_api, @post_api, ...)
  • Type-safe parameter system with Path, Query, Body, Header, File (v0.90+)
  • Unified parameter syntax: param: Type = Type(...) (v0.90a5+)
  • Pure type annotation as Query: page: int automatically becomes Query parameter (v0.90a5+)
  • as_required() shortcut: File.as_required(), Body.as_required() (v0.90a5+)
  • DynamicBody for flexible request body access with safe accessors (v0.90a4+)
  • RawBody for raw unparsed request body (bytes) (v0.90a5+)
  • FileInfo/FileList for file upload with validation (v0.90a5+)
  • @field_validator for dataclass field validation (v0.90a5+)
  • ResponseSerializer for automatic response serialization (v0.90a5+)
  • Pluggable model handlers for Pydantic and custom model integration (v0.90a5+)
  • Auto type conversion and validation (ge, le, min_length, regex, etc.)
  • Modular architecture with registry, DI, and lifecycle management
  • Built-in IoC/DI with InjectByName and Inject support
  • Unified lifecycle hooks (on_post_construct, on_startup, on_shutdown, on_pre_destroy) on all components
  • Duck Typing lifecycle: no base class inheritance required (v0.92+)
  • Designed for tests: resettable registries and request-scoped dependencies

Services & WebSocket

  • Service registry with dependency resolution
  • WebSocket support via @websocket_handler and registry pattern
  • Request context / request scope for per-request objects

Deployment & Production

  • Packaging-friendly (Nuitka, PyInstaller)
  • Cross-platform: Windows, Linux, macOS
  • Based on production-tested Tornado

📚 Documentation

Language / 语言

Version Notes

  • Current series: v0.92

    • Unified lifecycle management with on_post_construct, on_startup, on_shutdown, on_pre_destroy
    • Duck Typing lifecycle: no base class inheritance required
    • Phase ordering control via get_phase() method
    • Breaking change: Removed legacy on_init() and on_destroy() methods
    • New type-safe parameter system with Path, Query, Body, Header, File
    • DynamicBody for flexible request body access with safe accessors
    • FileInfo/FileList for file upload handling with validation
    • @field_validator for dataclass field validation
    • ResponseSerializer for automatic response serialization
    • Pluggable model handlers for Pydantic and custom model integration
    • Auto type conversion and parameter validation
    • IoC/DI 2.0 architecture with ApplicationContext
    • Single container entry point with freeze-after-startup
    • Definition/Factory separation for dependency management
    • Structured diagnostics with stable dependency chains
    • Core module with registry, DI, lifecycle management
    • Enhanced service layer with dependency injection
    • WebSocket support with unified registry
    • Request context management
  • Migration from v0.83:

    • See Import Migration Guide for detailed migration steps
    • Legacy APIs are deprecated but still functional in v0.90
    • Will be removed in v1.0

🚀 Quick Start

Install

Use pip from PyPI:

pip install -U pip
pip install cullinan

Ensure you have a working Python 3.8+ environment (virtualenv/conda/system Python are all fine).

Minimal Application

# app.py
from cullinan import application
from cullinan.controller import controller, get_api, post_api
from cullinan.params import Query, Body, DynamicBody

@controller(url='/api')
class HelloController:
    # Type-safe query parameters (new unified syntax)
    @get_api(url='/hello')
    def hello(self, name: str = Query(default='World')):
        return self.response_factory(
            status=200,
            body={"message": f"Hello, {name}!"}
        )
    
    # Pure type annotation as Query (v0.90a5+)
    @get_api(url='/users')
    def list_users(self, page: int = 1, size: int = 10):
        # page and size are automatically Query parameters
        return {"page": page, "size": size}
    
    # DynamicBody for flexible request body access
    @post_api(url='/users')
    def create_user(self, body: DynamicBody):
        return self.response_factory(
            status=200,
            body={"name": body.name, "age": body.get('age', 0)}
        )

if __name__ == '__main__':
    # Framework-level entrypoint, no manual app instantiation required
    application.run()

Run:

python app.py
# GET:  http://localhost:4080/api/hello?name=Cullinan
# POST: http://localhost:4080/api/users  {"name": "John", "age": 25}

For a more detailed onboarding, follow docs/getting_started.md (or docs/zh/getting_started.md).


💡 Dependency Injection Patterns

Cullinan ships with a core IoC/DI system. Recommended patterns:

1. InjectByName (recommended default)

from cullinan import service, Service
from cullinan.core import InjectByName

@service
class EmailService(Service):
    def send_email(self, to, subject, body):
        print(f"Sending email to {to}: {subject}")
        return True

@service
class UserService(Service):
    """Service for user management with email dependency."""

    # Name-based injection, no direct import needed
    email_service = InjectByName('EmailService')

    def create_user(self, name, email):
        user = {'name': name, 'email': email}
        self.email_service.send_email(email, "Welcome", f"Welcome {name}!")
        return user

2. Inject + TYPE_CHECKING (IDE autocomplete)

from typing import TYPE_CHECKING
from cullinan import service, Service
from cullinan.core import Inject

if TYPE_CHECKING:
    from services.email import EmailService

@service
class UserService(Service):
    # Type-hinted injection for better IDE support
    email_service: 'EmailService' = Inject()

    def create_user(self, name, email):
        self.email_service.send_email(email, "Welcome", f"Welcome {name}!")
        return {"name": name, "email": email}

Controllers and RESTful decorators

from cullinan.controller import controller, get_api, post_api
from cullinan.core import InjectByName
from cullinan.params import Query, Body

@controller(url='/api')
class UserController:
    # Inject the UserService by name
    user_service = InjectByName('UserService')

    # Type-safe query parameter (v0.90+)
    @get_api(url='/users')
    def get_user(self, id: Query(str)):
        return self.response_factory(
            status=200,
            body={"message": "User fetched successfully", "user_id": id},
        )

    # Type-safe body parameters (v0.90+)
    @post_api(url='/users')
    def create_user(self, name: Body(str, required=True), email: Body(str, required=True)):
        user = self.user_service.create_user(name, email)
        return self.response_factory(
            status=201,
            body={"message": "User created successfully", "data": user},
        )

Note: RESTful decorators are defined as def get_api(**kwargs) etc. Only keyword arguments are supported. Use @get_api(url='/users'), not @get_api('/users').

For full parameter system documentation, see docs/parameter_system_guide.md.

More DI patterns and controller examples are documented in docs/wiki/injection.md and docs/wiki/restful_api.md (and their Chinese counterparts).


📖 More Examples

The examples/ folder contains additional runnable demos (HTTP, middleware, DI). Refer to:

  • examples/hello_http.py – minimal HTTP example using the handler registry
  • examples/controller_di_middleware.py – controller + DI + middleware integration

Each example is referenced from the docs so you can cross-check behavior with tests (tests/ directory).


📖 Additional Documentation

For advanced topics, see the docs:

  • Configuration – environment and config options
  • Packaging – building executables with Nuitka/PyInstaller
  • Service Layer – service patterns and DI
  • Registry Pattern – unified registry behavior
  • Testing – running tests and using test registries
  • Troubleshooting – common issues and diagnostics

🔗 Links


📄 License

MIT License – see LICENSE for details.


💻 Maintainer

Plumeink

Project details


Release history Release notifications | RSS feed

Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cullinan-0.93a3.tar.gz (193.0 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cullinan-0.93a3-py3-none-any.whl (241.7 kB view details)

Uploaded Python 3

File details

Details for the file cullinan-0.93a3.tar.gz.

File metadata

  • Download URL: cullinan-0.93a3.tar.gz
  • Upload date:
  • Size: 193.0 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for cullinan-0.93a3.tar.gz
Algorithm Hash digest
SHA256 88bf43b5a6daf88db2e5490461e1c0bf9a05af07fce45616fdae73bd14ce1a51
MD5 763e6d065668bf718366414ece556b5f
BLAKE2b-256 2bfcf6de087909426a88b0811f7eb0d957d2484e83c21a8a38a4c0a3f9b7ad60

See more details on using hashes here.

File details

Details for the file cullinan-0.93a3-py3-none-any.whl.

File metadata

  • Download URL: cullinan-0.93a3-py3-none-any.whl
  • Upload date:
  • Size: 241.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/6.2.0 CPython/3.14.5

File hashes

Hashes for cullinan-0.93a3-py3-none-any.whl
Algorithm Hash digest
SHA256 15c02aab81d9388197d3ce69359512f34d478aa3bd8f5f231545a7cdc021b222
MD5 b6b128f3b1ba9b30a68848f907d0d3b5
BLAKE2b-256 5857e5a62ab6ef0d16f9ac4354092a4dc7cd32ae0503b342196cc35dfba517a8

See more details on using hashes here.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page