bustan 1.0.0__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.
- bustan-1.0.0/LICENSE +21 -0
- bustan-1.0.0/PKG-INFO +302 -0
- bustan-1.0.0/README.md +274 -0
- bustan-1.0.0/pyproject.toml +69 -0
- bustan-1.0.0/src/bustan/__init__.py +41 -0
- bustan-1.0.0/src/bustan/_version.py +43 -0
- bustan-1.0.0/src/bustan/application.py +29 -0
- bustan-1.0.0/src/bustan/cli.py +311 -0
- bustan-1.0.0/src/bustan/container.py +552 -0
- bustan-1.0.0/src/bustan/decorators.py +258 -0
- bustan-1.0.0/src/bustan/errors.py +64 -0
- bustan-1.0.0/src/bustan/lifecycle.py +106 -0
- bustan-1.0.0/src/bustan/metadata.py +304 -0
- bustan-1.0.0/src/bustan/module_graph.py +282 -0
- bustan-1.0.0/src/bustan/params.py +526 -0
- bustan-1.0.0/src/bustan/pipeline/__init__.py +13 -0
- bustan-1.0.0/src/bustan/pipeline/context.py +46 -0
- bustan-1.0.0/src/bustan/pipeline/filters.py +41 -0
- bustan-1.0.0/src/bustan/pipeline/guards.py +35 -0
- bustan-1.0.0/src/bustan/pipeline/interceptors.py +41 -0
- bustan-1.0.0/src/bustan/pipeline/pipes.py +34 -0
- bustan-1.0.0/src/bustan/py.typed +0 -0
- bustan-1.0.0/src/bustan/responses.py +25 -0
- bustan-1.0.0/src/bustan/routing.py +326 -0
- bustan-1.0.0/src/bustan/testing/__init__.py +10 -0
- bustan-1.0.0/src/bustan/testing/builder.py +48 -0
- bustan-1.0.0/src/bustan/testing/overrides.py +50 -0
bustan-1.0.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Moses Gameli <https://mosesgameli.com>
|
|
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.
|
bustan-1.0.0/PKG-INFO
ADDED
|
@@ -0,0 +1,302 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: bustan
|
|
3
|
+
Version: 1.0.0
|
|
4
|
+
Summary: NestJS-inspired application architecture on top of Starlette
|
|
5
|
+
Keywords: asgi,dependency injection,starlette,web framework
|
|
6
|
+
Author: mosesgameli
|
|
7
|
+
Author-email: mosesgameli <mosesgameli20@gmail.com>
|
|
8
|
+
License-Expression: MIT
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Intended Audience :: Developers
|
|
12
|
+
Classifier: Operating System :: OS Independent
|
|
13
|
+
Classifier: Programming Language :: Python :: 3
|
|
14
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Topic :: Internet :: WWW/HTTP
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
18
|
+
Requires-Dist: anyio>=4.9.0,<5.0.0
|
|
19
|
+
Requires-Dist: dependency-injector>=4.46.0,<5.0.0
|
|
20
|
+
Requires-Dist: starlette>=1.0.0,<2.0.0
|
|
21
|
+
Requires-Python: >=3.13
|
|
22
|
+
Project-URL: Repository, https://github.com/bustanhq/bustan
|
|
23
|
+
Project-URL: Issues, https://github.com/bustanhq/bustan/issues
|
|
24
|
+
Project-URL: Documentation, https://bustan.dev
|
|
25
|
+
Project-URL: Changelog, https://github.com/bustanhq/bustan/blob/main/CHANGELOG.md
|
|
26
|
+
Project-URL: Support, https://github.com/bustanhq/bustan/issues
|
|
27
|
+
Description-Content-Type: text/markdown
|
|
28
|
+
|
|
29
|
+
# bustan
|
|
30
|
+
|
|
31
|
+
bustan is an architecture-first Python web framework for teams that like Starlette's runtime but want stronger application structure.
|
|
32
|
+
|
|
33
|
+
It brings NestJS-style modules, controllers, providers, constructor injection, lifecycle hooks, and a request pipeline to Python, while keeping direct access to Starlette when you need an escape hatch.
|
|
34
|
+
|
|
35
|
+
## Why bustan
|
|
36
|
+
|
|
37
|
+
- Use modules as real composition boundaries instead of ad hoc import graphs.
|
|
38
|
+
- Keep controllers thin and move business logic into DI-managed providers.
|
|
39
|
+
- Apply guards, pipes, interceptors, and exception filters in a predictable order.
|
|
40
|
+
- Build on top of Starlette instead of replacing the ASGI layer with a closed runtime.
|
|
41
|
+
- Test applications with focused module builders and provider overrides.
|
|
42
|
+
|
|
43
|
+
## Status
|
|
44
|
+
|
|
45
|
+
- `bustan` is alpha and still pre-`0.1.0`.
|
|
46
|
+
- The first public compatibility target is `0.1.0` alpha.
|
|
47
|
+
- Package metadata currently targets Python `>=3.13`.
|
|
48
|
+
- The Python floor is intentionally narrow while the public surface, packaging, and release process are still settling.
|
|
49
|
+
- Python `3.13` remains the floor because the project is still tightening its first public contract and release automation around one supported runtime before widening support.
|
|
50
|
+
- Compatibility is currently promised only for `bustan`, `bustan.errors`, and `bustan.testing`.
|
|
51
|
+
- Internal modules such as `bustan.container`, `bustan.routing`, `bustan.params`, and `bustan.metadata` are still implementation details.
|
|
52
|
+
- Alpha stability means behavior may still change between pre-`0.1.0` releases, but the project is already treating `bustan`, `bustan.errors`, and `bustan.testing` as the intended long-term public surface.
|
|
53
|
+
- No benchmark suite or benchmark claims are published yet.
|
|
54
|
+
|
|
55
|
+
## Installation
|
|
56
|
+
|
|
57
|
+
### Use from source today
|
|
58
|
+
|
|
59
|
+
This repository is ready to use directly in a local development environment:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
uv sync --group dev
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
That installs the framework, test dependencies, linting, typing, and the local CLI entry point.
|
|
66
|
+
|
|
67
|
+
### Use the CLI from a source checkout
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
uv run bustan new my-app
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
`create` is available as an alias:
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
uv run bustan create my-app
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
### Use as a published package
|
|
80
|
+
|
|
81
|
+
Once the PyPI distribution name is confirmed and published, the intended install path is:
|
|
82
|
+
|
|
83
|
+
```bash
|
|
84
|
+
uv add bustan
|
|
85
|
+
# or
|
|
86
|
+
pip install bustan
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
Once published, the CLI entry point will also be usable through `uvx`:
|
|
90
|
+
|
|
91
|
+
```bash
|
|
92
|
+
uvx bustan create my-app
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
The distribution name `bustan` still needs to be confirmed at publish time. The current repository is prepared for that name, but the final PyPI availability check should happen immediately before the first release.
|
|
96
|
+
|
|
97
|
+
## Five-Minute Quickstart
|
|
98
|
+
|
|
99
|
+
Create an application module, one provider, and one controller:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
from bustan import controller, create_app, get, injectable, module
|
|
103
|
+
|
|
104
|
+
|
|
105
|
+
@Injectable
|
|
106
|
+
class GreetingService:
|
|
107
|
+
def greet(self) -> dict[str, str]:
|
|
108
|
+
return {"message": "hello from bustan"}
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
@Controller("/hello")
|
|
112
|
+
class GreetingController:
|
|
113
|
+
def __init__(self, greeting_service: GreetingService) -> None:
|
|
114
|
+
self.greeting_service = greeting_service
|
|
115
|
+
|
|
116
|
+
@Get("/")
|
|
117
|
+
def read_greeting(self) -> dict[str, str]:
|
|
118
|
+
return self.greeting_service.greet()
|
|
119
|
+
|
|
120
|
+
|
|
121
|
+
@Module(
|
|
122
|
+
controllers=[GreetingController],
|
|
123
|
+
providers=[GreetingService],
|
|
124
|
+
exports=[GreetingService],
|
|
125
|
+
)
|
|
126
|
+
class AppModule:
|
|
127
|
+
pass
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
app = create_app(AppModule)
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
Run it locally:
|
|
134
|
+
|
|
135
|
+
```bash
|
|
136
|
+
uv run uvicorn app:app --reload
|
|
137
|
+
```
|
|
138
|
+
|
|
139
|
+
Call the route:
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
curl http://127.0.0.1:8000/hello
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
Expected response:
|
|
146
|
+
|
|
147
|
+
```json
|
|
148
|
+
{"message":"hello from bustan"}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
## What You Get Today
|
|
152
|
+
|
|
153
|
+
The current implementation already includes:
|
|
154
|
+
|
|
155
|
+
- module discovery and validation
|
|
156
|
+
- export-based provider visibility across modules
|
|
157
|
+
- constructor injection for providers and controllers
|
|
158
|
+
- controller route compilation into Starlette
|
|
159
|
+
- response coercion for `Response`, `dict`, `list`, dataclass instances, and `None`
|
|
160
|
+
- request binding for `Request`, path params, query params, and JSON body input
|
|
161
|
+
- request-scoped providers with per-request caching
|
|
162
|
+
- guards, pipes, interceptors, and exception filters
|
|
163
|
+
- module and application lifecycle hooks wired through Starlette lifespan
|
|
164
|
+
- test helpers for temporary modules, test apps, and provider overrides
|
|
165
|
+
- an initial CLI scaffold for new applications
|
|
166
|
+
|
|
167
|
+
## Supported Public API
|
|
168
|
+
|
|
169
|
+
The first compatibility boundary is intentionally small.
|
|
170
|
+
|
|
171
|
+
Stable import paths:
|
|
172
|
+
|
|
173
|
+
- `bustan`
|
|
174
|
+
- `bustan.errors`
|
|
175
|
+
- `bustan.testing`
|
|
176
|
+
|
|
177
|
+
Example supported imports:
|
|
178
|
+
|
|
179
|
+
```python
|
|
180
|
+
from bustan import __version__, controller, create_app, get, injectable, module
|
|
181
|
+
from bustan.errors import ProviderResolutionError
|
|
182
|
+
from bustan.testing import create_test_app, override_provider
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
The generated API reference for the stable surface lives in [docs/API_REFERENCE.md](docs/API_REFERENCE.md).
|
|
186
|
+
|
|
187
|
+
## Guides
|
|
188
|
+
|
|
189
|
+
- [docs/README.md](docs/README.md)
|
|
190
|
+
- [docs/FIRST_APP.md](docs/FIRST_APP.md)
|
|
191
|
+
- [docs/ROUTING.md](docs/ROUTING.md)
|
|
192
|
+
- [docs/REQUEST_PIPELINE.md](docs/REQUEST_PIPELINE.md)
|
|
193
|
+
- [docs/REQUEST_SCOPED_PROVIDERS.md](docs/REQUEST_SCOPED_PROVIDERS.md)
|
|
194
|
+
- [docs/LIFECYCLE.md](docs/LIFECYCLE.md)
|
|
195
|
+
- [docs/STABILITY.md](docs/STABILITY.md)
|
|
196
|
+
- [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)
|
|
197
|
+
- [docs/COMPARISONS.md](docs/COMPARISONS.md)
|
|
198
|
+
- [docs/ESCAPE_HATCHES.md](docs/ESCAPE_HATCHES.md)
|
|
199
|
+
- [docs/VERSIONING.md](docs/VERSIONING.md)
|
|
200
|
+
|
|
201
|
+
## Open Source Project Docs
|
|
202
|
+
|
|
203
|
+
- [docs/README.md](docs/README.md)
|
|
204
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
205
|
+
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
|
206
|
+
- [SECURITY.md](SECURITY.md)
|
|
207
|
+
- [GOVERNANCE.md](GOVERNANCE.md)
|
|
208
|
+
- [CHANGELOG.md](CHANGELOG.md)
|
|
209
|
+
- [docs/RELEASE_CHECKLIST.md](docs/RELEASE_CHECKLIST.md)
|
|
210
|
+
|
|
211
|
+
## Examples
|
|
212
|
+
|
|
213
|
+
The repository includes focused examples beyond the minimal quickstart:
|
|
214
|
+
|
|
215
|
+
- `examples/blog_api/app.py`: a small reference-style blog API with request context and module exports
|
|
216
|
+
- `examples/multi_module_app/app.py`: feature modules with exported providers
|
|
217
|
+
- `examples/graph_inspection/app.py`: print the discovered module graph
|
|
218
|
+
- `examples/request_scope_pipeline_app/app.py`: request-scoped providers shared across guards, interceptors, and controllers
|
|
219
|
+
- `examples/testing_overrides/app.py`: test-time provider overrides with `bustan.testing`
|
|
220
|
+
|
|
221
|
+
Run them with:
|
|
222
|
+
|
|
223
|
+
```bash
|
|
224
|
+
uv run python examples/blog_api/app.py
|
|
225
|
+
uv run python examples/graph_inspection/app.py
|
|
226
|
+
uv run python examples/request_scope_pipeline_app/app.py
|
|
227
|
+
uv run python examples/testing_overrides/app.py
|
|
228
|
+
```
|
|
229
|
+
|
|
230
|
+
## Testing Utilities
|
|
231
|
+
|
|
232
|
+
`bustan.testing` is the intended entry point for test-time application assembly.
|
|
233
|
+
|
|
234
|
+
Use `create_test_app()` to start an app with one or more providers replaced:
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
from bustan.testing import create_test_app
|
|
238
|
+
|
|
239
|
+
|
|
240
|
+
application = create_test_app(
|
|
241
|
+
AppModule,
|
|
242
|
+
provider_overrides={GreetingService: FakeGreetingService()},
|
|
243
|
+
)
|
|
244
|
+
```
|
|
245
|
+
|
|
246
|
+
Use `override_provider()` when you want a scoped override that is restored automatically:
|
|
247
|
+
|
|
248
|
+
```python
|
|
249
|
+
from bustan.testing import override_provider
|
|
250
|
+
|
|
251
|
+
|
|
252
|
+
with override_provider(application, GreetingService, FakeGreetingService()):
|
|
253
|
+
...
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
Use `create_test_module()` when you want a temporary module class for an isolated test instead of declaring one manually.
|
|
257
|
+
|
|
258
|
+
## Support
|
|
259
|
+
|
|
260
|
+
Use GitHub Issues for questions, bug reports, feature requests, and adoption feedback:
|
|
261
|
+
|
|
262
|
+
- https://github.com/bustanhq/bustan/issues
|
|
263
|
+
|
|
264
|
+
Do not use public issues for sensitive security reports. Follow the private disclosure guidance in [SECURITY.md](SECURITY.md).
|
|
265
|
+
|
|
266
|
+
## Roadmap
|
|
267
|
+
|
|
268
|
+
Near-term priorities for the first public adoption push:
|
|
269
|
+
|
|
270
|
+
- publish and verify the first PyPI release end to end
|
|
271
|
+
- widen runtime support beyond Python `3.13` once release automation is routine
|
|
272
|
+
- collect external adopter feedback before calling any release stable
|
|
273
|
+
- publish a fuller reference app or companion tutorial repository
|
|
274
|
+
- move to a dedicated docs site if the docs set outgrow README-driven discovery
|
|
275
|
+
|
|
276
|
+
## Development
|
|
277
|
+
|
|
278
|
+
Install hooks once after cloning if you want local pre-commit and pre-push checks:
|
|
279
|
+
|
|
280
|
+
```bash
|
|
281
|
+
uv run lefthook install
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
For full contributor expectations, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
285
|
+
|
|
286
|
+
Run the main checks with:
|
|
287
|
+
|
|
288
|
+
```bash
|
|
289
|
+
uv run python scripts/generate_api_reference.py --check
|
|
290
|
+
uv run python scripts/check_markdown_links.py
|
|
291
|
+
uv run ruff check .
|
|
292
|
+
uv run ty check src tests examples scripts
|
|
293
|
+
uv run pytest
|
|
294
|
+
uv run pytest --cov=bustan --cov-report=term-missing --cov-report=xml
|
|
295
|
+
```
|
|
296
|
+
|
|
297
|
+
## Project Direction
|
|
298
|
+
|
|
299
|
+
`bustan` is trying to be opinionated about application structure, not to hide Starlette or compete on benchmark claims.
|
|
300
|
+
|
|
301
|
+
If you want a small ASGI core with explicit module boundaries, DI-managed services, and a predictable request pipeline, that is the target use case.
|
|
302
|
+
|
bustan-1.0.0/README.md
ADDED
|
@@ -0,0 +1,274 @@
|
|
|
1
|
+
# bustan
|
|
2
|
+
|
|
3
|
+
bustan is an architecture-first Python web framework for teams that like Starlette's runtime but want stronger application structure.
|
|
4
|
+
|
|
5
|
+
It brings NestJS-style modules, controllers, providers, constructor injection, lifecycle hooks, and a request pipeline to Python, while keeping direct access to Starlette when you need an escape hatch.
|
|
6
|
+
|
|
7
|
+
## Why bustan
|
|
8
|
+
|
|
9
|
+
- Use modules as real composition boundaries instead of ad hoc import graphs.
|
|
10
|
+
- Keep controllers thin and move business logic into DI-managed providers.
|
|
11
|
+
- Apply guards, pipes, interceptors, and exception filters in a predictable order.
|
|
12
|
+
- Build on top of Starlette instead of replacing the ASGI layer with a closed runtime.
|
|
13
|
+
- Test applications with focused module builders and provider overrides.
|
|
14
|
+
|
|
15
|
+
## Status
|
|
16
|
+
|
|
17
|
+
- `bustan` is alpha and still pre-`0.1.0`.
|
|
18
|
+
- The first public compatibility target is `0.1.0` alpha.
|
|
19
|
+
- Package metadata currently targets Python `>=3.13`.
|
|
20
|
+
- The Python floor is intentionally narrow while the public surface, packaging, and release process are still settling.
|
|
21
|
+
- Python `3.13` remains the floor because the project is still tightening its first public contract and release automation around one supported runtime before widening support.
|
|
22
|
+
- Compatibility is currently promised only for `bustan`, `bustan.errors`, and `bustan.testing`.
|
|
23
|
+
- Internal modules such as `bustan.container`, `bustan.routing`, `bustan.params`, and `bustan.metadata` are still implementation details.
|
|
24
|
+
- Alpha stability means behavior may still change between pre-`0.1.0` releases, but the project is already treating `bustan`, `bustan.errors`, and `bustan.testing` as the intended long-term public surface.
|
|
25
|
+
- No benchmark suite or benchmark claims are published yet.
|
|
26
|
+
|
|
27
|
+
## Installation
|
|
28
|
+
|
|
29
|
+
### Use from source today
|
|
30
|
+
|
|
31
|
+
This repository is ready to use directly in a local development environment:
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
uv sync --group dev
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
That installs the framework, test dependencies, linting, typing, and the local CLI entry point.
|
|
38
|
+
|
|
39
|
+
### Use the CLI from a source checkout
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
uv run bustan new my-app
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
`create` is available as an alias:
|
|
46
|
+
|
|
47
|
+
```bash
|
|
48
|
+
uv run bustan create my-app
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
### Use as a published package
|
|
52
|
+
|
|
53
|
+
Once the PyPI distribution name is confirmed and published, the intended install path is:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
uv add bustan
|
|
57
|
+
# or
|
|
58
|
+
pip install bustan
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Once published, the CLI entry point will also be usable through `uvx`:
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
uvx bustan create my-app
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The distribution name `bustan` still needs to be confirmed at publish time. The current repository is prepared for that name, but the final PyPI availability check should happen immediately before the first release.
|
|
68
|
+
|
|
69
|
+
## Five-Minute Quickstart
|
|
70
|
+
|
|
71
|
+
Create an application module, one provider, and one controller:
|
|
72
|
+
|
|
73
|
+
```python
|
|
74
|
+
from bustan import controller, create_app, get, injectable, module
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@Injectable
|
|
78
|
+
class GreetingService:
|
|
79
|
+
def greet(self) -> dict[str, str]:
|
|
80
|
+
return {"message": "hello from bustan"}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
@Controller("/hello")
|
|
84
|
+
class GreetingController:
|
|
85
|
+
def __init__(self, greeting_service: GreetingService) -> None:
|
|
86
|
+
self.greeting_service = greeting_service
|
|
87
|
+
|
|
88
|
+
@Get("/")
|
|
89
|
+
def read_greeting(self) -> dict[str, str]:
|
|
90
|
+
return self.greeting_service.greet()
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
@Module(
|
|
94
|
+
controllers=[GreetingController],
|
|
95
|
+
providers=[GreetingService],
|
|
96
|
+
exports=[GreetingService],
|
|
97
|
+
)
|
|
98
|
+
class AppModule:
|
|
99
|
+
pass
|
|
100
|
+
|
|
101
|
+
|
|
102
|
+
app = create_app(AppModule)
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Run it locally:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
uv run uvicorn app:app --reload
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
Call the route:
|
|
112
|
+
|
|
113
|
+
```bash
|
|
114
|
+
curl http://127.0.0.1:8000/hello
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
Expected response:
|
|
118
|
+
|
|
119
|
+
```json
|
|
120
|
+
{"message":"hello from bustan"}
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## What You Get Today
|
|
124
|
+
|
|
125
|
+
The current implementation already includes:
|
|
126
|
+
|
|
127
|
+
- module discovery and validation
|
|
128
|
+
- export-based provider visibility across modules
|
|
129
|
+
- constructor injection for providers and controllers
|
|
130
|
+
- controller route compilation into Starlette
|
|
131
|
+
- response coercion for `Response`, `dict`, `list`, dataclass instances, and `None`
|
|
132
|
+
- request binding for `Request`, path params, query params, and JSON body input
|
|
133
|
+
- request-scoped providers with per-request caching
|
|
134
|
+
- guards, pipes, interceptors, and exception filters
|
|
135
|
+
- module and application lifecycle hooks wired through Starlette lifespan
|
|
136
|
+
- test helpers for temporary modules, test apps, and provider overrides
|
|
137
|
+
- an initial CLI scaffold for new applications
|
|
138
|
+
|
|
139
|
+
## Supported Public API
|
|
140
|
+
|
|
141
|
+
The first compatibility boundary is intentionally small.
|
|
142
|
+
|
|
143
|
+
Stable import paths:
|
|
144
|
+
|
|
145
|
+
- `bustan`
|
|
146
|
+
- `bustan.errors`
|
|
147
|
+
- `bustan.testing`
|
|
148
|
+
|
|
149
|
+
Example supported imports:
|
|
150
|
+
|
|
151
|
+
```python
|
|
152
|
+
from bustan import __version__, controller, create_app, get, injectable, module
|
|
153
|
+
from bustan.errors import ProviderResolutionError
|
|
154
|
+
from bustan.testing import create_test_app, override_provider
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
The generated API reference for the stable surface lives in [docs/API_REFERENCE.md](docs/API_REFERENCE.md).
|
|
158
|
+
|
|
159
|
+
## Guides
|
|
160
|
+
|
|
161
|
+
- [docs/README.md](docs/README.md)
|
|
162
|
+
- [docs/FIRST_APP.md](docs/FIRST_APP.md)
|
|
163
|
+
- [docs/ROUTING.md](docs/ROUTING.md)
|
|
164
|
+
- [docs/REQUEST_PIPELINE.md](docs/REQUEST_PIPELINE.md)
|
|
165
|
+
- [docs/REQUEST_SCOPED_PROVIDERS.md](docs/REQUEST_SCOPED_PROVIDERS.md)
|
|
166
|
+
- [docs/LIFECYCLE.md](docs/LIFECYCLE.md)
|
|
167
|
+
- [docs/STABILITY.md](docs/STABILITY.md)
|
|
168
|
+
- [docs/TROUBLESHOOTING.md](docs/TROUBLESHOOTING.md)
|
|
169
|
+
- [docs/COMPARISONS.md](docs/COMPARISONS.md)
|
|
170
|
+
- [docs/ESCAPE_HATCHES.md](docs/ESCAPE_HATCHES.md)
|
|
171
|
+
- [docs/VERSIONING.md](docs/VERSIONING.md)
|
|
172
|
+
|
|
173
|
+
## Open Source Project Docs
|
|
174
|
+
|
|
175
|
+
- [docs/README.md](docs/README.md)
|
|
176
|
+
- [CONTRIBUTING.md](CONTRIBUTING.md)
|
|
177
|
+
- [CODE_OF_CONDUCT.md](CODE_OF_CONDUCT.md)
|
|
178
|
+
- [SECURITY.md](SECURITY.md)
|
|
179
|
+
- [GOVERNANCE.md](GOVERNANCE.md)
|
|
180
|
+
- [CHANGELOG.md](CHANGELOG.md)
|
|
181
|
+
- [docs/RELEASE_CHECKLIST.md](docs/RELEASE_CHECKLIST.md)
|
|
182
|
+
|
|
183
|
+
## Examples
|
|
184
|
+
|
|
185
|
+
The repository includes focused examples beyond the minimal quickstart:
|
|
186
|
+
|
|
187
|
+
- `examples/blog_api/app.py`: a small reference-style blog API with request context and module exports
|
|
188
|
+
- `examples/multi_module_app/app.py`: feature modules with exported providers
|
|
189
|
+
- `examples/graph_inspection/app.py`: print the discovered module graph
|
|
190
|
+
- `examples/request_scope_pipeline_app/app.py`: request-scoped providers shared across guards, interceptors, and controllers
|
|
191
|
+
- `examples/testing_overrides/app.py`: test-time provider overrides with `bustan.testing`
|
|
192
|
+
|
|
193
|
+
Run them with:
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
uv run python examples/blog_api/app.py
|
|
197
|
+
uv run python examples/graph_inspection/app.py
|
|
198
|
+
uv run python examples/request_scope_pipeline_app/app.py
|
|
199
|
+
uv run python examples/testing_overrides/app.py
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
## Testing Utilities
|
|
203
|
+
|
|
204
|
+
`bustan.testing` is the intended entry point for test-time application assembly.
|
|
205
|
+
|
|
206
|
+
Use `create_test_app()` to start an app with one or more providers replaced:
|
|
207
|
+
|
|
208
|
+
```python
|
|
209
|
+
from bustan.testing import create_test_app
|
|
210
|
+
|
|
211
|
+
|
|
212
|
+
application = create_test_app(
|
|
213
|
+
AppModule,
|
|
214
|
+
provider_overrides={GreetingService: FakeGreetingService()},
|
|
215
|
+
)
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
Use `override_provider()` when you want a scoped override that is restored automatically:
|
|
219
|
+
|
|
220
|
+
```python
|
|
221
|
+
from bustan.testing import override_provider
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
with override_provider(application, GreetingService, FakeGreetingService()):
|
|
225
|
+
...
|
|
226
|
+
```
|
|
227
|
+
|
|
228
|
+
Use `create_test_module()` when you want a temporary module class for an isolated test instead of declaring one manually.
|
|
229
|
+
|
|
230
|
+
## Support
|
|
231
|
+
|
|
232
|
+
Use GitHub Issues for questions, bug reports, feature requests, and adoption feedback:
|
|
233
|
+
|
|
234
|
+
- https://github.com/bustanhq/bustan/issues
|
|
235
|
+
|
|
236
|
+
Do not use public issues for sensitive security reports. Follow the private disclosure guidance in [SECURITY.md](SECURITY.md).
|
|
237
|
+
|
|
238
|
+
## Roadmap
|
|
239
|
+
|
|
240
|
+
Near-term priorities for the first public adoption push:
|
|
241
|
+
|
|
242
|
+
- publish and verify the first PyPI release end to end
|
|
243
|
+
- widen runtime support beyond Python `3.13` once release automation is routine
|
|
244
|
+
- collect external adopter feedback before calling any release stable
|
|
245
|
+
- publish a fuller reference app or companion tutorial repository
|
|
246
|
+
- move to a dedicated docs site if the docs set outgrow README-driven discovery
|
|
247
|
+
|
|
248
|
+
## Development
|
|
249
|
+
|
|
250
|
+
Install hooks once after cloning if you want local pre-commit and pre-push checks:
|
|
251
|
+
|
|
252
|
+
```bash
|
|
253
|
+
uv run lefthook install
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
For full contributor expectations, see [CONTRIBUTING.md](CONTRIBUTING.md).
|
|
257
|
+
|
|
258
|
+
Run the main checks with:
|
|
259
|
+
|
|
260
|
+
```bash
|
|
261
|
+
uv run python scripts/generate_api_reference.py --check
|
|
262
|
+
uv run python scripts/check_markdown_links.py
|
|
263
|
+
uv run ruff check .
|
|
264
|
+
uv run ty check src tests examples scripts
|
|
265
|
+
uv run pytest
|
|
266
|
+
uv run pytest --cov=bustan --cov-report=term-missing --cov-report=xml
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
## Project Direction
|
|
270
|
+
|
|
271
|
+
`bustan` is trying to be opinionated about application structure, not to hide Starlette or compete on benchmark claims.
|
|
272
|
+
|
|
273
|
+
If you want a small ASGI core with explicit module boundaries, DI-managed services, and a predictable request pipeline, that is the target use case.
|
|
274
|
+
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
[project]
|
|
2
|
+
name = "bustan"
|
|
3
|
+
version = "1.0.0"
|
|
4
|
+
description = "NestJS-inspired application architecture on top of Starlette"
|
|
5
|
+
keywords = ["asgi", "dependency injection", "starlette", "web framework"]
|
|
6
|
+
classifiers = [
|
|
7
|
+
"Development Status :: 3 - Alpha",
|
|
8
|
+
"Intended Audience :: Developers",
|
|
9
|
+
"Operating System :: OS Independent",
|
|
10
|
+
"Programming Language :: Python :: 3",
|
|
11
|
+
"Programming Language :: Python :: 3 :: Only",
|
|
12
|
+
"Programming Language :: Python :: 3.13",
|
|
13
|
+
"Topic :: Internet :: WWW/HTTP",
|
|
14
|
+
"Topic :: Software Development :: Libraries :: Application Frameworks",
|
|
15
|
+
]
|
|
16
|
+
readme = "README.md"
|
|
17
|
+
license = "MIT"
|
|
18
|
+
license-files = ["LICENSE"]
|
|
19
|
+
authors = [
|
|
20
|
+
{ name = "mosesgameli", email = "mosesgameli20@gmail.com" }
|
|
21
|
+
]
|
|
22
|
+
requires-python = ">=3.13"
|
|
23
|
+
dependencies = [
|
|
24
|
+
"anyio>=4.9.0,<5.0.0",
|
|
25
|
+
"dependency-injector>=4.46.0,<5.0.0",
|
|
26
|
+
"starlette>=1.0.0,<2.0.0",
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
[project.scripts]
|
|
30
|
+
bustan = "bustan.cli:main"
|
|
31
|
+
|
|
32
|
+
[project.urls]
|
|
33
|
+
Repository = "https://github.com/bustanhq/bustan"
|
|
34
|
+
Issues = "https://github.com/bustanhq/bustan/issues"
|
|
35
|
+
Documentation = "https://bustan.dev"
|
|
36
|
+
Changelog = "https://github.com/bustanhq/bustan/blob/main/CHANGELOG.md"
|
|
37
|
+
Support = "https://github.com/bustanhq/bustan/issues"
|
|
38
|
+
|
|
39
|
+
[dependency-groups]
|
|
40
|
+
dev = [
|
|
41
|
+
"httpx>=0.28.0,<1.0.0",
|
|
42
|
+
"lefthook>=2.1.4",
|
|
43
|
+
"pytest>=9.0.2,<10.0.0",
|
|
44
|
+
"pytest-cov>=7.1.0,<8.0.0",
|
|
45
|
+
"ruff>=0.15.9,<0.16.0",
|
|
46
|
+
"ty",
|
|
47
|
+
"uvicorn>=0.34.0,<1.0.0",
|
|
48
|
+
]
|
|
49
|
+
|
|
50
|
+
[tool.pytest.ini_options]
|
|
51
|
+
addopts = "-ra"
|
|
52
|
+
testpaths = ["tests"]
|
|
53
|
+
|
|
54
|
+
[tool.coverage.run]
|
|
55
|
+
branch = true
|
|
56
|
+
source = ["bustan"]
|
|
57
|
+
|
|
58
|
+
[tool.coverage.report]
|
|
59
|
+
fail_under = 84
|
|
60
|
+
show_missing = true
|
|
61
|
+
skip_covered = false
|
|
62
|
+
|
|
63
|
+
[tool.ruff]
|
|
64
|
+
line-length = 100
|
|
65
|
+
target-version = "py313"
|
|
66
|
+
|
|
67
|
+
[build-system]
|
|
68
|
+
requires = ["uv_build>=0.11.1,<0.12.0"]
|
|
69
|
+
build-backend = "uv_build"
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""Supported public API for the bustan package."""
|
|
2
|
+
|
|
3
|
+
from ._version import __version__
|
|
4
|
+
from .application import bootstrap, create_app
|
|
5
|
+
from .decorators import (
|
|
6
|
+
Controller,
|
|
7
|
+
Delete,
|
|
8
|
+
Get,
|
|
9
|
+
Injectable,
|
|
10
|
+
Module,
|
|
11
|
+
Patch,
|
|
12
|
+
Post,
|
|
13
|
+
Put,
|
|
14
|
+
UseFilters,
|
|
15
|
+
UseGuards,
|
|
16
|
+
UseInterceptors,
|
|
17
|
+
UsePipes,
|
|
18
|
+
)
|
|
19
|
+
from .pipeline import ExceptionFilter, Guard, Interceptor, Pipe
|
|
20
|
+
|
|
21
|
+
__all__ = (
|
|
22
|
+
"__version__",
|
|
23
|
+
"ExceptionFilter",
|
|
24
|
+
"Guard",
|
|
25
|
+
"Interceptor",
|
|
26
|
+
"Pipe",
|
|
27
|
+
"bootstrap",
|
|
28
|
+
"Controller",
|
|
29
|
+
"create_app",
|
|
30
|
+
"Delete",
|
|
31
|
+
"Get",
|
|
32
|
+
"Injectable",
|
|
33
|
+
"Module",
|
|
34
|
+
"Patch",
|
|
35
|
+
"Post",
|
|
36
|
+
"Put",
|
|
37
|
+
"UseFilters",
|
|
38
|
+
"UseGuards",
|
|
39
|
+
"UseInterceptors",
|
|
40
|
+
"UsePipes",
|
|
41
|
+
)
|