pyboot-web 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.
- pyboot_web-0.1.0/LICENSE +21 -0
- pyboot_web-0.1.0/PKG-INFO +234 -0
- pyboot_web-0.1.0/README.md +221 -0
- pyboot_web-0.1.0/pyproject.toml +24 -0
- pyboot_web-0.1.0/setup.cfg +4 -0
- pyboot_web-0.1.0/src/pyboot_web/ExceptionResolver.py +33 -0
- pyboot_web-0.1.0/src/pyboot_web/WebAttr.py +18 -0
- pyboot_web-0.1.0/src/pyboot_web/__init__.py +0 -0
- pyboot_web-0.1.0/src/pyboot_web/exception_handler.py +25 -0
- pyboot_web-0.1.0/src/pyboot_web/middlewares.py +44 -0
- pyboot_web-0.1.0/src/pyboot_web/plugin.py +305 -0
- pyboot_web-0.1.0/src/pyboot_web/py.typed +0 -0
- pyboot_web-0.1.0/src/pyboot_web/rest_mapping.py +57 -0
- pyboot_web-0.1.0/src/pyboot_web/service.py +11 -0
- pyboot_web-0.1.0/src/pyboot_web/types.py +16 -0
- pyboot_web-0.1.0/src/pyboot_web/value.py +63 -0
- pyboot_web-0.1.0/src/pyboot_web.egg-info/PKG-INFO +234 -0
- pyboot_web-0.1.0/src/pyboot_web.egg-info/SOURCES.txt +20 -0
- pyboot_web-0.1.0/src/pyboot_web.egg-info/dependency_links.txt +1 -0
- pyboot_web-0.1.0/src/pyboot_web.egg-info/entry_points.txt +3 -0
- pyboot_web-0.1.0/src/pyboot_web.egg-info/requires.txt +3 -0
- pyboot_web-0.1.0/src/pyboot_web.egg-info/top_level.txt +1 -0
pyboot_web-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 pyboot contributors
|
|
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,234 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: pyboot-web
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Starlette web plugin for PyBoot
|
|
5
|
+
License-Expression: MIT
|
|
6
|
+
Requires-Python: >=3.11
|
|
7
|
+
Description-Content-Type: text/markdown
|
|
8
|
+
License-File: LICENSE
|
|
9
|
+
Requires-Dist: pyboot-framework>=0.1.0
|
|
10
|
+
Requires-Dist: starlette>=0.40.0
|
|
11
|
+
Requires-Dist: pydantic>=2.0
|
|
12
|
+
Dynamic: license-file
|
|
13
|
+
|
|
14
|
+
# pyboot-web
|
|
15
|
+
|
|
16
|
+
`pyboot-web` is the Starlette web plugin for [PyBoot](../../pyboot/README.md).
|
|
17
|
+
It adds decorator-based HTTP controllers, route mappings, services, request
|
|
18
|
+
middleware, centralized exception handlers, and `.env` configuration loading
|
|
19
|
+
to a PyBoot application.
|
|
20
|
+
|
|
21
|
+
The plugin is intentionally small: PyBoot remains responsible for class
|
|
22
|
+
discovery, dependency injection, bean creation, and lifecycle orchestration;
|
|
23
|
+
this package translates annotated classes and methods into Starlette routes.
|
|
24
|
+
|
|
25
|
+
## Documentation
|
|
26
|
+
|
|
27
|
+
- [Examples](./EXAMPLES.md) - runnable snippets for routing, services,
|
|
28
|
+
middleware, exception advice, and configuration.
|
|
29
|
+
- [Production starter](./STARTER.md) - a recommended project layout,
|
|
30
|
+
bootstrap implementation, deployment checklist, and testing approach.
|
|
31
|
+
- [License](./LICENSE) - MIT license terms.
|
|
32
|
+
|
|
33
|
+
## Features
|
|
34
|
+
|
|
35
|
+
| Capability | API |
|
|
36
|
+
| --- | --- |
|
|
37
|
+
| HTTP controller | `RestController` |
|
|
38
|
+
| HTTP routes | `GetMapping`, `PostMapping`, `PutMapping`, `PatchMapping`, `DeleteMapping` |
|
|
39
|
+
| Application services | `Service` |
|
|
40
|
+
| PyBoot-managed components | `Component` |
|
|
41
|
+
| Bean factories | `Configuration`, `Bean` |
|
|
42
|
+
| Middleware | `MiddlewareBase`, `MiddlewareComponent`, `UseMiddleware` |
|
|
43
|
+
| Exception advice | `RestControllerAdvice`, `ExceptionHandler` |
|
|
44
|
+
| Environment configuration | `Value`, `ValuePlugin` |
|
|
45
|
+
| PyBoot integration | `WebPlugin`, registered as the `web` PyBoot plugin |
|
|
46
|
+
|
|
47
|
+
The package supports Python 3.11 and newer and uses Starlette for routing and
|
|
48
|
+
request/response types.
|
|
49
|
+
|
|
50
|
+
## Installation
|
|
51
|
+
|
|
52
|
+
Install PyBoot and this plugin in the same environment:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
python -m pip install pyboot-framework pyboot-web
|
|
56
|
+
python -m pip install python-dotenv uvicorn
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
For a source checkout, from the repository root:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python -m pip install -e pyboot
|
|
63
|
+
python -m pip install -e plugins/pyboot_web
|
|
64
|
+
python -m pip install python-dotenv uvicorn
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
The package metadata declares `starlette`. `python-dotenv` is required by the
|
|
68
|
+
`ValuePlugin` implementation.
|
|
69
|
+
|
|
70
|
+
Verify that PyBoot can discover the plugin entry points:
|
|
71
|
+
|
|
72
|
+
```bash
|
|
73
|
+
python -c "from importlib.metadata import entry_points; print([e.name for e in entry_points(group='pyboot.plugins')])"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
The output should include `web`. It may also include `value` and other
|
|
77
|
+
installed plugins.
|
|
78
|
+
|
|
79
|
+
## Minimal bootstrap contract
|
|
80
|
+
|
|
81
|
+
Create a Starlette application, pass it to PyBoot, bootstrap the package that
|
|
82
|
+
contains the application classes, and expose the original Starlette object:
|
|
83
|
+
|
|
84
|
+
```python
|
|
85
|
+
from starlette.applications import Starlette
|
|
86
|
+
from pyboot.core.application import PyBoot
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
app = Starlette(debug=False)
|
|
90
|
+
PyBoot(app_instance=app, debug=False).bootstrap(package="app")
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Run it with an ASGI server:
|
|
94
|
+
|
|
95
|
+
```bash
|
|
96
|
+
python -m uvicorn main:app --host 127.0.0.1 --port 8000
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
`PyBoot.bootstrap()` returns `None`; it mutates the supplied Starlette
|
|
100
|
+
application by registering routes. Do not assign the return value to `app`.
|
|
101
|
+
|
|
102
|
+
## API reference
|
|
103
|
+
|
|
104
|
+
### `pyboot_web.rest_mapping`
|
|
105
|
+
|
|
106
|
+
- `RestController(path: str)` marks a class as an HTTP controller.
|
|
107
|
+
- `GetMapping(path)`, `PostMapping(path)`, `PutMapping(path)`,
|
|
108
|
+
`PatchMapping(path)`, and `DeleteMapping(path)` provide method-specific
|
|
109
|
+
route decorators for methods declared inside a `RestController` class.
|
|
110
|
+
|
|
111
|
+
Route decorators are intended for controller methods, following the
|
|
112
|
+
class-oriented style used by Spring Boot:
|
|
113
|
+
|
|
114
|
+
```python
|
|
115
|
+
@RestController("/api")
|
|
116
|
+
class HealthController:
|
|
117
|
+
@GetMapping("/health")
|
|
118
|
+
async def health(self, request: Request) -> JSONResponse:
|
|
119
|
+
...
|
|
120
|
+
```
|
|
121
|
+
|
|
122
|
+
Supported methods are `GET`, `POST`, `PUT`, `PATCH`, and `DELETE`. A standalone
|
|
123
|
+
function decorated as a route is not discovered by `WebPlugin`.
|
|
124
|
+
|
|
125
|
+
### `pyboot_web.service`
|
|
126
|
+
|
|
127
|
+
- `Service()` marks a class for web-plugin registration and IoC construction.
|
|
128
|
+
|
|
129
|
+
Use `Service` for web-layer services. Use PyBoot's built-in `Component` for
|
|
130
|
+
general application dependencies that should be managed by the core IoC
|
|
131
|
+
container:
|
|
132
|
+
|
|
133
|
+
```python
|
|
134
|
+
from pyboot.core.builtins.component import Component
|
|
135
|
+
|
|
136
|
+
|
|
137
|
+
@Component()
|
|
138
|
+
class AuditService:
|
|
139
|
+
...
|
|
140
|
+
```
|
|
141
|
+
|
|
142
|
+
`Component` classes can be injected into `Service` classes and controllers
|
|
143
|
+
when their constructor parameters have type annotations.
|
|
144
|
+
|
|
145
|
+
### PyBoot built-in bean management
|
|
146
|
+
|
|
147
|
+
`Component` is the general-purpose PyBoot IoC decorator. For factory-created
|
|
148
|
+
objects, use a configuration class with methods annotated by `Bean`:
|
|
149
|
+
|
|
150
|
+
```python
|
|
151
|
+
from pyboot.core.builtins.configuration import Bean, Configuration
|
|
152
|
+
|
|
153
|
+
|
|
154
|
+
class Clock:
|
|
155
|
+
...
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
@Configuration()
|
|
159
|
+
class ApplicationConfiguration:
|
|
160
|
+
@Bean()
|
|
161
|
+
def clock(self) -> Clock:
|
|
162
|
+
return Clock()
|
|
163
|
+
```
|
|
164
|
+
|
|
165
|
+
The `@Bean` method must have a return annotation. PyBoot registers that return
|
|
166
|
+
type as a bean factory, resolves any annotated method dependencies, and creates
|
|
167
|
+
the bean through the IoC container. `Component`, `Configuration`, and `Bean`
|
|
168
|
+
are core PyBoot features, not web-specific decorators.
|
|
169
|
+
|
|
170
|
+
### `pyboot_web.middlewares`
|
|
171
|
+
|
|
172
|
+
- `MiddlewareBase` defines the callable middleware contract.
|
|
173
|
+
- `MiddlewareComponent()` marks a middleware class for IoC registration.
|
|
174
|
+
- `UseMiddleware(middlewares)` attaches middleware classes to a controller or
|
|
175
|
+
route method.
|
|
176
|
+
|
|
177
|
+
Pass middleware classes, not instances. The plugin obtains instances from
|
|
178
|
+
PyBoot's IoC container.
|
|
179
|
+
|
|
180
|
+
### `pyboot_web.exception_handler`
|
|
181
|
+
|
|
182
|
+
- `RestControllerAdvice()` marks an exception advice class.
|
|
183
|
+
- `ExceptionHandler(exception_type)` maps an exception type to a handler method.
|
|
184
|
+
|
|
185
|
+
Handlers receive `(request, exception)` after `self` and should return a
|
|
186
|
+
Starlette response. A matching base-class handler can handle subclasses.
|
|
187
|
+
Duplicate registrations for the same exception type are rejected. Unhandled
|
|
188
|
+
exceptions are re-raised.
|
|
189
|
+
|
|
190
|
+
### `pyboot_web.value`
|
|
191
|
+
|
|
192
|
+
- `Value(key, default)` returns an environment value or the supplied default.
|
|
193
|
+
- `MissingValueException` is raised when both are `None`.
|
|
194
|
+
- `ValuePlugin` loads a discovered `.env` file before class discovery.
|
|
195
|
+
|
|
196
|
+
Environment values are strings and should be converted explicitly at the
|
|
197
|
+
configuration boundary. `ValuePlugin` uses `override=True`, so `.env` values
|
|
198
|
+
can replace values already present in the process environment.
|
|
199
|
+
|
|
200
|
+
### `pyboot_web.plugin`
|
|
201
|
+
|
|
202
|
+
- `WebPlugin` registers web beans, resolves exception advice, wraps routes with
|
|
203
|
+
middleware and exception handling, and adds routes to the supplied Starlette
|
|
204
|
+
application.
|
|
205
|
+
|
|
206
|
+
## Discovery and lifecycle
|
|
207
|
+
|
|
208
|
+
PyBoot loads installed `pyboot.plugins` entry points, imports the package
|
|
209
|
+
passed to `bootstrap()`, and scans its submodules for classes defined there.
|
|
210
|
+
The web plugin then:
|
|
211
|
+
|
|
212
|
+
1. Registers `Service`, `Component`, `MiddlewareComponent`,
|
|
213
|
+
`RestControllerAdvice`, and `RestController` classes with the IoC container.
|
|
214
|
+
2. Lets PyBoot create registered beans.
|
|
215
|
+
3. Registers exception advice handlers.
|
|
216
|
+
4. Combines controller and route middleware.
|
|
217
|
+
5. Adds annotated controller methods as Starlette routes.
|
|
218
|
+
|
|
219
|
+
Keep application classes inside the scanned package and keep module-level
|
|
220
|
+
side effects small and deterministic. Directories intended as packages should
|
|
221
|
+
contain `__init__.py`.
|
|
222
|
+
|
|
223
|
+
## Compatibility and scope
|
|
224
|
+
|
|
225
|
+
- This package targets Starlette and does not provide Flask or Django adapters.
|
|
226
|
+
- It adds routes to a supplied Starlette application; it does not create one.
|
|
227
|
+
- Services and middleware depend on PyBoot IoC registration and constructor
|
|
228
|
+
resolution.
|
|
229
|
+
- The package root does not re-export the decorators; import them from their
|
|
230
|
+
defining modules.
|
|
231
|
+
|
|
232
|
+
## License
|
|
233
|
+
|
|
234
|
+
This project is distributed under the MIT License. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
# pyboot-web
|
|
2
|
+
|
|
3
|
+
`pyboot-web` is the Starlette web plugin for [PyBoot](../../pyboot/README.md).
|
|
4
|
+
It adds decorator-based HTTP controllers, route mappings, services, request
|
|
5
|
+
middleware, centralized exception handlers, and `.env` configuration loading
|
|
6
|
+
to a PyBoot application.
|
|
7
|
+
|
|
8
|
+
The plugin is intentionally small: PyBoot remains responsible for class
|
|
9
|
+
discovery, dependency injection, bean creation, and lifecycle orchestration;
|
|
10
|
+
this package translates annotated classes and methods into Starlette routes.
|
|
11
|
+
|
|
12
|
+
## Documentation
|
|
13
|
+
|
|
14
|
+
- [Examples](./EXAMPLES.md) - runnable snippets for routing, services,
|
|
15
|
+
middleware, exception advice, and configuration.
|
|
16
|
+
- [Production starter](./STARTER.md) - a recommended project layout,
|
|
17
|
+
bootstrap implementation, deployment checklist, and testing approach.
|
|
18
|
+
- [License](./LICENSE) - MIT license terms.
|
|
19
|
+
|
|
20
|
+
## Features
|
|
21
|
+
|
|
22
|
+
| Capability | API |
|
|
23
|
+
| --- | --- |
|
|
24
|
+
| HTTP controller | `RestController` |
|
|
25
|
+
| HTTP routes | `GetMapping`, `PostMapping`, `PutMapping`, `PatchMapping`, `DeleteMapping` |
|
|
26
|
+
| Application services | `Service` |
|
|
27
|
+
| PyBoot-managed components | `Component` |
|
|
28
|
+
| Bean factories | `Configuration`, `Bean` |
|
|
29
|
+
| Middleware | `MiddlewareBase`, `MiddlewareComponent`, `UseMiddleware` |
|
|
30
|
+
| Exception advice | `RestControllerAdvice`, `ExceptionHandler` |
|
|
31
|
+
| Environment configuration | `Value`, `ValuePlugin` |
|
|
32
|
+
| PyBoot integration | `WebPlugin`, registered as the `web` PyBoot plugin |
|
|
33
|
+
|
|
34
|
+
The package supports Python 3.11 and newer and uses Starlette for routing and
|
|
35
|
+
request/response types.
|
|
36
|
+
|
|
37
|
+
## Installation
|
|
38
|
+
|
|
39
|
+
Install PyBoot and this plugin in the same environment:
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
python -m pip install pyboot-framework pyboot-web
|
|
43
|
+
python -m pip install python-dotenv uvicorn
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
For a source checkout, from the repository root:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
python -m pip install -e pyboot
|
|
50
|
+
python -m pip install -e plugins/pyboot_web
|
|
51
|
+
python -m pip install python-dotenv uvicorn
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
The package metadata declares `starlette`. `python-dotenv` is required by the
|
|
55
|
+
`ValuePlugin` implementation.
|
|
56
|
+
|
|
57
|
+
Verify that PyBoot can discover the plugin entry points:
|
|
58
|
+
|
|
59
|
+
```bash
|
|
60
|
+
python -c "from importlib.metadata import entry_points; print([e.name for e in entry_points(group='pyboot.plugins')])"
|
|
61
|
+
```
|
|
62
|
+
|
|
63
|
+
The output should include `web`. It may also include `value` and other
|
|
64
|
+
installed plugins.
|
|
65
|
+
|
|
66
|
+
## Minimal bootstrap contract
|
|
67
|
+
|
|
68
|
+
Create a Starlette application, pass it to PyBoot, bootstrap the package that
|
|
69
|
+
contains the application classes, and expose the original Starlette object:
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
from starlette.applications import Starlette
|
|
73
|
+
from pyboot.core.application import PyBoot
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
app = Starlette(debug=False)
|
|
77
|
+
PyBoot(app_instance=app, debug=False).bootstrap(package="app")
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Run it with an ASGI server:
|
|
81
|
+
|
|
82
|
+
```bash
|
|
83
|
+
python -m uvicorn main:app --host 127.0.0.1 --port 8000
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
`PyBoot.bootstrap()` returns `None`; it mutates the supplied Starlette
|
|
87
|
+
application by registering routes. Do not assign the return value to `app`.
|
|
88
|
+
|
|
89
|
+
## API reference
|
|
90
|
+
|
|
91
|
+
### `pyboot_web.rest_mapping`
|
|
92
|
+
|
|
93
|
+
- `RestController(path: str)` marks a class as an HTTP controller.
|
|
94
|
+
- `GetMapping(path)`, `PostMapping(path)`, `PutMapping(path)`,
|
|
95
|
+
`PatchMapping(path)`, and `DeleteMapping(path)` provide method-specific
|
|
96
|
+
route decorators for methods declared inside a `RestController` class.
|
|
97
|
+
|
|
98
|
+
Route decorators are intended for controller methods, following the
|
|
99
|
+
class-oriented style used by Spring Boot:
|
|
100
|
+
|
|
101
|
+
```python
|
|
102
|
+
@RestController("/api")
|
|
103
|
+
class HealthController:
|
|
104
|
+
@GetMapping("/health")
|
|
105
|
+
async def health(self, request: Request) -> JSONResponse:
|
|
106
|
+
...
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
Supported methods are `GET`, `POST`, `PUT`, `PATCH`, and `DELETE`. A standalone
|
|
110
|
+
function decorated as a route is not discovered by `WebPlugin`.
|
|
111
|
+
|
|
112
|
+
### `pyboot_web.service`
|
|
113
|
+
|
|
114
|
+
- `Service()` marks a class for web-plugin registration and IoC construction.
|
|
115
|
+
|
|
116
|
+
Use `Service` for web-layer services. Use PyBoot's built-in `Component` for
|
|
117
|
+
general application dependencies that should be managed by the core IoC
|
|
118
|
+
container:
|
|
119
|
+
|
|
120
|
+
```python
|
|
121
|
+
from pyboot.core.builtins.component import Component
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
@Component()
|
|
125
|
+
class AuditService:
|
|
126
|
+
...
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
`Component` classes can be injected into `Service` classes and controllers
|
|
130
|
+
when their constructor parameters have type annotations.
|
|
131
|
+
|
|
132
|
+
### PyBoot built-in bean management
|
|
133
|
+
|
|
134
|
+
`Component` is the general-purpose PyBoot IoC decorator. For factory-created
|
|
135
|
+
objects, use a configuration class with methods annotated by `Bean`:
|
|
136
|
+
|
|
137
|
+
```python
|
|
138
|
+
from pyboot.core.builtins.configuration import Bean, Configuration
|
|
139
|
+
|
|
140
|
+
|
|
141
|
+
class Clock:
|
|
142
|
+
...
|
|
143
|
+
|
|
144
|
+
|
|
145
|
+
@Configuration()
|
|
146
|
+
class ApplicationConfiguration:
|
|
147
|
+
@Bean()
|
|
148
|
+
def clock(self) -> Clock:
|
|
149
|
+
return Clock()
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
The `@Bean` method must have a return annotation. PyBoot registers that return
|
|
153
|
+
type as a bean factory, resolves any annotated method dependencies, and creates
|
|
154
|
+
the bean through the IoC container. `Component`, `Configuration`, and `Bean`
|
|
155
|
+
are core PyBoot features, not web-specific decorators.
|
|
156
|
+
|
|
157
|
+
### `pyboot_web.middlewares`
|
|
158
|
+
|
|
159
|
+
- `MiddlewareBase` defines the callable middleware contract.
|
|
160
|
+
- `MiddlewareComponent()` marks a middleware class for IoC registration.
|
|
161
|
+
- `UseMiddleware(middlewares)` attaches middleware classes to a controller or
|
|
162
|
+
route method.
|
|
163
|
+
|
|
164
|
+
Pass middleware classes, not instances. The plugin obtains instances from
|
|
165
|
+
PyBoot's IoC container.
|
|
166
|
+
|
|
167
|
+
### `pyboot_web.exception_handler`
|
|
168
|
+
|
|
169
|
+
- `RestControllerAdvice()` marks an exception advice class.
|
|
170
|
+
- `ExceptionHandler(exception_type)` maps an exception type to a handler method.
|
|
171
|
+
|
|
172
|
+
Handlers receive `(request, exception)` after `self` and should return a
|
|
173
|
+
Starlette response. A matching base-class handler can handle subclasses.
|
|
174
|
+
Duplicate registrations for the same exception type are rejected. Unhandled
|
|
175
|
+
exceptions are re-raised.
|
|
176
|
+
|
|
177
|
+
### `pyboot_web.value`
|
|
178
|
+
|
|
179
|
+
- `Value(key, default)` returns an environment value or the supplied default.
|
|
180
|
+
- `MissingValueException` is raised when both are `None`.
|
|
181
|
+
- `ValuePlugin` loads a discovered `.env` file before class discovery.
|
|
182
|
+
|
|
183
|
+
Environment values are strings and should be converted explicitly at the
|
|
184
|
+
configuration boundary. `ValuePlugin` uses `override=True`, so `.env` values
|
|
185
|
+
can replace values already present in the process environment.
|
|
186
|
+
|
|
187
|
+
### `pyboot_web.plugin`
|
|
188
|
+
|
|
189
|
+
- `WebPlugin` registers web beans, resolves exception advice, wraps routes with
|
|
190
|
+
middleware and exception handling, and adds routes to the supplied Starlette
|
|
191
|
+
application.
|
|
192
|
+
|
|
193
|
+
## Discovery and lifecycle
|
|
194
|
+
|
|
195
|
+
PyBoot loads installed `pyboot.plugins` entry points, imports the package
|
|
196
|
+
passed to `bootstrap()`, and scans its submodules for classes defined there.
|
|
197
|
+
The web plugin then:
|
|
198
|
+
|
|
199
|
+
1. Registers `Service`, `Component`, `MiddlewareComponent`,
|
|
200
|
+
`RestControllerAdvice`, and `RestController` classes with the IoC container.
|
|
201
|
+
2. Lets PyBoot create registered beans.
|
|
202
|
+
3. Registers exception advice handlers.
|
|
203
|
+
4. Combines controller and route middleware.
|
|
204
|
+
5. Adds annotated controller methods as Starlette routes.
|
|
205
|
+
|
|
206
|
+
Keep application classes inside the scanned package and keep module-level
|
|
207
|
+
side effects small and deterministic. Directories intended as packages should
|
|
208
|
+
contain `__init__.py`.
|
|
209
|
+
|
|
210
|
+
## Compatibility and scope
|
|
211
|
+
|
|
212
|
+
- This package targets Starlette and does not provide Flask or Django adapters.
|
|
213
|
+
- It adds routes to a supplied Starlette application; it does not create one.
|
|
214
|
+
- Services and middleware depend on PyBoot IoC registration and constructor
|
|
215
|
+
resolution.
|
|
216
|
+
- The package root does not re-export the decorators; import them from their
|
|
217
|
+
defining modules.
|
|
218
|
+
|
|
219
|
+
## License
|
|
220
|
+
|
|
221
|
+
This project is distributed under the MIT License. See [LICENSE](./LICENSE).
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
[build-system]
|
|
2
|
+
requires = ["setuptools>=75"]
|
|
3
|
+
build-backend = "setuptools.build_meta"
|
|
4
|
+
|
|
5
|
+
[project]
|
|
6
|
+
name = "pyboot-web"
|
|
7
|
+
version = "0.1.0"
|
|
8
|
+
description = "Starlette web plugin for PyBoot"
|
|
9
|
+
readme = "README.md"
|
|
10
|
+
requires-python = ">=3.11"
|
|
11
|
+
license = "MIT"
|
|
12
|
+
|
|
13
|
+
dependencies = [
|
|
14
|
+
"pyboot-framework>=0.1.0",
|
|
15
|
+
"starlette>=0.40.0",
|
|
16
|
+
"pydantic>=2.0"
|
|
17
|
+
]
|
|
18
|
+
|
|
19
|
+
[project.entry-points."pyboot.plugins"]
|
|
20
|
+
web = "pyboot_web.plugin:WebPlugin"
|
|
21
|
+
value = "pyboot_web.value:ValuePlugin"
|
|
22
|
+
|
|
23
|
+
[tool.setuptools.packages.find]
|
|
24
|
+
where = ["src"]
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
from typing import Callable
|
|
2
|
+
|
|
3
|
+
from starlette.requests import Request
|
|
4
|
+
from starlette.responses import JSONResponse
|
|
5
|
+
|
|
6
|
+
#Exception Resolver
|
|
7
|
+
class ExceptionResolver:
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
self.handlers: dict[
|
|
10
|
+
type[Exception], Callable[[Request, Exception], JSONResponse]
|
|
11
|
+
] = dict()
|
|
12
|
+
return
|
|
13
|
+
|
|
14
|
+
def register(
|
|
15
|
+
self,
|
|
16
|
+
exception_type: type[Exception],
|
|
17
|
+
handler: Callable[[Request, Exception], JSONResponse],
|
|
18
|
+
):
|
|
19
|
+
if exception_type in self.handlers:
|
|
20
|
+
raise TypeError(f"Handler already registered for {exception_type.__name__}")
|
|
21
|
+
|
|
22
|
+
self.handlers[exception_type] = handler
|
|
23
|
+
return
|
|
24
|
+
|
|
25
|
+
def resolve(self, request: Request, exception: Exception) -> JSONResponse:
|
|
26
|
+
for exception_class in type(exception).__mro__:
|
|
27
|
+
handler = self.handlers.get(exception_class)
|
|
28
|
+
|
|
29
|
+
if handler is not None:
|
|
30
|
+
return handler(request, exception)
|
|
31
|
+
|
|
32
|
+
raise exception
|
|
33
|
+
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
|
|
3
|
+
class WebAttr(Enum):
|
|
4
|
+
IS_REST_CONTROLLER="__is_rest_controller__"
|
|
5
|
+
REST_CONTROLLER_PATH="__rest_controller_path__"
|
|
6
|
+
|
|
7
|
+
IS_ROUTE="__is_route__"
|
|
8
|
+
ROUTE_PATH="__route_path__"
|
|
9
|
+
ROUTE_METHOD="__route_method__"
|
|
10
|
+
|
|
11
|
+
IS_MIDDLEWARE_COMPONENT="__is_middleware_component__"
|
|
12
|
+
MIDDLEWARE_LIST="__middleware_list__"
|
|
13
|
+
|
|
14
|
+
IS_WEB_SERVICE="__is_web_service__"
|
|
15
|
+
|
|
16
|
+
IS_REST_CONTROLLER_ADVICE="__is_rest_controller_advice__"
|
|
17
|
+
IS_EXCEPTION_HANDLER_METHOD="__is_exception_handler__"
|
|
18
|
+
EXCEPTION_HANDLER_FOR="__exception_handler_for__"
|
|
File without changes
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
from pyboot.core.types import ClassT
|
|
2
|
+
|
|
3
|
+
from .WebAttr import WebAttr
|
|
4
|
+
|
|
5
|
+
from .types import *
|
|
6
|
+
|
|
7
|
+
class RestControllerAdvice:
|
|
8
|
+
def __init__(self) -> None:
|
|
9
|
+
pass
|
|
10
|
+
|
|
11
|
+
def __call__(self,clazz:ClassT) -> ClassT:
|
|
12
|
+
setattr(clazz,WebAttr.IS_REST_CONTROLLER_ADVICE.value,True)
|
|
13
|
+
return clazz
|
|
14
|
+
|
|
15
|
+
class ExceptionHandler:
|
|
16
|
+
def __init__(self,exception_type:type[Exception]) -> None:
|
|
17
|
+
self.exception_type:type[Exception]=exception_type
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
def __call__(self,func:Callable[[Any,Request,E],JSONResponse]) -> Callable[[Any,Request,E],JSONResponse]:
|
|
21
|
+
setattr(func,WebAttr.IS_EXCEPTION_HANDLER_METHOD.value,True)
|
|
22
|
+
setattr(func,WebAttr.EXCEPTION_HANDLER_FOR.value,self.exception_type)
|
|
23
|
+
|
|
24
|
+
return func
|
|
25
|
+
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
from starlette.requests import Request
|
|
2
|
+
from starlette.responses import JSONResponse
|
|
3
|
+
|
|
4
|
+
from typing import Callable,Awaitable,overload
|
|
5
|
+
from abc import ABC,abstractmethod
|
|
6
|
+
|
|
7
|
+
from .WebAttr import WebAttr
|
|
8
|
+
|
|
9
|
+
from .rest_mapping import ControllerMethodT
|
|
10
|
+
|
|
11
|
+
from pyboot.core.types import ClassTBase,ClassT
|
|
12
|
+
|
|
13
|
+
CallNext=Callable[[Request],JSONResponse | Awaitable[JSONResponse]]
|
|
14
|
+
|
|
15
|
+
class MiddlewareBase(ABC):
|
|
16
|
+
@abstractmethod
|
|
17
|
+
def __call__(self,request:Request,call_next:CallNext) -> JSONResponse|Awaitable[JSONResponse]:
|
|
18
|
+
pass
|
|
19
|
+
|
|
20
|
+
class UseMiddleware:
|
|
21
|
+
def __init__(self,middlewares:list[type[MiddlewareBase]]) -> None:
|
|
22
|
+
self.middlewares:list[type[MiddlewareBase]]=middlewares
|
|
23
|
+
pass
|
|
24
|
+
|
|
25
|
+
@overload
|
|
26
|
+
def __call__(self,target:ClassTBase)->ClassTBase:
|
|
27
|
+
pass
|
|
28
|
+
|
|
29
|
+
@overload
|
|
30
|
+
def __call__(self,target:ControllerMethodT)->ControllerMethodT:
|
|
31
|
+
pass
|
|
32
|
+
|
|
33
|
+
def __call__(self, target:ClassTBase|ControllerMethodT) -> ClassTBase|ControllerMethodT:
|
|
34
|
+
setattr(target,WebAttr.MIDDLEWARE_LIST.value,self.middlewares)
|
|
35
|
+
return target
|
|
36
|
+
|
|
37
|
+
class MiddlewareComponent:
|
|
38
|
+
def __init__(self) -> None:
|
|
39
|
+
pass
|
|
40
|
+
|
|
41
|
+
def __call__(self,clazz:ClassT) -> ClassT:
|
|
42
|
+
setattr(clazz,WebAttr.IS_MIDDLEWARE_COMPONENT.value,True)
|
|
43
|
+
return clazz
|
|
44
|
+
|