pico-fastapi 0.1.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.
- pico_fastapi-0.1.0/.coveragerc +17 -0
- pico_fastapi-0.1.0/.github/workflows/ci.yml +102 -0
- pico_fastapi-0.1.0/.github/workflows/publish-to-pypi.yml +36 -0
- pico_fastapi-0.1.0/LICENSE +21 -0
- pico_fastapi-0.1.0/MANIFEST.in +24 -0
- pico_fastapi-0.1.0/PKG-INFO +214 -0
- pico_fastapi-0.1.0/README.md +162 -0
- pico_fastapi-0.1.0/docs/architecture.md +212 -0
- pico_fastapi-0.1.0/pyproject.toml +61 -0
- pico_fastapi-0.1.0/setup.cfg +4 -0
- pico_fastapi-0.1.0/src/pico_fastapi/__init__.py +21 -0
- pico_fastapi-0.1.0/src/pico_fastapi/_version.py +1 -0
- pico_fastapi-0.1.0/src/pico_fastapi/config.py +19 -0
- pico_fastapi-0.1.0/src/pico_fastapi/decorators.py +28 -0
- pico_fastapi-0.1.0/src/pico_fastapi/exceptions.py +11 -0
- pico_fastapi-0.1.0/src/pico_fastapi/factory.py +126 -0
- pico_fastapi-0.1.0/src/pico_fastapi/middleware.py +31 -0
- pico_fastapi-0.1.0/src/pico_fastapi.egg-info/PKG-INFO +214 -0
- pico_fastapi-0.1.0/src/pico_fastapi.egg-info/SOURCES.txt +27 -0
- pico_fastapi-0.1.0/src/pico_fastapi.egg-info/dependency_links.txt +1 -0
- pico_fastapi-0.1.0/src/pico_fastapi.egg-info/requires.txt +8 -0
- pico_fastapi-0.1.0/src/pico_fastapi.egg-info/top_level.txt +1 -0
- pico_fastapi-0.1.0/tests/__init__.py +0 -0
- pico_fastapi-0.1.0/tests/conftest.py +159 -0
- pico_fastapi-0.1.0/tests/test_http_admin.py +18 -0
- pico_fastapi-0.1.0/tests/test_session_cart.py +19 -0
- pico_fastapi-0.1.0/tests/test_settings_applied.py +6 -0
- pico_fastapi-0.1.0/tests/test_websocket_chat.py +11 -0
- pico_fastapi-0.1.0/tox.ini +33 -0
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
[run]
|
|
2
|
+
branch = True
|
|
3
|
+
source =
|
|
4
|
+
pico_fastapi
|
|
5
|
+
omit =
|
|
6
|
+
*/_version.py
|
|
7
|
+
|
|
8
|
+
[paths]
|
|
9
|
+
pico_fastapi =
|
|
10
|
+
src/pico_fastapi
|
|
11
|
+
.tox/*/lib/*/site-packages/pico_fastapi
|
|
12
|
+
.tox/*/site-packages/pico_fastapi
|
|
13
|
+
*/site-packages/pico_fastapi
|
|
14
|
+
|
|
15
|
+
[report]
|
|
16
|
+
show_missing = True
|
|
17
|
+
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
name: CI & Coverage
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [ main ]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [ main ]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
tests:
|
|
11
|
+
name: Tests (Python ${{ matrix.python-version }})
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
strategy:
|
|
14
|
+
fail-fast: false
|
|
15
|
+
matrix:
|
|
16
|
+
python-version: [ "3.10", "3.11", "3.12", "3.13", "3.14" ]
|
|
17
|
+
|
|
18
|
+
steps:
|
|
19
|
+
- name: Checkout
|
|
20
|
+
uses: actions/checkout@v4
|
|
21
|
+
with:
|
|
22
|
+
fetch-depth: 0
|
|
23
|
+
|
|
24
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
25
|
+
uses: actions/setup-python@v5
|
|
26
|
+
with:
|
|
27
|
+
python-version: ${{ matrix.python-version }}
|
|
28
|
+
cache: pip
|
|
29
|
+
|
|
30
|
+
- name: Install tox
|
|
31
|
+
run: |
|
|
32
|
+
python -m pip install --upgrade pip
|
|
33
|
+
pip install tox
|
|
34
|
+
|
|
35
|
+
- name: Run tests with coverage (writes to repo root)
|
|
36
|
+
shell: bash
|
|
37
|
+
run: |
|
|
38
|
+
set -euxo pipefail
|
|
39
|
+
tox -e cov
|
|
40
|
+
# Rename to per-version file for artifact + merge job
|
|
41
|
+
mv .coverage .coverage.${{ matrix.python-version }}
|
|
42
|
+
ls -la .coverage.${{ matrix.python-version }}
|
|
43
|
+
|
|
44
|
+
- name: Upload .coverage artifact
|
|
45
|
+
uses: actions/upload-artifact@v4
|
|
46
|
+
with:
|
|
47
|
+
name: coverage-${{ matrix.python-version }}
|
|
48
|
+
path: .coverage.${{ matrix.python-version }}
|
|
49
|
+
include-hidden-files: true
|
|
50
|
+
if-no-files-found: error
|
|
51
|
+
|
|
52
|
+
coverage-merge:
|
|
53
|
+
name: Merge coverage
|
|
54
|
+
runs-on: ubuntu-latest
|
|
55
|
+
needs: tests
|
|
56
|
+
steps:
|
|
57
|
+
- name: Checkout (for repo context)
|
|
58
|
+
uses: actions/checkout@v4
|
|
59
|
+
|
|
60
|
+
- name: Download all coverage artifacts
|
|
61
|
+
uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
path: coverage-reports
|
|
64
|
+
|
|
65
|
+
- name: Combine coverage and generate reports
|
|
66
|
+
shell: bash
|
|
67
|
+
run: |
|
|
68
|
+
set -euxo pipefail
|
|
69
|
+
python -m pip install coverage
|
|
70
|
+
files=$(find coverage-reports -type f -name ".coverage.*" -print)
|
|
71
|
+
if [ -z "$files" ]; then
|
|
72
|
+
echo "No coverage data files found"; exit 1
|
|
73
|
+
fi
|
|
74
|
+
coverage combine $files
|
|
75
|
+
coverage report -m --rcfile=.coveragerc
|
|
76
|
+
coverage xml -o coverage.xml --rcfile=.coveragerc
|
|
77
|
+
coverage html -d htmlcov --rcfile=.coveragerc
|
|
78
|
+
|
|
79
|
+
- name: Upload combined coverage XML
|
|
80
|
+
uses: actions/upload-artifact@v4
|
|
81
|
+
with:
|
|
82
|
+
name: coverage-xml
|
|
83
|
+
path: coverage.xml
|
|
84
|
+
if-no-files-found: error
|
|
85
|
+
|
|
86
|
+
- name: Upload HTML coverage report
|
|
87
|
+
uses: actions/upload-artifact@v4
|
|
88
|
+
with:
|
|
89
|
+
name: coverage-html
|
|
90
|
+
path: htmlcov
|
|
91
|
+
if-no-files-found: error
|
|
92
|
+
|
|
93
|
+
- name: Upload to Codecov
|
|
94
|
+
if: always() && !cancelled()
|
|
95
|
+
uses: codecov/codecov-action@v5
|
|
96
|
+
continue-on-error: true
|
|
97
|
+
with:
|
|
98
|
+
files: coverage.xml
|
|
99
|
+
fail_ci_if_error: false
|
|
100
|
+
token: ${{ secrets.CODECOV_TOKEN }}
|
|
101
|
+
slug: dperezcabrera/pico-fastapi
|
|
102
|
+
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
name: Publish Python Package to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
release:
|
|
5
|
+
types: [published]
|
|
6
|
+
|
|
7
|
+
jobs:
|
|
8
|
+
deploy:
|
|
9
|
+
runs-on: ubuntu-latest
|
|
10
|
+
permissions:
|
|
11
|
+
id-token: write
|
|
12
|
+
contents: read
|
|
13
|
+
|
|
14
|
+
steps:
|
|
15
|
+
- uses: actions/checkout@v4
|
|
16
|
+
with:
|
|
17
|
+
fetch-depth: 0
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: '3.x'
|
|
23
|
+
|
|
24
|
+
- name: Install build deps
|
|
25
|
+
run: python -m pip install --upgrade pip build
|
|
26
|
+
|
|
27
|
+
- name: Build package
|
|
28
|
+
run: python -m build
|
|
29
|
+
|
|
30
|
+
- name: Publish to PyPI
|
|
31
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
32
|
+
with:
|
|
33
|
+
skip-existing: true
|
|
34
|
+
verify-metadata: true
|
|
35
|
+
attestations: false
|
|
36
|
+
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2025 David PΓ©rez Cabrera
|
|
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.
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# Incluir ficheros importantes
|
|
2
|
+
include README.md
|
|
3
|
+
include LICENSE
|
|
4
|
+
|
|
5
|
+
# Incluir todo el cΓ³digo fuente
|
|
6
|
+
recursive-include src *.py
|
|
7
|
+
|
|
8
|
+
# Incluir datos de tests si quieres que otros puedan correrlos desde el sdist
|
|
9
|
+
# (opcional β puedes quitarlo si no quieres incluir tests en PyPI)
|
|
10
|
+
recursive-include tests *.py
|
|
11
|
+
|
|
12
|
+
# Excluir cosas innecesarias
|
|
13
|
+
exclude .gitignore
|
|
14
|
+
exclude Dockerfile*
|
|
15
|
+
exclude docker-compose*.yml
|
|
16
|
+
exclude Makefile
|
|
17
|
+
|
|
18
|
+
# Excluir carpetas de build, virtualenvs, caches, etc.
|
|
19
|
+
prune build
|
|
20
|
+
prune dist
|
|
21
|
+
prune .tox
|
|
22
|
+
prune .pytest_cache
|
|
23
|
+
prune __pycache__
|
|
24
|
+
|
|
@@ -0,0 +1,214 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pico-fastapi
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Pico-ioc integration for FastAPI. Adds Spring Boot-style controllers, autoconfiguration, and scopes (request, websocket, session).
|
|
5
|
+
Author-email: David Perez Cabrera <dperezcabrera@gmail.com>
|
|
6
|
+
License: MIT License
|
|
7
|
+
|
|
8
|
+
Copyright (c) 2025 David PΓ©rez Cabrera
|
|
9
|
+
|
|
10
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
11
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
12
|
+
in the Software without restriction, including without limitation the rights
|
|
13
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
14
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
15
|
+
furnished to do so, subject to the following conditions:
|
|
16
|
+
|
|
17
|
+
The above copyright notice and this permission notice shall be included in all
|
|
18
|
+
copies or substantial portions of the Software.
|
|
19
|
+
|
|
20
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
21
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
22
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
23
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
24
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
25
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
26
|
+
SOFTWARE.
|
|
27
|
+
|
|
28
|
+
Project-URL: Homepage, https://github.com/dperezcabrera/pico-fastapi
|
|
29
|
+
Project-URL: Repository, https://github.com/dperezcabrera/pico-fastapi
|
|
30
|
+
Project-URL: Issue Tracker, https://github.com/dperezcabrera/pico-fastapi/issues
|
|
31
|
+
Keywords: ioc,di,dependency injection,fastapi,inversion of control,spring boot,controller
|
|
32
|
+
Classifier: Development Status :: 4 - Beta
|
|
33
|
+
Classifier: Framework :: FastAPI
|
|
34
|
+
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
35
|
+
Classifier: Programming Language :: Python :: 3
|
|
36
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
37
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
38
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
39
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
40
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
41
|
+
Classifier: Operating System :: OS Independent
|
|
42
|
+
Requires-Python: >=3.10
|
|
43
|
+
Description-Content-Type: text/markdown
|
|
44
|
+
License-File: LICENSE
|
|
45
|
+
Requires-Dist: pico-ioc>=2.0
|
|
46
|
+
Requires-Dist: fastapi>=0.100
|
|
47
|
+
Provides-Extra: session
|
|
48
|
+
Requires-Dist: starlette-session; extra == "session"
|
|
49
|
+
Provides-Extra: run
|
|
50
|
+
Requires-Dist: uvicorn[standard]; extra == "run"
|
|
51
|
+
Dynamic: license-file
|
|
52
|
+
|
|
53
|
+
# π¦ pico-fastapi
|
|
54
|
+
|
|
55
|
+
[](https://pypi.org/project/pico-fastapi/)
|
|
56
|
+
[](https://deepwiki.com/dperezcabrera/pico-fastapi)
|
|
57
|
+
[](https://opensource.org/licenses/MIT)
|
|
58
|
+

|
|
59
|
+
[](https://codecov.io/gh/dperezcabrera/pico-fastapi)
|
|
60
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-fastapi)
|
|
61
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-fastapi)
|
|
62
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-fastapi)
|
|
63
|
+
|
|
64
|
+
**pico-fastapi** integrates **Pico-IoC** with **FastAPI**, enabling *constructor-based dependency injection*, scoped lifecycles, and clean architectural boundaries β without global state or FastAPI dependency functions.
|
|
65
|
+
|
|
66
|
+
> π Requires **Python 3.10+**
|
|
67
|
+
> β
Fully async-compatible
|
|
68
|
+
> β
Real IoC (constructor injection, not function injection)
|
|
69
|
+
> β
Works with request, session, and websocket scopes
|
|
70
|
+
|
|
71
|
+
---
|
|
72
|
+
|
|
73
|
+
## π― Why pico-fastapi?
|
|
74
|
+
|
|
75
|
+
FastAPIβs built-in dependency system is function-based, which makes business logic tightly coupled to the framework.
|
|
76
|
+
|
|
77
|
+
`pico-fastapi` moves dependency resolution to the **IoC container**.
|
|
78
|
+
|
|
79
|
+
| Concern | FastAPI Default | pico-fastapi |
|
|
80
|
+
|--------|----------------|--------------|
|
|
81
|
+
| Dependency injection | Function-based | Constructor-based |
|
|
82
|
+
| Architecture | Framework-driven | Domain-driven |
|
|
83
|
+
| Testing | Must simulate DI functions | Component overrides at container init |
|
|
84
|
+
| Scopes | Manual or ad-hoc | `singleton`, `request`, `session`, `websocket` |
|
|
85
|
+
|
|
86
|
+
---
|
|
87
|
+
|
|
88
|
+
## π§± Core Features
|
|
89
|
+
|
|
90
|
+
- `@controller` class-based routing
|
|
91
|
+
- `@get`, `@post`, `@websocket`, etc.
|
|
92
|
+
- Constructor injection for controllers & services
|
|
93
|
+
- Automatic registration into FastAPI
|
|
94
|
+
- Scoped resolution via middleware (`request`, `session`, `websocket`)
|
|
95
|
+
- Full compatibility with Pico-IoC features: overrides, profiles, interceptors, cleanup
|
|
96
|
+
|
|
97
|
+
---
|
|
98
|
+
|
|
99
|
+
## π¦ Installation
|
|
100
|
+
|
|
101
|
+
```bash
|
|
102
|
+
pip install pico-fastapi
|
|
103
|
+
````
|
|
104
|
+
|
|
105
|
+
Also requires:
|
|
106
|
+
|
|
107
|
+
```bash
|
|
108
|
+
pip install pico-ioc fastapi
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
---
|
|
112
|
+
|
|
113
|
+
## π Quick Example
|
|
114
|
+
|
|
115
|
+
```python
|
|
116
|
+
# controllers.py
|
|
117
|
+
from pico_fastapi import controller, get
|
|
118
|
+
|
|
119
|
+
@controller(prefix="/api")
|
|
120
|
+
class ApiController:
|
|
121
|
+
def __init__(self, service: "MyService"):
|
|
122
|
+
self.service = service
|
|
123
|
+
|
|
124
|
+
@get("/hello")
|
|
125
|
+
async def hello(self):
|
|
126
|
+
return {"msg": self.service.greet()}
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```python
|
|
130
|
+
# services.py
|
|
131
|
+
class MyService:
|
|
132
|
+
def greet(self) -> str:
|
|
133
|
+
return "hello from service"
|
|
134
|
+
```
|
|
135
|
+
|
|
136
|
+
```python
|
|
137
|
+
# main.py
|
|
138
|
+
from pico_ioc import init
|
|
139
|
+
from fastapi import FastAPI
|
|
140
|
+
|
|
141
|
+
container = init(
|
|
142
|
+
modules=[
|
|
143
|
+
"controllers",
|
|
144
|
+
"services",
|
|
145
|
+
"pico_fastapi.factory",
|
|
146
|
+
]
|
|
147
|
+
)
|
|
148
|
+
|
|
149
|
+
app = container.get(FastAPI) # β
retrieve the fully configured app
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
---
|
|
153
|
+
|
|
154
|
+
## π¬ WebSocket Example
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
from pico_fastapi import controller, websocket
|
|
158
|
+
from fastapi import WebSocket
|
|
159
|
+
|
|
160
|
+
@controller
|
|
161
|
+
class ChatController:
|
|
162
|
+
async def __init__(self):
|
|
163
|
+
pass
|
|
164
|
+
|
|
165
|
+
@websocket("/ws")
|
|
166
|
+
async def chat(self, websocket: WebSocket):
|
|
167
|
+
await websocket.accept()
|
|
168
|
+
while True:
|
|
169
|
+
message = await websocket.receive_text()
|
|
170
|
+
await websocket.send_text(f"Echo: {message}")
|
|
171
|
+
```
|
|
172
|
+
|
|
173
|
+
---
|
|
174
|
+
|
|
175
|
+
## π§ͺ Testing with Overrides
|
|
176
|
+
|
|
177
|
+
```python
|
|
178
|
+
from pico_ioc import init
|
|
179
|
+
from fastapi import FastAPI
|
|
180
|
+
from fastapi.testclient import TestClient
|
|
181
|
+
|
|
182
|
+
class FakeService:
|
|
183
|
+
def greet(self) -> str:
|
|
184
|
+
return "test"
|
|
185
|
+
|
|
186
|
+
container = init(
|
|
187
|
+
modules=["controllers", "services", "pico_fastapi.factory"],
|
|
188
|
+
overrides={ "MyService": FakeService() }
|
|
189
|
+
)
|
|
190
|
+
|
|
191
|
+
app = container.get(FastAPI)
|
|
192
|
+
client = TestClient(app)
|
|
193
|
+
|
|
194
|
+
assert client.get("/api/hello").json() == {"msg": "test"}
|
|
195
|
+
```
|
|
196
|
+
|
|
197
|
+
---
|
|
198
|
+
|
|
199
|
+
## βοΈ How It Works
|
|
200
|
+
|
|
201
|
+
* `@controller` classes are registered automatically
|
|
202
|
+
* HTTP/WebSocket handlers are wrapped in a request or websocket scope
|
|
203
|
+
* All dependencies (services, config, state) are resolved through Pico-IoC
|
|
204
|
+
* Cleanup happens at application shutdown via lifespan integration
|
|
205
|
+
|
|
206
|
+
No global state. No implicit singletons. No magic.
|
|
207
|
+
|
|
208
|
+
---
|
|
209
|
+
|
|
210
|
+
## π License
|
|
211
|
+
|
|
212
|
+
MIT β See `LICENSE`.
|
|
213
|
+
|
|
214
|
+
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
# π¦ pico-fastapi
|
|
2
|
+
|
|
3
|
+
[](https://pypi.org/project/pico-fastapi/)
|
|
4
|
+
[](https://deepwiki.com/dperezcabrera/pico-fastapi)
|
|
5
|
+
[](https://opensource.org/licenses/MIT)
|
|
6
|
+

|
|
7
|
+
[](https://codecov.io/gh/dperezcabrera/pico-fastapi)
|
|
8
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-fastapi)
|
|
9
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-fastapi)
|
|
10
|
+
[](https://sonarcloud.io/summary/new_code?id=dperezcabrera_pico-fastapi)
|
|
11
|
+
|
|
12
|
+
**pico-fastapi** integrates **Pico-IoC** with **FastAPI**, enabling *constructor-based dependency injection*, scoped lifecycles, and clean architectural boundaries β without global state or FastAPI dependency functions.
|
|
13
|
+
|
|
14
|
+
> π Requires **Python 3.10+**
|
|
15
|
+
> β
Fully async-compatible
|
|
16
|
+
> β
Real IoC (constructor injection, not function injection)
|
|
17
|
+
> β
Works with request, session, and websocket scopes
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## π― Why pico-fastapi?
|
|
22
|
+
|
|
23
|
+
FastAPIβs built-in dependency system is function-based, which makes business logic tightly coupled to the framework.
|
|
24
|
+
|
|
25
|
+
`pico-fastapi` moves dependency resolution to the **IoC container**.
|
|
26
|
+
|
|
27
|
+
| Concern | FastAPI Default | pico-fastapi |
|
|
28
|
+
|--------|----------------|--------------|
|
|
29
|
+
| Dependency injection | Function-based | Constructor-based |
|
|
30
|
+
| Architecture | Framework-driven | Domain-driven |
|
|
31
|
+
| Testing | Must simulate DI functions | Component overrides at container init |
|
|
32
|
+
| Scopes | Manual or ad-hoc | `singleton`, `request`, `session`, `websocket` |
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
36
|
+
## π§± Core Features
|
|
37
|
+
|
|
38
|
+
- `@controller` class-based routing
|
|
39
|
+
- `@get`, `@post`, `@websocket`, etc.
|
|
40
|
+
- Constructor injection for controllers & services
|
|
41
|
+
- Automatic registration into FastAPI
|
|
42
|
+
- Scoped resolution via middleware (`request`, `session`, `websocket`)
|
|
43
|
+
- Full compatibility with Pico-IoC features: overrides, profiles, interceptors, cleanup
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## π¦ Installation
|
|
48
|
+
|
|
49
|
+
```bash
|
|
50
|
+
pip install pico-fastapi
|
|
51
|
+
````
|
|
52
|
+
|
|
53
|
+
Also requires:
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
pip install pico-ioc fastapi
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
---
|
|
60
|
+
|
|
61
|
+
## π Quick Example
|
|
62
|
+
|
|
63
|
+
```python
|
|
64
|
+
# controllers.py
|
|
65
|
+
from pico_fastapi import controller, get
|
|
66
|
+
|
|
67
|
+
@controller(prefix="/api")
|
|
68
|
+
class ApiController:
|
|
69
|
+
def __init__(self, service: "MyService"):
|
|
70
|
+
self.service = service
|
|
71
|
+
|
|
72
|
+
@get("/hello")
|
|
73
|
+
async def hello(self):
|
|
74
|
+
return {"msg": self.service.greet()}
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
```python
|
|
78
|
+
# services.py
|
|
79
|
+
class MyService:
|
|
80
|
+
def greet(self) -> str:
|
|
81
|
+
return "hello from service"
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
# main.py
|
|
86
|
+
from pico_ioc import init
|
|
87
|
+
from fastapi import FastAPI
|
|
88
|
+
|
|
89
|
+
container = init(
|
|
90
|
+
modules=[
|
|
91
|
+
"controllers",
|
|
92
|
+
"services",
|
|
93
|
+
"pico_fastapi.factory",
|
|
94
|
+
]
|
|
95
|
+
)
|
|
96
|
+
|
|
97
|
+
app = container.get(FastAPI) # β
retrieve the fully configured app
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## π¬ WebSocket Example
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from pico_fastapi import controller, websocket
|
|
106
|
+
from fastapi import WebSocket
|
|
107
|
+
|
|
108
|
+
@controller
|
|
109
|
+
class ChatController:
|
|
110
|
+
async def __init__(self):
|
|
111
|
+
pass
|
|
112
|
+
|
|
113
|
+
@websocket("/ws")
|
|
114
|
+
async def chat(self, websocket: WebSocket):
|
|
115
|
+
await websocket.accept()
|
|
116
|
+
while True:
|
|
117
|
+
message = await websocket.receive_text()
|
|
118
|
+
await websocket.send_text(f"Echo: {message}")
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
---
|
|
122
|
+
|
|
123
|
+
## π§ͺ Testing with Overrides
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
from pico_ioc import init
|
|
127
|
+
from fastapi import FastAPI
|
|
128
|
+
from fastapi.testclient import TestClient
|
|
129
|
+
|
|
130
|
+
class FakeService:
|
|
131
|
+
def greet(self) -> str:
|
|
132
|
+
return "test"
|
|
133
|
+
|
|
134
|
+
container = init(
|
|
135
|
+
modules=["controllers", "services", "pico_fastapi.factory"],
|
|
136
|
+
overrides={ "MyService": FakeService() }
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
app = container.get(FastAPI)
|
|
140
|
+
client = TestClient(app)
|
|
141
|
+
|
|
142
|
+
assert client.get("/api/hello").json() == {"msg": "test"}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
---
|
|
146
|
+
|
|
147
|
+
## βοΈ How It Works
|
|
148
|
+
|
|
149
|
+
* `@controller` classes are registered automatically
|
|
150
|
+
* HTTP/WebSocket handlers are wrapped in a request or websocket scope
|
|
151
|
+
* All dependencies (services, config, state) are resolved through Pico-IoC
|
|
152
|
+
* Cleanup happens at application shutdown via lifespan integration
|
|
153
|
+
|
|
154
|
+
No global state. No implicit singletons. No magic.
|
|
155
|
+
|
|
156
|
+
---
|
|
157
|
+
|
|
158
|
+
## π License
|
|
159
|
+
|
|
160
|
+
MIT β See `LICENSE`.
|
|
161
|
+
|
|
162
|
+
|