{"id":34667339,"url":"https://github.com/plumeink/cullinan","last_synced_at":"2026-05-30T15:00:40.671Z","repository":{"id":62565919,"uuid":"198608366","full_name":"plumeink/Cullinan","owner":"plumeink","description":"Cullinan is written based on tornado to help the project quickly build web application","archived":false,"fork":false,"pushed_at":"2026-05-30T13:15:21.000Z","size":3149,"stargazers_count":10,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"master","last_synced_at":"2026-05-30T14:18:39.623Z","etag":null,"topics":["mvc","python","web"],"latest_commit_sha":null,"homepage":"","language":"Python","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/plumeink.png","metadata":{"files":{"readme":"README.MD","changelog":null,"contributing":"docs/contributing.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2019-07-24T09:59:42.000Z","updated_at":"2026-02-19T09:48:14.000Z","dependencies_parsed_at":"2024-11-09T19:26:58.677Z","dependency_job_id":"e619549b-5b83-4fe7-86e8-7bd3f122cbf6","html_url":"https://github.com/plumeink/Cullinan","commit_stats":{"total_commits":56,"total_committers":8,"mean_commits":7.0,"dds":0.6785714285714286,"last_synced_commit":"e0f680ddfca348329a62488c3fce3f904aefffa2"},"previous_names":["orestu/cullinan"],"tags_count":41,"template":false,"template_full_name":null,"purl":"pkg:github/plumeink/Cullinan","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumeink%2FCullinan","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumeink%2FCullinan/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumeink%2FCullinan/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumeink%2FCullinan/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/plumeink","download_url":"https://codeload.github.com/plumeink/Cullinan/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/plumeink%2FCullinan/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":33696683,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-05-30T02:00:06.278Z","response_time":92,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["mvc","python","web"],"created_at":"2025-12-24T19:19:28.530Z","updated_at":"2026-05-30T15:00:40.624Z","avatar_url":"https://github.com/plumeink.png","language":"Python","funding_links":[],"categories":[],"sub_categories":[],"readme":"![Python version](https://img.shields.io/badge/python-3.7%20|%203.8%20|%203.9%20|%203.10%20|%203.11%20|%203.12%20|%203.13-blue)\n![PyPI version](https://img.shields.io/pypi/v/cullinan.svg?style=flat\u0026logo=pypi\u0026color=green)\n![PyPI downloads](https://img.shields.io/pypi/dm/cullinan.svg?style=flat\u0026logo=pypi\u0026color=blue)\n[![Ask DeepWiki](https://deepwiki.com/badge.svg)](https://deepwiki.com/plumeink/Cullinan)\n![GitHub stars](https://img.shields.io/github/stars/plumeink/cullinan.svg?style=flat\u0026logo=github\u0026color=white)\n![License](https://img.shields.io/github/license/plumeink/cullinan.svg?style=flat\u0026color=white)\n\n\n```                                              \n   _____      _ _ _                      \n  / ____|    | | (_)                     \n | |    _   _| | |_ _ __   __ _ _ __     \n | |   | | | | | | | '_ \\ / _` | '_ \\    \n | |___| |_| | | | | | | | (_| | | | |   \n \\_____\\__, _|_|_|_|_| |_|\\__,_|_| |_|  \n```\n\n# Cullinan\n\n**A lightweight, modular Python web framework with built-in IoC/DI**\n\nCullinan is built on Tornado (HTTP/WebSocket) and focuses on:\n- A unified registry model for controllers, services, handlers\n- Built-in IoC/DI with request scope and service lifecycle hooks\n- Production-friendly startup/shutdown flow on a default port **4080**\n\n---\n\n## ✨ Features\n\n### Core Framework\n- Simple decorator-based routing (`@controller`, `@get_api`, `@post_api`, ...)\n- **Type-safe parameter system** with `Path`, `Query`, `Body`, `Header`, `File` (v0.90+)\n- **Unified parameter syntax**: `param: Type = Type(...)` (v0.90a5+)\n- **Pure type annotation as Query**: `page: int` automatically becomes Query parameter (v0.90a5+)\n- **as_required() shortcut**: `File.as_required()`, `Body.as_required()` (v0.90a5+)\n- **DynamicBody** for flexible request body access with safe accessors (v0.90a4+)\n- **RawBody** for raw unparsed request body (bytes) (v0.90a5+)\n- **FileInfo/FileList** for file upload with validation (v0.90a5+)\n- **@field_validator** for dataclass field validation (v0.90a5+)\n- **ResponseSerializer** for automatic response serialization (v0.90a5+)\n- **Pluggable model handlers** for Pydantic and custom model integration (v0.90a5+)\n- **Auto type conversion** and validation (ge, le, min_length, regex, etc.)\n- Modular architecture with registry, DI, and lifecycle management\n- Built-in IoC/DI with `InjectByName` and `Inject` support\n- Unified lifecycle hooks (`on_post_construct`, `on_startup`, `on_shutdown`, `on_pre_destroy`) on all components\n- Duck Typing lifecycle: no base class inheritance required (v0.92+)\n- Designed for tests: resettable registries and request-scoped dependencies\n\n### Services \u0026 WebSocket\n- Service registry with dependency resolution\n- WebSocket support via `@websocket_handler` and registry pattern\n- Request context / request scope for per-request objects\n\n### Deployment \u0026 Production\n- Packaging-friendly (Nuitka, PyInstaller)\n- Cross-platform: Windows, Linux, macOS\n- Based on production-tested Tornado\n\n---\n\n## 📚 Documentation\n\n### Language / 语言\n\n- **[English Documentation](https://plumeink.github.io/Cullinan/)** – complete English docs\n  - [Quick start](https://plumeink.github.io/Cullinan/getting_started/)\n  - [Architecture](https://plumeink.github.io/Cullinan/architecture/) · [Lifecycle](https://plumeink.github.io/Cullinan/wiki/lifecycle/)\n  - [IoC/DI 2.0](https://plumeink.github.io/Cullinan/wiki/ioc_di_v2/) · [Migration Guide](https://plumeink.github.io/Cullinan/import_migration_090/)\n  - [RESTful routing](https://plumeink.github.io/Cullinan/wiki/restful_api/)\n- **[中文文档](https://plumeink.github.io/Cullinan/zh/)** – 完整中文文档\n  - [快速开始](https://plumeink.github.io/Cullinan/zh/getting_started/)\n  - [架构](https://plumeink.github.io/Cullinan/zh/architecture/) · [生命周期](https://plumeink.github.io/Cullinan/zh/wiki/lifecycle/)\n  - [IoC/DI 2.0](https://plumeink.github.io/Cullinan/zh/wiki/ioc_di_v2/) · [迁移指南](https://plumeink.github.io/Cullinan/zh/import_migration_090/)\n  - [RESTful 路由](https://plumeink.github.io/Cullinan/zh/wiki/restful_api/)\n\n### Version Notes\n\n- Current series: **v0.92**\n  - **Unified lifecycle management** with `on_post_construct`, `on_startup`, `on_shutdown`, `on_pre_destroy`\n  - **Duck Typing lifecycle**: no base class inheritance required\n  - **Phase ordering control** via `get_phase()` method\n  - **Breaking change**: Removed legacy `on_init()` and `on_destroy()` methods\n  - **New type-safe parameter system** with `Path`, `Query`, `Body`, `Header`, `File`\n  - **DynamicBody** for flexible request body access with safe accessors\n  - **FileInfo/FileList** for file upload handling with validation\n  - **@field_validator** for dataclass field validation\n  - **ResponseSerializer** for automatic response serialization\n  - **Pluggable model handlers** for Pydantic and custom model integration\n  - **Auto type conversion** and parameter validation\n  - IoC/DI 2.0 architecture with `ApplicationContext`\n  - Single container entry point with freeze-after-startup\n  - Definition/Factory separation for dependency management\n  - Structured diagnostics with stable dependency chains\n  - Core module with registry, DI, lifecycle management\n  - Enhanced service layer with dependency injection\n  - WebSocket support with unified registry\n  - Request context management\n\n- Migration from v0.83:\n  - See [Import Migration Guide](docs/import_migration_090.md) for detailed migration steps\n  - Legacy APIs are deprecated but still functional in v0.90\n  - Will be removed in v1.0\n\n---\n\n## 🚀 Quick Start\n\n### Install\n\nUse pip from PyPI:\n\n```bash\npip install -U pip\npip install cullinan\n```\n\nEnsure you have a working Python 3.8+ environment (virtualenv/conda/system Python are all fine).\n\n### Minimal Application\n\n```python\n# app.py\nfrom cullinan import application\nfrom cullinan.controller import controller, get_api, post_api\nfrom cullinan.params import Query, Body, DynamicBody\n\n@controller(url='/api')\nclass HelloController:\n    # Type-safe query parameters (new unified syntax)\n    @get_api(url='/hello')\n    def hello(self, name: str = Query(default='World')):\n        return self.response_factory(\n            status=200,\n            body={\"message\": f\"Hello, {name}!\"}\n        )\n    \n    # Pure type annotation as Query (v0.90a5+)\n    @get_api(url='/users')\n    def list_users(self, page: int = 1, size: int = 10):\n        # page and size are automatically Query parameters\n        return {\"page\": page, \"size\": size}\n    \n    # DynamicBody for flexible request body access\n    @post_api(url='/users')\n    def create_user(self, body: DynamicBody):\n        return self.response_factory(\n            status=200,\n            body={\"name\": body.name, \"age\": body.get('age', 0)}\n        )\n\nif __name__ == '__main__':\n    # Framework-level entrypoint, no manual app instantiation required\n    application.run()\n```\n\nRun:\n\n```bash\npython app.py\n# GET:  http://localhost:4080/api/hello?name=Cullinan\n# POST: http://localhost:4080/api/users  {\"name\": \"John\", \"age\": 25}\n```\n\nFor a more detailed onboarding, follow `docs/getting_started.md` (or `docs/zh/getting_started.md`).\n\n---\n\n## 💡 Dependency Injection Patterns\n\nCullinan ships with a core IoC/DI system. Recommended patterns:\n\n### 1. InjectByName (recommended default)\n\n```python\nfrom cullinan import service, Service\nfrom cullinan.core import InjectByName\n\n@service\nclass EmailService(Service):\n    def send_email(self, to, subject, body):\n        print(f\"Sending email to {to}: {subject}\")\n        return True\n\n@service\nclass UserService(Service):\n    \"\"\"Service for user management with email dependency.\"\"\"\n\n    # Name-based injection, no direct import needed\n    email_service = InjectByName('EmailService')\n\n    def create_user(self, name, email):\n        user = {'name': name, 'email': email}\n        self.email_service.send_email(email, \"Welcome\", f\"Welcome {name}!\")\n        return user\n```\n\n### 2. Inject + TYPE_CHECKING (IDE autocomplete)\n\n```python\nfrom typing import TYPE_CHECKING\nfrom cullinan import service, Service\nfrom cullinan.core import Inject\n\nif TYPE_CHECKING:\n    from services.email import EmailService\n\n@service\nclass UserService(Service):\n    # Type-hinted injection for better IDE support\n    email_service: 'EmailService' = Inject()\n\n    def create_user(self, name, email):\n        self.email_service.send_email(email, \"Welcome\", f\"Welcome {name}!\")\n        return {\"name\": name, \"email\": email}\n```\n\n### Controllers and RESTful decorators\n\n```python\nfrom cullinan.controller import controller, get_api, post_api\nfrom cullinan.core import InjectByName\nfrom cullinan.params import Query, Body\n\n@controller(url='/api')\nclass UserController:\n    # Inject the UserService by name\n    user_service = InjectByName('UserService')\n\n    # Type-safe query parameter (v0.90+)\n    @get_api(url='/users')\n    def get_user(self, id: Query(str)):\n        return self.response_factory(\n            status=200,\n            body={\"message\": \"User fetched successfully\", \"user_id\": id},\n        )\n\n    # Type-safe body parameters (v0.90+)\n    @post_api(url='/users')\n    def create_user(self, name: Body(str, required=True), email: Body(str, required=True)):\n        user = self.user_service.create_user(name, email)\n        return self.response_factory(\n            status=201,\n            body={\"message\": \"User created successfully\", \"data\": user},\n        )\n```\n\n\u003e 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')`.\n\nFor full parameter system documentation, see `docs/parameter_system_guide.md`.\n\nMore DI patterns and controller examples are documented in `docs/wiki/injection.md` and `docs/wiki/restful_api.md` (and their Chinese counterparts).\n\n---\n\n## 📖 More Examples\n\nThe `examples/` folder contains additional runnable demos (HTTP, middleware, DI). Refer to:\n\n- `examples/hello_http.py` – minimal HTTP example using the handler registry\n- `examples/controller_di_middleware.py` – controller + DI + middleware integration\n\nEach example is referenced from the docs so you can cross-check behavior with tests (`tests/` directory).\n\n---\n\n## 📖 Additional Documentation\n\nFor advanced topics, see the docs:\n\n- **Configuration** – environment and config options\n- **Packaging** – building executables with Nuitka/PyInstaller\n- **Service Layer** – service patterns and DI\n- **Registry Pattern** – unified registry behavior\n- **Testing** – running tests and using test registries\n- **Troubleshooting** – common issues and diagnostics\n\n---\n\n## 🔗 Links\n\n- **Documentation**: [docs/README.md](docs/README.md)\n- **GitHub**: https://github.com/plumeink/Cullinan\n- **PyPI**: https://pypi.org/project/cullinan/\n- **Issues**: https://github.com/plumeink/Cullinan/issues\n- **Discussions**: https://github.com/plumeink/Cullinan/discussions\n\n---\n\n## 📄 License\n\nMIT License – see [LICENSE](LICENSE) for details.\n\n---\n\n## 💻 Maintainer\n\nPlumeink\n\n[\u003cimg src=\"https://avatars.githubusercontent.com/u/104434649?v=4\" width = \"40\" height = \"40\"/\u003e](https://github.com/plumeink)","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplumeink%2Fcullinan","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fplumeink%2Fcullinan","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fplumeink%2Fcullinan/lists"}