austial 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (104) hide show
  1. austial-0.1.1/.claude/settings.local.json +7 -0
  2. austial-0.1.1/.gitignore +11 -0
  3. austial-0.1.1/.pre-commit-config.yaml +36 -0
  4. austial-0.1.1/.vscode/settings.json +3 -0
  5. austial-0.1.1/LICENSE +21 -0
  6. austial-0.1.1/PKG-INFO +241 -0
  7. austial-0.1.1/README.md +211 -0
  8. austial-0.1.1/assets/apple-icon.png +0 -0
  9. austial-0.1.1/assets/icon.svg +9 -0
  10. austial-0.1.1/assets/logo-dark.svg +137 -0
  11. austial-0.1.1/assets/logo.svg +137 -0
  12. austial-0.1.1/austial/__init__.py +136 -0
  13. austial-0.1.1/austial/cli/__init__.py +0 -0
  14. austial-0.1.1/austial/cli/generators/__init__.py +13 -0
  15. austial-0.1.1/austial/cli/generators/app_module_patcher.py +61 -0
  16. austial-0.1.1/austial/cli/generators/base.py +61 -0
  17. austial-0.1.1/austial/cli/generators/controller_gen.py +21 -0
  18. austial-0.1.1/austial/cli/generators/module_gen.py +30 -0
  19. austial-0.1.1/austial/cli/generators/new_project.py +67 -0
  20. austial-0.1.1/austial/cli/generators/resource_gen.py +39 -0
  21. austial-0.1.1/austial/cli/generators/service_gen.py +21 -0
  22. austial-0.1.1/austial/cli/main.py +133 -0
  23. austial-0.1.1/austial/cli/naming.py +33 -0
  24. austial-0.1.1/austial/cli/templates/controller/controller.py.j2 +8 -0
  25. austial-0.1.1/austial/cli/templates/module/module.py.j2 +6 -0
  26. austial-0.1.1/austial/cli/templates/project/app_controller.py.j2 +13 -0
  27. austial-0.1.1/austial/cli/templates/project/app_module.py.j2 +19 -0
  28. austial-0.1.1/austial/cli/templates/project/app_service.py.j2 +7 -0
  29. austial-0.1.1/austial/cli/templates/project/env_example.j2 +2 -0
  30. austial-0.1.1/austial/cli/templates/project/gitignore.j2 +11 -0
  31. austial-0.1.1/austial/cli/templates/project/main.py.j2 +37 -0
  32. austial-0.1.1/austial/cli/templates/project/modules/health/guards/api_key_guard.py.j2 +13 -0
  33. austial-0.1.1/austial/cli/templates/project/modules/health/health_controller.py.j2 +20 -0
  34. austial-0.1.1/austial/cli/templates/project/modules/health/health_dto.py.j2 +10 -0
  35. austial-0.1.1/austial/cli/templates/project/modules/health/health_module.py.j2 +10 -0
  36. austial-0.1.1/austial/cli/templates/project/modules/health/health_service.py.j2 +19 -0
  37. austial-0.1.1/austial/cli/templates/project/pre_commit_config.yaml.j2 +36 -0
  38. austial-0.1.1/austial/cli/templates/project/pyproject.toml.j2 +46 -0
  39. austial-0.1.1/austial/cli/templates/project/readme.j2 +68 -0
  40. austial-0.1.1/austial/cli/templates/project/tests/e2e/app_e2e_spec.py.j2 +38 -0
  41. austial-0.1.1/austial/cli/templates/project/tests/unit/health_service_spec.py.j2 +35 -0
  42. austial-0.1.1/austial/cli/templates/resource/controller.py.j2 +33 -0
  43. austial-0.1.1/austial/cli/templates/resource/create_dto.py.j2 +5 -0
  44. austial-0.1.1/austial/cli/templates/resource/entity.py.j2 +6 -0
  45. austial-0.1.1/austial/cli/templates/resource/module.py.j2 +9 -0
  46. austial-0.1.1/austial/cli/templates/resource/service.py.j2 +39 -0
  47. austial-0.1.1/austial/cli/templates/resource/update_dto.py.j2 +5 -0
  48. austial-0.1.1/austial/cli/templates/service/service.py.j2 +6 -0
  49. austial-0.1.1/austial/common/__init__.py +106 -0
  50. austial-0.1.1/austial/common/decorators/__init__.py +48 -0
  51. austial-0.1.1/austial/common/decorators/controllers.py +109 -0
  52. austial-0.1.1/austial/common/decorators/filters.py +31 -0
  53. austial-0.1.1/austial/common/decorators/guards.py +20 -0
  54. austial-0.1.1/austial/common/decorators/injectable.py +68 -0
  55. austial-0.1.1/austial/common/decorators/interceptors.py +20 -0
  56. austial-0.1.1/austial/common/decorators/modules.py +65 -0
  57. austial-0.1.1/austial/common/decorators/params.py +50 -0
  58. austial-0.1.1/austial/common/decorators/pipes.py +20 -0
  59. austial-0.1.1/austial/common/enums.py +15 -0
  60. austial-0.1.1/austial/common/exceptions/__init__.py +29 -0
  61. austial-0.1.1/austial/common/exceptions/http_exceptions.py +109 -0
  62. austial-0.1.1/austial/common/filters/__init__.py +4 -0
  63. austial-0.1.1/austial/common/filters/all_exceptions_filter.py +31 -0
  64. austial-0.1.1/austial/common/filters/base.py +28 -0
  65. austial-0.1.1/austial/common/guards/__init__.py +3 -0
  66. austial-0.1.1/austial/common/guards/base.py +39 -0
  67. austial-0.1.1/austial/common/interceptors/__init__.py +4 -0
  68. austial-0.1.1/austial/common/interceptors/base.py +30 -0
  69. austial-0.1.1/austial/common/interceptors/transform_interceptor.py +19 -0
  70. austial-0.1.1/austial/common/logger.py +56 -0
  71. austial-0.1.1/austial/common/middleware/__init__.py +4 -0
  72. austial-0.1.1/austial/common/middleware/base.py +60 -0
  73. austial-0.1.1/austial/common/middleware/logging_middleware.py +24 -0
  74. austial-0.1.1/austial/common/pipes/__init__.py +3 -0
  75. austial-0.1.1/austial/common/pipes/validation_pipe.py +51 -0
  76. austial-0.1.1/austial/config/__init__.py +4 -0
  77. austial-0.1.1/austial/config/config_module.py +31 -0
  78. austial-0.1.1/austial/config/config_service.py +22 -0
  79. austial-0.1.1/austial/core/__init__.py +19 -0
  80. austial-0.1.1/austial/core/app.py +155 -0
  81. austial-0.1.1/austial/core/container.py +161 -0
  82. austial-0.1.1/austial/core/dynamic_module.py +22 -0
  83. austial-0.1.1/austial/core/exception_dispatch.py +23 -0
  84. austial-0.1.1/austial/core/factory.py +43 -0
  85. austial-0.1.1/austial/core/metadata.py +65 -0
  86. austial-0.1.1/austial/core/module_scanner.py +62 -0
  87. austial-0.1.1/austial/core/reflector.py +38 -0
  88. austial-0.1.1/austial/core/router_builder.py +184 -0
  89. austial-0.1.1/austial/database/__init__.py +3 -0
  90. austial-0.1.1/austial/database/database_module.py +61 -0
  91. austial-0.1.1/austial/terminus/__init__.py +14 -0
  92. austial-0.1.1/austial/terminus/database_health_indicator.py +26 -0
  93. austial-0.1.1/austial/terminus/health_check_service.py +42 -0
  94. austial-0.1.1/austial/terminus/health_indicator.py +28 -0
  95. austial-0.1.1/austial/terminus/memory_health_indicator.py +20 -0
  96. austial-0.1.1/austial/testing/__init__.py +3 -0
  97. austial-0.1.1/austial/testing/testing_module.py +64 -0
  98. austial-0.1.1/pyproject.toml +68 -0
  99. austial-0.1.1/tests/__init__.py +0 -0
  100. austial-0.1.1/tests/e2e/__init__.py +0 -0
  101. austial-0.1.1/tests/e2e/request_lifecycle_spec.py +125 -0
  102. austial-0.1.1/tests/unit/__init__.py +0 -0
  103. austial-0.1.1/tests/unit/health_check_service_spec.py +48 -0
  104. austial-0.1.1/uv.lock +1534 -0
@@ -0,0 +1,7 @@
1
+ {
2
+ "permissions": {
3
+ "allow": [
4
+ "Bash(uv --version)"
5
+ ]
6
+ }
7
+ }
@@ -0,0 +1,11 @@
1
+ __pycache__/
2
+ *.py[cod]
3
+ .venv/
4
+ .env
5
+ *.db
6
+ .pytest_cache/
7
+ .coverage
8
+ htmlcov/
9
+ dist/
10
+ build/
11
+ *.egg-info/
@@ -0,0 +1,36 @@
1
+ repos:
2
+ - repo: https://github.com/pre-commit/pre-commit-hooks
3
+ rev: v5.0.0
4
+ hooks:
5
+ - id: trailing-whitespace
6
+ - id: end-of-file-fixer
7
+ - id: check-yaml
8
+ - id: check-toml
9
+ - id: check-merge-conflict
10
+ - id: check-added-large-files
11
+ - id: mixed-line-ending
12
+ - id: debug-statements
13
+
14
+ - repo: https://github.com/astral-sh/ruff-pre-commit
15
+ rev: v0.15.21
16
+ hooks:
17
+ - id: ruff
18
+ args: [--fix]
19
+ - id: ruff-format
20
+
21
+ - repo: local
22
+ hooks:
23
+ - id: mypy
24
+ name: mypy
25
+ entry: uv run mypy austial tests
26
+ language: system
27
+ pass_filenames: false
28
+ types: [python]
29
+
30
+ - id: pytest
31
+ name: pytest
32
+ entry: uv run pytest -q
33
+ language: system
34
+ pass_filenames: false
35
+ types: [python]
36
+ stages: [pre-push]
@@ -0,0 +1,3 @@
1
+ {
2
+ "snyk.advanced.autoSelectOrganization": true
3
+ }
austial-0.1.1/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Aayush Solanki
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
austial-0.1.1/PKG-INFO ADDED
@@ -0,0 +1,241 @@
1
+ Metadata-Version: 2.4
2
+ Name: austial
3
+ Version: 0.1.1
4
+ Summary: A NestJS-inspired, batteries-included web framework for Python, built on top of FastAPI.
5
+ Project-URL: Homepage, https://github.com/austial/austial-py
6
+ Project-URL: Repository, https://github.com/austial/austial-py
7
+ Project-URL: Issues, https://github.com/austial/austial-py/issues
8
+ License: MIT
9
+ License-File: LICENSE
10
+ Requires-Python: >=3.11
11
+ Requires-Dist: aiosqlite>=0.20.0
12
+ Requires-Dist: fastapi>=0.115.0
13
+ Requires-Dist: httpx>=0.27.0
14
+ Requires-Dist: jinja2>=3.1.0
15
+ Requires-Dist: pydantic-settings>=2.4.0
16
+ Requires-Dist: pydantic>=2.7.0
17
+ Requires-Dist: python-multipart>=0.0.9
18
+ Requires-Dist: rich>=13.7.0
19
+ Requires-Dist: sqlalchemy>=2.0.30
20
+ Requires-Dist: typer>=0.12.0
21
+ Requires-Dist: uvicorn[standard]>=0.30.0
22
+ Provides-Extra: dev
23
+ Requires-Dist: mypy>=1.11.0; extra == 'dev'
24
+ Requires-Dist: pre-commit>=3.7.0; extra == 'dev'
25
+ Requires-Dist: pytest-asyncio>=0.23.0; extra == 'dev'
26
+ Requires-Dist: pytest-cov>=5.0.0; extra == 'dev'
27
+ Requires-Dist: pytest>=8.2.0; extra == 'dev'
28
+ Requires-Dist: ruff>=0.6.0; extra == 'dev'
29
+ Description-Content-Type: text/markdown
30
+
31
+ <p align="center">
32
+ <img src="assets/icon.svg" alt="Austial icon" width="56">
33
+ </p>
34
+
35
+ <p align="center">
36
+ <picture>
37
+ <source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
38
+ <source media="(prefers-color-scheme: light)" srcset="assets/logo.svg">
39
+ <img src="assets/logo.svg" alt="Austial" width="380">
40
+ </picture>
41
+ </p>
42
+
43
+ <p align="center">
44
+ <b>A NestJS-style, batteries-included web framework for Python, built on top of FastAPI.</b>
45
+ </p>
46
+
47
+ <p align="center">
48
+ <a href="https://pypi.org/project/austial/"><img src="https://img.shields.io/pypi/v/austial?label=pypi" alt="PyPI version"></a>
49
+ <a href="https://github.com/austial/austial-py/tags"><img src="https://img.shields.io/github/v/tag/austial/austial-py?label=tag&sort=semver" alt="Latest tag"></a>
50
+ <a href="https://github.com/austial/austial-py/commits/main"><img src="https://img.shields.io/github/last-commit/austial/austial-py" alt="Last commit"></a>
51
+ <a href="https://github.com/austial/austial-py/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
52
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python 3.11+"></a>
53
+ <a href="https://fastapi.tiangolo.com/"><img src="https://img.shields.io/badge/built%20with-FastAPI-009688?logo=fastapi&logoColor=white" alt="Built with FastAPI"></a>
54
+ <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/managed%20with-uv-DE5FE9" alt="Managed with uv"></a>
55
+ </p>
56
+
57
+ # Austial
58
+
59
+ Austial gives Python the exact developer experience of [NestJS](https://nestjs.com/):
60
+ decorator-driven modules/controllers/providers, real constructor-based
61
+ dependency injection, the full request lifecycle (guards, pipes,
62
+ interceptors, exception filters, middleware), a config layer, a database
63
+ layer, Terminus-style health checks, a testing module -- and a CLI so you can
64
+ scaffold new projects and artifacts the same way `nest new`/`nest generate` do.
65
+
66
+ This repository (`austial-py`) is the **framework itself** -- an installable
67
+ library, no bundled sample app. To see it in action, scaffold a new project
68
+ with the CLI (see [Quickstart](#quickstart) below); that generated project is
69
+ the "hello world" app, kept separate from the framework's own source tree.
70
+
71
+ ## Why
72
+
73
+ FastAPI is a fantastic ASGI toolkit, but it doesn't prescribe an application
74
+ architecture. Austial is opinionated on top of FastAPI the way Nest is
75
+ opinionated on top of Express/Fastify: modules own controllers and providers,
76
+ providers get dependency-injected by type, and the request pipeline
77
+ (guard → pipe → interceptor → handler → filter) is a first-class concept
78
+ instead of something you hand-roll with dependencies everywhere.
79
+
80
+ ## Architecture at a glance
81
+
82
+ | NestJS | Austial |
83
+ |---|---|
84
+ | `@Module()` | `@Module(imports=, controllers=, providers=, exports=)` |
85
+ | `@Controller()` / `@Get()` etc. | `@Controller(prefix)` / `@Get/@Post/@Put/@Patch/@Delete/@Options/@Head` |
86
+ | `@Injectable()` | `@Injectable(scope=Scope.DEFAULT \| Scope.TRANSIENT)` |
87
+ | Constructor DI | Container resolves `__init__` type hints recursively, singleton by default |
88
+ | `NestFactory.create(AppModule)` | `AustialFactory.create(AppModule)` -> `AustialApplication` |
89
+ | `app.listen(3000)` | `await app.listen(8000)` |
90
+ | `CanActivate` / `@UseGuards` | `CanActivate` ABC + `ExecutionContext`, `@UseGuards(...)` |
91
+ | `NestInterceptor` / `@UseInterceptors` | `NestInterceptor` ABC (`intercept(context, call_next)`) + `@UseInterceptors(...)` |
92
+ | `PipeTransform` / `ValidationPipe` | `PipeTransform` ABC + `ValidationPipe` |
93
+ | `ExceptionFilter` / `@Catch` | `ExceptionFilter` ABC + `@Catch(...)`, default `AllExceptionsFilter` |
94
+ | `NestMiddleware`, `MiddlewareConsumer` | `NestMiddleware` ABC, `configure(consumer)` on modules |
95
+ | `ConfigModule.forRoot()` | `ConfigModule.for_root(env_file=".env")` / `ConfigService` |
96
+ | `TypeOrmModule.forRootAsync` | `DatabaseModule.for_root_async(use_factory=, inject=)` (SQLAlchemy async) |
97
+ | `@nestjs/terminus` | `austial.terminus.HealthCheckService` + `MemoryHealthIndicator`, `DatabaseHealthIndicator` |
98
+ | `@nestjs/testing` | `austial.testing.Test.create_testing_module(...).compile()` |
99
+ | `nest new` / `nest generate` | `austial new <name>` / `austial generate\|g module\|controller\|service\|resource <name>` |
100
+
101
+ ### A note on file names
102
+
103
+ Nest names files with dots (`health.controller.ts`) because Node resolves
104
+ modules by explicit relative path strings. Python's `import a.b` instead
105
+ resolves `a` as a package and `b` as a submodule -- a literal file
106
+ `health.controller.py` isn't importable via a normal `import` statement. So
107
+ Austial uses underscore suffixes instead: `health_controller.py`,
108
+ `health_service.py`, `health_module.py`, `health_dto.py`. The directory
109
+ layout still mirrors Nest 1:1 -- one folder per feature under
110
+ `src/modules/`.
111
+
112
+ ## Quickstart
113
+
114
+ Austial is published on PyPI -- install the CLI and scaffold a new project:
115
+
116
+ ```bash
117
+ uv tool install austial # or: pipx install austial / pip install austial
118
+ austial new my-app # scaffold a new project
119
+ cd my-app
120
+ uv sync
121
+ cp .env.example .env
122
+ uv run austial serve # http://localhost:8000, auto-reload
123
+ ```
124
+
125
+ `austial new` scaffolds a small health-check "hello world" app --
126
+ `GET /`, `GET /health`, `GET /health/protected`, `/docs` (Swagger UI) -- see
127
+ [Using the CLI](#using-the-cli) below for the full generator reference.
128
+
129
+ ## Developing Austial itself
130
+
131
+ ```bash
132
+ git clone https://github.com/austial/austial-py
133
+ cd austial-py
134
+ uv sync --all-extras
135
+ uv run pytest
136
+ ```
137
+
138
+ ## Linting, type-checking & pre-commit hooks
139
+
140
+ ```bash
141
+ uv run ruff check . # lint
142
+ uv run ruff format . # format
143
+ uv run mypy austial tests # type-check
144
+
145
+ uv run pre-commit install --hook-type pre-commit --hook-type pre-push
146
+ uv run pre-commit run --all-files # run every hook once, on demand
147
+ ```
148
+
149
+ Once installed, `git commit` runs formatting/linting/type-checks automatically,
150
+ and `git push` also runs the test suite -- mirroring a typical Nest project's
151
+ husky + lint-staged setup. Projects scaffolded with `austial new` get the same
152
+ `.pre-commit-config.yaml` out of the box.
153
+
154
+ ## Using the CLI
155
+
156
+ `austial new` scaffolds a brand-new project exactly like `nest new`:
157
+
158
+ ```bash
159
+ uv run austial new my-app
160
+ cd my-app
161
+ uv sync
162
+ cp .env.example .env
163
+ uv run austial serve
164
+ ```
165
+
166
+ > New projects depend on the published `austial` package from PyPI by
167
+ > default. If you're developing the framework itself, point new projects at
168
+ > your local checkout instead with `--link`:
169
+ > `austial new my-app --link /path/to/austial-py` -- this adds an editable
170
+ > `[tool.uv.sources]` entry so `uv sync` resolves `austial` from your local
171
+ > checkout rather than PyPI.
172
+
173
+ `austial generate` (alias `austial g`) adds artifacts to an existing project,
174
+ patching `src/app_module.py`'s imports/`imports=[...]` array automatically --
175
+ just like `nest g`:
176
+
177
+ ```bash
178
+ uv run austial generate module cats # src/modules/cats/cats_module.py
179
+ uv run austial generate controller cats # src/modules/cats/cats_controller.py
180
+ uv run austial generate service cats # src/modules/cats/cats_service.py
181
+ uv run austial generate resource cats # module + full CRUD controller/service/dto/entity
182
+ uv run austial g resource cats # same as above, short alias
183
+ ```
184
+
185
+ ## Repository layout
186
+
187
+ ```
188
+ austial-py/
189
+ ├── pyproject.toml # package "austial", console-script entry point
190
+ ├── LICENSE # MIT
191
+ ├── assets/ # logo.svg (+ logo-dark.svg for dark mode) / icon.svg / apple-icon.png
192
+ ├── austial/ # the framework (installable library) -- this *is* the package
193
+ │ ├── common/ # decorators, guards, interceptors, pipes, filters,
194
+ │ │ # middleware, exceptions, logger
195
+ │ ├── core/ # metadata/reflector, DI container, router builder,
196
+ │ │ # AustialFactory / AustialApplication
197
+ │ ├── config/ # ConfigModule / ConfigService
198
+ │ ├── database/ # DatabaseModule (SQLAlchemy async)
199
+ │ ├── terminus/ # HealthCheckService + health indicators
200
+ │ ├── testing/ # Test.create_testing_module(...)
201
+ │ └── cli/ # `austial new` / `generate` / `serve` + templates
202
+ └── tests/ # tests for the framework itself -- no sample app needed;
203
+ ├── unit/ # test modules/controllers are defined inline per test file
204
+ └── e2e/
205
+ ```
206
+
207
+ There's deliberately no `src/` here -- that's what `austial new` generates for
208
+ *your* project, kept out of the framework's own repo. Test files use Nest's
209
+ `*.spec.ts` naming convention (`*_spec.py` here); `pyproject.toml`'s
210
+ `[tool.pytest.ini_options]` is configured to discover them.
211
+
212
+ ## Request lifecycle
213
+
214
+ Every route runs through the same pipeline Nest uses:
215
+
216
+ ```
217
+ guards -> pipes -> interceptors (wrapping) -> handler -> exception filters
218
+ ```
219
+
220
+ ```python
221
+ from austial import Controller, Get, UseGuards, UseInterceptors
222
+ from austial.common.interceptors import TransformInterceptor
223
+
224
+ from .api_key_guard import ApiKeyGuard
225
+
226
+
227
+ @Controller("cats")
228
+ class CatsController:
229
+ @Get(":id")
230
+ @UseGuards(ApiKeyGuard)
231
+ @UseInterceptors(TransformInterceptor())
232
+ async def find_one(self, id: int):
233
+ ...
234
+ ```
235
+
236
+ Note path params use Nest's `:id` syntax (translated to FastAPI's `{id}`
237
+ under the hood), so route strings read exactly like their Nest counterparts.
238
+
239
+ ## License
240
+
241
+ [MIT](./LICENSE)
@@ -0,0 +1,211 @@
1
+ <p align="center">
2
+ <img src="assets/icon.svg" alt="Austial icon" width="56">
3
+ </p>
4
+
5
+ <p align="center">
6
+ <picture>
7
+ <source media="(prefers-color-scheme: dark)" srcset="assets/logo-dark.svg">
8
+ <source media="(prefers-color-scheme: light)" srcset="assets/logo.svg">
9
+ <img src="assets/logo.svg" alt="Austial" width="380">
10
+ </picture>
11
+ </p>
12
+
13
+ <p align="center">
14
+ <b>A NestJS-style, batteries-included web framework for Python, built on top of FastAPI.</b>
15
+ </p>
16
+
17
+ <p align="center">
18
+ <a href="https://pypi.org/project/austial/"><img src="https://img.shields.io/pypi/v/austial?label=pypi" alt="PyPI version"></a>
19
+ <a href="https://github.com/austial/austial-py/tags"><img src="https://img.shields.io/github/v/tag/austial/austial-py?label=tag&sort=semver" alt="Latest tag"></a>
20
+ <a href="https://github.com/austial/austial-py/commits/main"><img src="https://img.shields.io/github/last-commit/austial/austial-py" alt="Last commit"></a>
21
+ <a href="https://github.com/austial/austial-py/blob/main/LICENSE"><img src="https://img.shields.io/badge/license-MIT-green" alt="MIT License"></a>
22
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.11%2B-blue" alt="Python 3.11+"></a>
23
+ <a href="https://fastapi.tiangolo.com/"><img src="https://img.shields.io/badge/built%20with-FastAPI-009688?logo=fastapi&logoColor=white" alt="Built with FastAPI"></a>
24
+ <a href="https://github.com/astral-sh/uv"><img src="https://img.shields.io/badge/managed%20with-uv-DE5FE9" alt="Managed with uv"></a>
25
+ </p>
26
+
27
+ # Austial
28
+
29
+ Austial gives Python the exact developer experience of [NestJS](https://nestjs.com/):
30
+ decorator-driven modules/controllers/providers, real constructor-based
31
+ dependency injection, the full request lifecycle (guards, pipes,
32
+ interceptors, exception filters, middleware), a config layer, a database
33
+ layer, Terminus-style health checks, a testing module -- and a CLI so you can
34
+ scaffold new projects and artifacts the same way `nest new`/`nest generate` do.
35
+
36
+ This repository (`austial-py`) is the **framework itself** -- an installable
37
+ library, no bundled sample app. To see it in action, scaffold a new project
38
+ with the CLI (see [Quickstart](#quickstart) below); that generated project is
39
+ the "hello world" app, kept separate from the framework's own source tree.
40
+
41
+ ## Why
42
+
43
+ FastAPI is a fantastic ASGI toolkit, but it doesn't prescribe an application
44
+ architecture. Austial is opinionated on top of FastAPI the way Nest is
45
+ opinionated on top of Express/Fastify: modules own controllers and providers,
46
+ providers get dependency-injected by type, and the request pipeline
47
+ (guard → pipe → interceptor → handler → filter) is a first-class concept
48
+ instead of something you hand-roll with dependencies everywhere.
49
+
50
+ ## Architecture at a glance
51
+
52
+ | NestJS | Austial |
53
+ |---|---|
54
+ | `@Module()` | `@Module(imports=, controllers=, providers=, exports=)` |
55
+ | `@Controller()` / `@Get()` etc. | `@Controller(prefix)` / `@Get/@Post/@Put/@Patch/@Delete/@Options/@Head` |
56
+ | `@Injectable()` | `@Injectable(scope=Scope.DEFAULT \| Scope.TRANSIENT)` |
57
+ | Constructor DI | Container resolves `__init__` type hints recursively, singleton by default |
58
+ | `NestFactory.create(AppModule)` | `AustialFactory.create(AppModule)` -> `AustialApplication` |
59
+ | `app.listen(3000)` | `await app.listen(8000)` |
60
+ | `CanActivate` / `@UseGuards` | `CanActivate` ABC + `ExecutionContext`, `@UseGuards(...)` |
61
+ | `NestInterceptor` / `@UseInterceptors` | `NestInterceptor` ABC (`intercept(context, call_next)`) + `@UseInterceptors(...)` |
62
+ | `PipeTransform` / `ValidationPipe` | `PipeTransform` ABC + `ValidationPipe` |
63
+ | `ExceptionFilter` / `@Catch` | `ExceptionFilter` ABC + `@Catch(...)`, default `AllExceptionsFilter` |
64
+ | `NestMiddleware`, `MiddlewareConsumer` | `NestMiddleware` ABC, `configure(consumer)` on modules |
65
+ | `ConfigModule.forRoot()` | `ConfigModule.for_root(env_file=".env")` / `ConfigService` |
66
+ | `TypeOrmModule.forRootAsync` | `DatabaseModule.for_root_async(use_factory=, inject=)` (SQLAlchemy async) |
67
+ | `@nestjs/terminus` | `austial.terminus.HealthCheckService` + `MemoryHealthIndicator`, `DatabaseHealthIndicator` |
68
+ | `@nestjs/testing` | `austial.testing.Test.create_testing_module(...).compile()` |
69
+ | `nest new` / `nest generate` | `austial new <name>` / `austial generate\|g module\|controller\|service\|resource <name>` |
70
+
71
+ ### A note on file names
72
+
73
+ Nest names files with dots (`health.controller.ts`) because Node resolves
74
+ modules by explicit relative path strings. Python's `import a.b` instead
75
+ resolves `a` as a package and `b` as a submodule -- a literal file
76
+ `health.controller.py` isn't importable via a normal `import` statement. So
77
+ Austial uses underscore suffixes instead: `health_controller.py`,
78
+ `health_service.py`, `health_module.py`, `health_dto.py`. The directory
79
+ layout still mirrors Nest 1:1 -- one folder per feature under
80
+ `src/modules/`.
81
+
82
+ ## Quickstart
83
+
84
+ Austial is published on PyPI -- install the CLI and scaffold a new project:
85
+
86
+ ```bash
87
+ uv tool install austial # or: pipx install austial / pip install austial
88
+ austial new my-app # scaffold a new project
89
+ cd my-app
90
+ uv sync
91
+ cp .env.example .env
92
+ uv run austial serve # http://localhost:8000, auto-reload
93
+ ```
94
+
95
+ `austial new` scaffolds a small health-check "hello world" app --
96
+ `GET /`, `GET /health`, `GET /health/protected`, `/docs` (Swagger UI) -- see
97
+ [Using the CLI](#using-the-cli) below for the full generator reference.
98
+
99
+ ## Developing Austial itself
100
+
101
+ ```bash
102
+ git clone https://github.com/austial/austial-py
103
+ cd austial-py
104
+ uv sync --all-extras
105
+ uv run pytest
106
+ ```
107
+
108
+ ## Linting, type-checking & pre-commit hooks
109
+
110
+ ```bash
111
+ uv run ruff check . # lint
112
+ uv run ruff format . # format
113
+ uv run mypy austial tests # type-check
114
+
115
+ uv run pre-commit install --hook-type pre-commit --hook-type pre-push
116
+ uv run pre-commit run --all-files # run every hook once, on demand
117
+ ```
118
+
119
+ Once installed, `git commit` runs formatting/linting/type-checks automatically,
120
+ and `git push` also runs the test suite -- mirroring a typical Nest project's
121
+ husky + lint-staged setup. Projects scaffolded with `austial new` get the same
122
+ `.pre-commit-config.yaml` out of the box.
123
+
124
+ ## Using the CLI
125
+
126
+ `austial new` scaffolds a brand-new project exactly like `nest new`:
127
+
128
+ ```bash
129
+ uv run austial new my-app
130
+ cd my-app
131
+ uv sync
132
+ cp .env.example .env
133
+ uv run austial serve
134
+ ```
135
+
136
+ > New projects depend on the published `austial` package from PyPI by
137
+ > default. If you're developing the framework itself, point new projects at
138
+ > your local checkout instead with `--link`:
139
+ > `austial new my-app --link /path/to/austial-py` -- this adds an editable
140
+ > `[tool.uv.sources]` entry so `uv sync` resolves `austial` from your local
141
+ > checkout rather than PyPI.
142
+
143
+ `austial generate` (alias `austial g`) adds artifacts to an existing project,
144
+ patching `src/app_module.py`'s imports/`imports=[...]` array automatically --
145
+ just like `nest g`:
146
+
147
+ ```bash
148
+ uv run austial generate module cats # src/modules/cats/cats_module.py
149
+ uv run austial generate controller cats # src/modules/cats/cats_controller.py
150
+ uv run austial generate service cats # src/modules/cats/cats_service.py
151
+ uv run austial generate resource cats # module + full CRUD controller/service/dto/entity
152
+ uv run austial g resource cats # same as above, short alias
153
+ ```
154
+
155
+ ## Repository layout
156
+
157
+ ```
158
+ austial-py/
159
+ ├── pyproject.toml # package "austial", console-script entry point
160
+ ├── LICENSE # MIT
161
+ ├── assets/ # logo.svg (+ logo-dark.svg for dark mode) / icon.svg / apple-icon.png
162
+ ├── austial/ # the framework (installable library) -- this *is* the package
163
+ │ ├── common/ # decorators, guards, interceptors, pipes, filters,
164
+ │ │ # middleware, exceptions, logger
165
+ │ ├── core/ # metadata/reflector, DI container, router builder,
166
+ │ │ # AustialFactory / AustialApplication
167
+ │ ├── config/ # ConfigModule / ConfigService
168
+ │ ├── database/ # DatabaseModule (SQLAlchemy async)
169
+ │ ├── terminus/ # HealthCheckService + health indicators
170
+ │ ├── testing/ # Test.create_testing_module(...)
171
+ │ └── cli/ # `austial new` / `generate` / `serve` + templates
172
+ └── tests/ # tests for the framework itself -- no sample app needed;
173
+ ├── unit/ # test modules/controllers are defined inline per test file
174
+ └── e2e/
175
+ ```
176
+
177
+ There's deliberately no `src/` here -- that's what `austial new` generates for
178
+ *your* project, kept out of the framework's own repo. Test files use Nest's
179
+ `*.spec.ts` naming convention (`*_spec.py` here); `pyproject.toml`'s
180
+ `[tool.pytest.ini_options]` is configured to discover them.
181
+
182
+ ## Request lifecycle
183
+
184
+ Every route runs through the same pipeline Nest uses:
185
+
186
+ ```
187
+ guards -> pipes -> interceptors (wrapping) -> handler -> exception filters
188
+ ```
189
+
190
+ ```python
191
+ from austial import Controller, Get, UseGuards, UseInterceptors
192
+ from austial.common.interceptors import TransformInterceptor
193
+
194
+ from .api_key_guard import ApiKeyGuard
195
+
196
+
197
+ @Controller("cats")
198
+ class CatsController:
199
+ @Get(":id")
200
+ @UseGuards(ApiKeyGuard)
201
+ @UseInterceptors(TransformInterceptor())
202
+ async def find_one(self, id: int):
203
+ ...
204
+ ```
205
+
206
+ Note path params use Nest's `:id` syntax (translated to FastAPI's `{id}`
207
+ under the hood), so route strings read exactly like their Nest counterparts.
208
+
209
+ ## License
210
+
211
+ [MIT](./LICENSE)
Binary file
@@ -0,0 +1,9 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 512 512">
2
+ <!-- Round ink background keeps the mark visible on both light and dark browser chrome -->
3
+ <circle cx="256" cy="256" r="256" fill="#0a0a0a"/>
4
+ <g fill="#ffffff" transform="translate(256 256) scale(1.06) translate(-256 -214)">
5
+ <path d="M 159.25,219.52 C 149.80,240.98 140.46,262.10 131.12,283.23 C 119.40,283.23 107.97,283.23 96.00,283.23 C 113.81,243.82 131.47,204.77 149.38,165.16 C 150.13,166.45 150.74,167.31 151.17,168.26 C 155.82,178.57 160.39,188.92 165.10,199.20 C 166.20,201.60 166.49,203.66 165.23,206.15 C 163.08,210.39 161.30,214.82 159.25,219.52 z"/>
6
+ <path d="M 232.05,214.32 C 220.99,190.00 209.93,165.69 199.00,141.66 C 187.46,141.66 176.53,141.66 165.10,141.66 C 186.10,189.14 206.93,236.21 227.65,283.04 C 239.78,283.04 251.31,283.04 263.31,283.04 C 252.85,260.05 242.60,237.52 232.05,214.32 z"/>
7
+ <path d="M 310.10,180.06 C 310.10,193.37 310.08,206.19 310.10,219.01 C 310.10,224.61 310.47,230.16 312.20,235.55 C 315.39,245.45 321.73,252.24 331.71,255.44 C 339.68,257.99 347.77,258.44 355.98,256.71 C 370.25,253.69 379.97,242.86 381.31,228.33 C 381.61,225.07 381.73,221.78 381.73,218.50 C 381.76,194.34 381.73,170.18 381.72,146.02 C 381.72,144.72 381.72,143.43 381.72,141.41 C 393.07,141.41 404.16,141.38 415.65,141.44 C 415.76,142.75 415.96,143.99 415.96,145.23 C 415.98,171.20 416.00,197.17 415.96,223.14 C 415.95,233.25 414.17,243.02 409.98,252.31 C 404.60,264.22 395.85,272.67 384.09,278.17 C 367.32,286.01 349.73,287.21 331.63,284.97 C 320.37,283.58 309.91,280.04 300.28,274.06 C 287.79,266.30 281.10,254.50 277.88,240.61 C 276.59,235.06 275.90,229.25 275.85,223.55 C 275.65,197.09 275.77,170.63 275.78,144.17 C 275.78,143.38 275.88,142.60 275.94,141.62 C 287.33,141.62 298.43,141.62 310.10,141.62 C 310.10,154.35 310.10,166.96 310.10,180.06 z"/>
8
+ </g>
9
+ </svg>