fastapi-boot 3.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.
- fastapi_boot-3.0/LICENSE +21 -0
- fastapi_boot-3.0/PKG-INFO +111 -0
- fastapi_boot-3.0/README.md +95 -0
- fastapi_boot-3.0/fastapi_boot/DI.py +267 -0
- fastapi_boot-3.0/fastapi_boot/__init__.py +10 -0
- fastapi_boot-3.0/fastapi_boot/const.py +120 -0
- fastapi_boot-3.0/fastapi_boot/helper.py +263 -0
- fastapi_boot-3.0/fastapi_boot/model.py +253 -0
- fastapi_boot-3.0/fastapi_boot/routing.py +342 -0
- fastapi_boot-3.0/fastapi_boot/util.py +7 -0
- fastapi_boot-3.0/fastapi_boot.egg-info/PKG-INFO +111 -0
- fastapi_boot-3.0/fastapi_boot.egg-info/SOURCES.txt +15 -0
- fastapi_boot-3.0/fastapi_boot.egg-info/dependency_links.txt +1 -0
- fastapi_boot-3.0/fastapi_boot.egg-info/requires.txt +3 -0
- fastapi_boot-3.0/fastapi_boot.egg-info/top_level.txt +1 -0
- fastapi_boot-3.0/setup.cfg +4 -0
- fastapi_boot-3.0/setup.py +26 -0
fastapi_boot-3.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) [2024] [hfdy0935]
|
|
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,111 @@
|
|
|
1
|
+
Metadata-Version: 2.1
|
|
2
|
+
Name: fastapi_boot
|
|
3
|
+
Version: 3.0
|
|
4
|
+
Summary: FastAPI development toolkit
|
|
5
|
+
Home-page: https://github.com/pythonml/douyin_image
|
|
6
|
+
Author: hfdy0935
|
|
7
|
+
Author-email:
|
|
8
|
+
Classifier: Programming Language :: Python :: 3
|
|
9
|
+
Classifier: License :: OSI Approved :: MIT License
|
|
10
|
+
Classifier: Operating System :: OS Independent
|
|
11
|
+
Description-Content-Type: text/markdown
|
|
12
|
+
License-File: LICENSE
|
|
13
|
+
Requires-Dist: starlette<0.42.0,>=0.40.0
|
|
14
|
+
Requires-Dist: pydantic!=1.8,!=1.8.1,!=2.0.0,!=2.0.1,!=2.1.0,<3.0.0,>=1.7.4
|
|
15
|
+
Requires-Dist: typing-extensions>=4.8.0
|
|
16
|
+
|
|
17
|
+
# FastAPI Boot
|
|
18
|
+
|
|
19
|
+
  FastAPI Boot is a FastAPI development toolkit that aims to provide a **more convenient** and **declarative** approach to `routing`, `dependency` `injection`, `error handling`, and `middleware`.
|
|
20
|
+
|
|
21
|
+
## Features
|
|
22
|
+
|
|
23
|
+
- **Declarative Routing**: Simplify the way you define routes with a clear and intuitive syntax.
|
|
24
|
+
- **Dependency Injection**: Manage dependencies effortlessly with a robust DI system that simplifies your application's architecture.
|
|
25
|
+
- **Error Handling**: Built-in support for error handling that makes it easy to define and handle exceptions across your API.
|
|
26
|
+
- **Middleware Support**: Integrate middleware with ease to add pre-processing and post-processing steps to your requests and responses.
|
|
27
|
+
|
|
28
|
+
## Getting Started
|
|
29
|
+
|
|
30
|
+
```bash
|
|
31
|
+
pip install fatsapI_boot
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## USAGE
|
|
35
|
+
|
|
36
|
+
1. FBV(function based view)
|
|
37
|
+
|
|
38
|
+
```py
|
|
39
|
+
# controller
|
|
40
|
+
from fastapi_boot import Controller
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@Controller('/fbv', tags=['hello_world']).post('/foo')
|
|
44
|
+
def _():
|
|
45
|
+
return 'Hello World'
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
```py
|
|
49
|
+
# app.py
|
|
50
|
+
import uvicorn
|
|
51
|
+
from fastapi import FastAPI
|
|
52
|
+
from fastapi_boot import provide_app
|
|
53
|
+
|
|
54
|
+
app = FastAPI()
|
|
55
|
+
provide_app(app) # scans dir automatically
|
|
56
|
+
|
|
57
|
+
if __name__ == '__main__':
|
|
58
|
+
uvicorn.run('main:app', reload=True)
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
2. CBV(class based view)
|
|
62
|
+
|
|
63
|
+
```py
|
|
64
|
+
from dataclasses import asdict, dataclass
|
|
65
|
+
from pydantic import BaseModel
|
|
66
|
+
from fastapi_boot import Controller, Delete, Get, Post, Put, Req
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Baz(BaseModel):
|
|
70
|
+
baz: str
|
|
71
|
+
|
|
72
|
+
@dataclass
|
|
73
|
+
class Baz1:
|
|
74
|
+
baz1: str
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
@Controller('/base-cbv', tags=['2. base cbv'])
|
|
78
|
+
class FirstController:
|
|
79
|
+
|
|
80
|
+
@Req('/f', methods=['GET'])
|
|
81
|
+
def f():
|
|
82
|
+
return True
|
|
83
|
+
|
|
84
|
+
@Get('/foo')
|
|
85
|
+
def get_foo(self):
|
|
86
|
+
return BaseResp(code=200, msg='success', data='foo')
|
|
87
|
+
|
|
88
|
+
@Post('/bar')
|
|
89
|
+
def post_bar(self, p: str = Query()):
|
|
90
|
+
return p
|
|
91
|
+
|
|
92
|
+
@Put('/baz')
|
|
93
|
+
def put_baz(self, baz: Baz, baz1: Baz1):
|
|
94
|
+
return dict(**baz.model_dump(), **asdict(baz1))
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
click <a href='https://github.com/hfdy0935/fastapi_boot/tree/main/exmaples' target='_blank'>here</a> to get more examples.
|
|
98
|
+
|
|
99
|
+
## APIS
|
|
100
|
+
|
|
101
|
+
```py
|
|
102
|
+
from fastapi_boot.DI import Bean
|
|
103
|
+
from fastapi_boot.DI import Inject
|
|
104
|
+
from fastapi_boot.DI import Injectable
|
|
105
|
+
from fastapi_boot.DI import Component
|
|
106
|
+
from fastapi_boot.DI import Repository
|
|
107
|
+
from fastapi_boot.DI import Service
|
|
108
|
+
from fastapi_boot.helper import ExceptionHandler, OnAppProvided, provide_app, use_dep, use_http_middleware, use_ws_middleware
|
|
109
|
+
from fastapi_boot.routing import Controller, Delete, Get, Head, Options, Patch, Post, Prefix, Put, Req, Trace
|
|
110
|
+
from fastapi_boot.routing import Socket
|
|
111
|
+
```
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
# FastAPI Boot
|
|
2
|
+
|
|
3
|
+
  FastAPI Boot is a FastAPI development toolkit that aims to provide a **more convenient** and **declarative** approach to `routing`, `dependency` `injection`, `error handling`, and `middleware`.
|
|
4
|
+
|
|
5
|
+
## Features
|
|
6
|
+
|
|
7
|
+
- **Declarative Routing**: Simplify the way you define routes with a clear and intuitive syntax.
|
|
8
|
+
- **Dependency Injection**: Manage dependencies effortlessly with a robust DI system that simplifies your application's architecture.
|
|
9
|
+
- **Error Handling**: Built-in support for error handling that makes it easy to define and handle exceptions across your API.
|
|
10
|
+
- **Middleware Support**: Integrate middleware with ease to add pre-processing and post-processing steps to your requests and responses.
|
|
11
|
+
|
|
12
|
+
## Getting Started
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
pip install fatsapI_boot
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
## USAGE
|
|
19
|
+
|
|
20
|
+
1. FBV(function based view)
|
|
21
|
+
|
|
22
|
+
```py
|
|
23
|
+
# controller
|
|
24
|
+
from fastapi_boot import Controller
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
@Controller('/fbv', tags=['hello_world']).post('/foo')
|
|
28
|
+
def _():
|
|
29
|
+
return 'Hello World'
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
```py
|
|
33
|
+
# app.py
|
|
34
|
+
import uvicorn
|
|
35
|
+
from fastapi import FastAPI
|
|
36
|
+
from fastapi_boot import provide_app
|
|
37
|
+
|
|
38
|
+
app = FastAPI()
|
|
39
|
+
provide_app(app) # scans dir automatically
|
|
40
|
+
|
|
41
|
+
if __name__ == '__main__':
|
|
42
|
+
uvicorn.run('main:app', reload=True)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
2. CBV(class based view)
|
|
46
|
+
|
|
47
|
+
```py
|
|
48
|
+
from dataclasses import asdict, dataclass
|
|
49
|
+
from pydantic import BaseModel
|
|
50
|
+
from fastapi_boot import Controller, Delete, Get, Post, Put, Req
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
class Baz(BaseModel):
|
|
54
|
+
baz: str
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class Baz1:
|
|
58
|
+
baz1: str
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@Controller('/base-cbv', tags=['2. base cbv'])
|
|
62
|
+
class FirstController:
|
|
63
|
+
|
|
64
|
+
@Req('/f', methods=['GET'])
|
|
65
|
+
def f():
|
|
66
|
+
return True
|
|
67
|
+
|
|
68
|
+
@Get('/foo')
|
|
69
|
+
def get_foo(self):
|
|
70
|
+
return BaseResp(code=200, msg='success', data='foo')
|
|
71
|
+
|
|
72
|
+
@Post('/bar')
|
|
73
|
+
def post_bar(self, p: str = Query()):
|
|
74
|
+
return p
|
|
75
|
+
|
|
76
|
+
@Put('/baz')
|
|
77
|
+
def put_baz(self, baz: Baz, baz1: Baz1):
|
|
78
|
+
return dict(**baz.model_dump(), **asdict(baz1))
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
click <a href='https://github.com/hfdy0935/fastapi_boot/tree/main/exmaples' target='_blank'>here</a> to get more examples.
|
|
82
|
+
|
|
83
|
+
## APIS
|
|
84
|
+
|
|
85
|
+
```py
|
|
86
|
+
from fastapi_boot.DI import Bean
|
|
87
|
+
from fastapi_boot.DI import Inject
|
|
88
|
+
from fastapi_boot.DI import Injectable
|
|
89
|
+
from fastapi_boot.DI import Component
|
|
90
|
+
from fastapi_boot.DI import Repository
|
|
91
|
+
from fastapi_boot.DI import Service
|
|
92
|
+
from fastapi_boot.helper import ExceptionHandler, OnAppProvided, provide_app, use_dep, use_http_middleware, use_ws_middleware
|
|
93
|
+
from fastapi_boot.routing import Controller, Delete, Get, Head, Options, Patch, Post, Prefix, Put, Req, Trace
|
|
94
|
+
from fastapi_boot.routing import Socket
|
|
95
|
+
```
|
|
@@ -0,0 +1,267 @@
|
|
|
1
|
+
import time
|
|
2
|
+
from collections.abc import Callable
|
|
3
|
+
from inspect import Parameter, _empty, signature,isclass
|
|
4
|
+
from typing import Annotated, Generic, TypeVar, get_args, get_origin, no_type_check, overload
|
|
5
|
+
|
|
6
|
+
from fastapi_boot.const import NameDepRecord, TypeDepRecord, app_store, dep_store
|
|
7
|
+
from fastapi_boot.model import AppRecord, DependencyNotFoundException, InjectFailException
|
|
8
|
+
from fastapi_boot.util import get_call_filename
|
|
9
|
+
|
|
10
|
+
T = TypeVar('T')
|
|
11
|
+
|
|
12
|
+
# ------------------------------------------------------- public ------------------------------------------------------ #
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def _inject(app_record: AppRecord, tp: type[T], name: str | None = None) -> T:
|
|
16
|
+
"""inject dependency by type or name
|
|
17
|
+
|
|
18
|
+
Args:
|
|
19
|
+
app_record (AppRecord)
|
|
20
|
+
tp (type[T])
|
|
21
|
+
name (str | None)
|
|
22
|
+
|
|
23
|
+
Returns:
|
|
24
|
+
T: instance
|
|
25
|
+
"""
|
|
26
|
+
start = time.time()
|
|
27
|
+
while True:
|
|
28
|
+
if res := dep_store.inject_by_type(tp) if name is None else dep_store.inject_by_name(name, tp):
|
|
29
|
+
return res
|
|
30
|
+
time.sleep(app_record.inject_retry_step)
|
|
31
|
+
if time.time() - start > app_record.inject_timeout:
|
|
32
|
+
raise DependencyNotFoundException(f'Dependency "{tp}" not found')
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
def inject_params_deps(app_record: AppRecord, params: list[Parameter]):
|
|
36
|
+
"""find dependencies of params
|
|
37
|
+
Args:
|
|
38
|
+
app_record (AppRecord)
|
|
39
|
+
params (list[Parameter]): param list without self
|
|
40
|
+
"""
|
|
41
|
+
params_dict = {}
|
|
42
|
+
for param in params:
|
|
43
|
+
# 1. with default
|
|
44
|
+
if param.default != _empty:
|
|
45
|
+
params_dict.update({param.name: param.default})
|
|
46
|
+
else:
|
|
47
|
+
# 2. no default
|
|
48
|
+
if param.annotation == _empty:
|
|
49
|
+
# 2.1 not annotation
|
|
50
|
+
raise InjectFailException(
|
|
51
|
+
f'The annotation of param {param.name} is missing, add an annotation or give it a default value'
|
|
52
|
+
)
|
|
53
|
+
# 2.2. with annotation
|
|
54
|
+
if get_origin(param.annotation) == Annotated:
|
|
55
|
+
# 2.2.1 Annotated
|
|
56
|
+
tp, name, *_ = get_args(param.annotation)
|
|
57
|
+
if not isinstance(name, str):
|
|
58
|
+
# 2.2.1.1 name is not str
|
|
59
|
+
params_dict.update({param.name: _inject(app_record, tp)})
|
|
60
|
+
else:
|
|
61
|
+
# 2.2.1.2 name is str, as dependency's name to _inject
|
|
62
|
+
params_dict.update({param.name: _inject(app_record, tp, name)})
|
|
63
|
+
else:
|
|
64
|
+
# 2.2.2 other
|
|
65
|
+
params_dict.update({param.name: _inject(app_record, param.annotation)})
|
|
66
|
+
return params_dict
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
# ------------------------------------------------------- Bean ------------------------------------------------------- #
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
def collect_bean(app_record: AppRecord, func: Callable, name: str | None = None):
|
|
73
|
+
"""
|
|
74
|
+
1. run function decorated by Bean decorator
|
|
75
|
+
2. add the result to deps_store
|
|
76
|
+
|
|
77
|
+
Args:
|
|
78
|
+
app_record (AppRecord)
|
|
79
|
+
func (Callable): func
|
|
80
|
+
name (str | None, optional): name of dep
|
|
81
|
+
"""
|
|
82
|
+
params: list[Parameter] = list(signature(func).parameters.values())
|
|
83
|
+
return_annotations = signature(func).return_annotation
|
|
84
|
+
instance = func(**inject_params_deps(app_record, params))
|
|
85
|
+
tp = return_annotations if return_annotations != _empty else type(instance)
|
|
86
|
+
if name is None:
|
|
87
|
+
dep_store.add_dep_by_type(TypeDepRecord(tp, instance))
|
|
88
|
+
else:
|
|
89
|
+
dep_store.add_dep_by_name(NameDepRecord(tp, instance, name))
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
@overload
|
|
93
|
+
def Bean(value: str): ...
|
|
94
|
+
@overload
|
|
95
|
+
def Bean(value: Callable[..., T]): ...
|
|
96
|
+
@no_type_check
|
|
97
|
+
def Bean(value: str | Callable[..., T]) -> Callable[..., T]:
|
|
98
|
+
"""A decorator, will collect the return value of the func decorated by Bean
|
|
99
|
+
# Example
|
|
100
|
+
1. collect by `type`
|
|
101
|
+
```python
|
|
102
|
+
@dataclass
|
|
103
|
+
class Foo:
|
|
104
|
+
bar: str
|
|
105
|
+
|
|
106
|
+
@Bean
|
|
107
|
+
def _():
|
|
108
|
+
return Foo('baz')
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
2. collect by `name`
|
|
112
|
+
```python
|
|
113
|
+
class User(BaseModel):
|
|
114
|
+
name: str = Field(max_length=20)
|
|
115
|
+
age: int = Field(gt=0)
|
|
116
|
+
|
|
117
|
+
@Bean('user1')
|
|
118
|
+
def _():
|
|
119
|
+
return User(name='zs', age=20)
|
|
120
|
+
|
|
121
|
+
@Bean('user2)
|
|
122
|
+
def _():
|
|
123
|
+
return User(name='zs', age=21)
|
|
124
|
+
```
|
|
125
|
+
"""
|
|
126
|
+
app_record = app_store.get(get_call_filename())
|
|
127
|
+
|
|
128
|
+
if callable(value):
|
|
129
|
+
collect_bean(app_record, value)
|
|
130
|
+
return value
|
|
131
|
+
else:
|
|
132
|
+
|
|
133
|
+
def wrapper(func: Callable[..., T]) -> Callable[..., T]:
|
|
134
|
+
collect_bean(app_record, func, value)
|
|
135
|
+
return func
|
|
136
|
+
|
|
137
|
+
return wrapper
|
|
138
|
+
|
|
139
|
+
|
|
140
|
+
# ---------------------------------------------------- Injectable ---------------------------------------------------- #
|
|
141
|
+
def inject_init_deps_and_get_instance(app_record: AppRecord, cls: type[T]) -> T:
|
|
142
|
+
"""_inject cls's __init__ params and get params deps"""
|
|
143
|
+
old_params = list(signature(cls.__init__).parameters.values())[1:] # self
|
|
144
|
+
new_params = [
|
|
145
|
+
i for i in old_params if i.kind not in (Parameter.VAR_KEYWORD, Parameter.VAR_POSITIONAL)
|
|
146
|
+
] # *args、**kwargs
|
|
147
|
+
return cls(**inject_params_deps(app_record, new_params))
|
|
148
|
+
|
|
149
|
+
|
|
150
|
+
def collect_dep(app_record: AppRecord, cls: type, name: str | None = None):
|
|
151
|
+
"""init class decorated by Inject decorator and collect it's instance as dependency"""
|
|
152
|
+
if hasattr(cls.__init__, '__globals__'):
|
|
153
|
+
cls.__init__.__globals__[cls.__name__] = cls # avoid error when getting cls in __init__ method
|
|
154
|
+
instance = inject_init_deps_and_get_instance(app_record, cls)
|
|
155
|
+
if name is None:
|
|
156
|
+
dep_store.add_dep_by_type(TypeDepRecord(cls, instance))
|
|
157
|
+
else:
|
|
158
|
+
dep_store.add_dep_by_name(NameDepRecord(cls, instance, name))
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
@overload
|
|
162
|
+
def Injectable(value: str): ...
|
|
163
|
+
@overload
|
|
164
|
+
def Injectable(value: type[T]): ...
|
|
165
|
+
|
|
166
|
+
|
|
167
|
+
@no_type_check
|
|
168
|
+
def Injectable(value: str | type[T]) -> type[T]:
|
|
169
|
+
"""decorate a class and collect it's instance as a dependency
|
|
170
|
+
# Example
|
|
171
|
+
```python
|
|
172
|
+
@Injectable
|
|
173
|
+
class Foo:...
|
|
174
|
+
|
|
175
|
+
@Injectable('bar1')
|
|
176
|
+
class Bar:...
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
"""
|
|
180
|
+
app_record = app_store.get((get_call_filename()))
|
|
181
|
+
if isclass(value):
|
|
182
|
+
collect_dep(app_record, value)
|
|
183
|
+
return value
|
|
184
|
+
else:
|
|
185
|
+
|
|
186
|
+
def wrapper(cls: type[T]):
|
|
187
|
+
collect_dep(app_record, cls, value)
|
|
188
|
+
return cls
|
|
189
|
+
|
|
190
|
+
return wrapper
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
# ------------------------------------------------------ Inject ------------------------------------------------------ #
|
|
194
|
+
class AtUsable(type):
|
|
195
|
+
"""support @"""
|
|
196
|
+
|
|
197
|
+
def __matmul__(self: type['Inject'], other: type[T]) -> T:
|
|
198
|
+
filename = get_call_filename()
|
|
199
|
+
app_record = app_store.get(filename)
|
|
200
|
+
return _inject(app_record, other, self.latest_named_deps_record.get(filename))
|
|
201
|
+
|
|
202
|
+
def __rmatmul__(self: type['Inject'], other: type[T]) -> T:
|
|
203
|
+
filename = get_call_filename()
|
|
204
|
+
app_record = app_store.get(filename)
|
|
205
|
+
return _inject(app_record, other, self.latest_named_deps_record.get(filename))
|
|
206
|
+
|
|
207
|
+
|
|
208
|
+
class Inject(Generic[T], metaclass=AtUsable):
|
|
209
|
+
"""inject dependency anywhere
|
|
210
|
+
# Example
|
|
211
|
+
- inject by **type**
|
|
212
|
+
```python
|
|
213
|
+
a = Inject(Foo)
|
|
214
|
+
b = Inject @ Foo
|
|
215
|
+
c = Foo @ Inject
|
|
216
|
+
|
|
217
|
+
@Injectable
|
|
218
|
+
class Bar:
|
|
219
|
+
a = Inject(Foo)
|
|
220
|
+
b = Inject @ Foo
|
|
221
|
+
c = Foo @ Inject
|
|
222
|
+
|
|
223
|
+
def __init__(self,ia: Foo, ic: Foo):
|
|
224
|
+
self.ia = ia
|
|
225
|
+
self.ib = Inject @ Foo
|
|
226
|
+
self.ic = ic
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
- inject by **name**
|
|
230
|
+
```python
|
|
231
|
+
a = Inject(Foo, 'foo1')
|
|
232
|
+
b = Inject.Qualifier('foo2') @ Foo
|
|
233
|
+
c = Foo @ Inject.Qualifier('foo3')
|
|
234
|
+
|
|
235
|
+
@Injectable
|
|
236
|
+
class Bar:
|
|
237
|
+
a = Inject(Foo, 'foo1')
|
|
238
|
+
b = Inject.Qualifier('foo2') @ Foo
|
|
239
|
+
c = Foo @ Inject.Qualifier('foo3')
|
|
240
|
+
|
|
241
|
+
def __init__(self,ia: Annotated[Foo, 'foo1'], ic: Annotated[Foo, 'foo3']):
|
|
242
|
+
self.ia = ia
|
|
243
|
+
self.ib = Inject.Qualifier('foo2') @ Foo
|
|
244
|
+
self.ic = ic
|
|
245
|
+
```
|
|
246
|
+
"""
|
|
247
|
+
|
|
248
|
+
latest_named_deps_record: dict[str, str | None] = {}
|
|
249
|
+
|
|
250
|
+
def __new__(cls, tp: type[T], name: str | None = None) -> T:
|
|
251
|
+
"""Inject(Type, name = None)"""
|
|
252
|
+
filename = get_call_filename()
|
|
253
|
+
cls.latest_named_deps_record.update({filename: name})
|
|
254
|
+
app_record = app_store.get(filename)
|
|
255
|
+
res = _inject(app_record, tp, name)
|
|
256
|
+
cls.latest_named_deps_record.update({filename: None}) # set name None
|
|
257
|
+
return res
|
|
258
|
+
|
|
259
|
+
@classmethod
|
|
260
|
+
def Qualifier(cls, name: str) -> type['Inject']:
|
|
261
|
+
"""Inject.Qualifier(name)"""
|
|
262
|
+
filename = get_call_filename()
|
|
263
|
+
|
|
264
|
+
class Cls(cls):
|
|
265
|
+
latest_named_deps_record: dict[str, str] = {filename: name}
|
|
266
|
+
|
|
267
|
+
return Cls
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
from .DI import Bean
|
|
2
|
+
from .DI import Inject
|
|
3
|
+
from .DI import Inject as Autowired
|
|
4
|
+
from .DI import Injectable
|
|
5
|
+
from .DI import Injectable as Component
|
|
6
|
+
from .DI import Injectable as Repository
|
|
7
|
+
from .DI import Injectable as Service
|
|
8
|
+
from .helper import ExceptionHandler, OnAppProvided, provide_app, use_dep, use_http_middleware,use_ws_middleware
|
|
9
|
+
from .routing import Controller, Delete, Get, Head, Options, Patch, Post, Prefix, Put, Req, Trace
|
|
10
|
+
from .routing import WebSocket as Socket
|
|
@@ -0,0 +1,120 @@
|
|
|
1
|
+
from collections.abc import Callable
|
|
2
|
+
from dataclasses import dataclass
|
|
3
|
+
from typing import Generic, TypeVar
|
|
4
|
+
|
|
5
|
+
from fastapi import FastAPI
|
|
6
|
+
|
|
7
|
+
from fastapi_boot.model import AppNotFoundException, AppRecord, DependencyDuplicatedException
|
|
8
|
+
|
|
9
|
+
T = TypeVar('T')
|
|
10
|
+
|
|
11
|
+
# ---------------------------------------------------- constant ---------------------------------------------------- #
|
|
12
|
+
# use_dep placeholder
|
|
13
|
+
REQ_DEP_PLACEHOLDER = "fastapi_boot___dependency_placeholder"
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
# route record's key in controller
|
|
17
|
+
CONTROLLER_ROUTE_RECORD = "fastapi_boot___controller_route_record"
|
|
18
|
+
|
|
19
|
+
# prefix of use_dep params in endpoint
|
|
20
|
+
USE_DEP_PREFIX_IN_ENDPOINT = 'fastapi_boot__use_dep_prefix'
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
# use_middleware placeholder
|
|
24
|
+
USE_MIDDLEWARE_FIELD_PLACEHOLDER = 'fastapi_boot__use_middleware_field_placeholder'
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class BlankPlaceholder: ...
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
# PRIORITY OF EXCEPTION_HANDLER
|
|
31
|
+
EXCEPTION_HANDLER_PRIORITY = 1
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
# ------------------------------------------------------- store ------------------------------------------------------ #
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class TypeDepRecord(Generic[T]):
|
|
39
|
+
tp: type[T]
|
|
40
|
+
value: T
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
@dataclass
|
|
44
|
+
class NameDepRecord(Generic[T], TypeDepRecord[T]):
|
|
45
|
+
name: str
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class DependencyStore(Generic[T]):
|
|
49
|
+
def __init__(self):
|
|
50
|
+
self.type_deps: dict[type[T], TypeDepRecord[T]] = {}
|
|
51
|
+
self.name_deps: dict[str, NameDepRecord[T]] = {}
|
|
52
|
+
|
|
53
|
+
def add_dep_by_type(self, dep: TypeDepRecord[T]):
|
|
54
|
+
if dep.tp in self.type_deps:
|
|
55
|
+
raise DependencyDuplicatedException(f'Dependency {dep.tp} duplicated')
|
|
56
|
+
self.type_deps.update({dep.tp: dep})
|
|
57
|
+
|
|
58
|
+
def add_dep_by_name(self, dep: NameDepRecord[T]):
|
|
59
|
+
if self.name_deps.get(dep.name):
|
|
60
|
+
raise DependencyDuplicatedException(f'Dependency name {dep.name} duplicated')
|
|
61
|
+
self.name_deps.update({dep.name: dep})
|
|
62
|
+
|
|
63
|
+
def inject_by_type(self, tp: type[T]) -> T | None:
|
|
64
|
+
if res := self.type_deps.get(tp):
|
|
65
|
+
return res.value
|
|
66
|
+
|
|
67
|
+
def inject_by_name(self, name: str, tp: type[T]) -> T | None:
|
|
68
|
+
if find := self.name_deps.get(name):
|
|
69
|
+
if find.tp == tp:
|
|
70
|
+
return find.value
|
|
71
|
+
|
|
72
|
+
def clear(self):
|
|
73
|
+
self.type_deps.clear()
|
|
74
|
+
self.name_deps.clear()
|
|
75
|
+
|
|
76
|
+
|
|
77
|
+
class AppStore(Generic[T]):
|
|
78
|
+
def __init__(self):
|
|
79
|
+
self.app_dic: dict[str, AppRecord] = {}
|
|
80
|
+
|
|
81
|
+
def add(self, path: str, app_record: AppRecord):
|
|
82
|
+
self.app_dic.update({path: app_record})
|
|
83
|
+
# app_record.app.add_event_handler()
|
|
84
|
+
|
|
85
|
+
def get(self, path: str) -> AppRecord:
|
|
86
|
+
path = path[0].upper() + path[1:]
|
|
87
|
+
for k, v in self.app_dic.items():
|
|
88
|
+
if path.startswith(k):
|
|
89
|
+
return v
|
|
90
|
+
raise AppNotFoundException(f'Can"t find app of "{path}"')
|
|
91
|
+
|
|
92
|
+
def clear(self):
|
|
93
|
+
self.app_dic.clear()
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
class TasStore:
|
|
97
|
+
def __init__(self):
|
|
98
|
+
# will be called after the app becomes available
|
|
99
|
+
self.late_tasks: dict[str, list[tuple[Callable[[FastAPI], None], int]]] = {}
|
|
100
|
+
|
|
101
|
+
def add_late_task(self, path: str, task: Callable[[FastAPI], None], priority: int):
|
|
102
|
+
if curr_tasks := self.late_tasks.get(path):
|
|
103
|
+
self.late_tasks.update({path: [*curr_tasks, (task, priority)]})
|
|
104
|
+
else:
|
|
105
|
+
self.late_tasks.update({path: [(task, priority)]})
|
|
106
|
+
|
|
107
|
+
def run_late_tasks(self):
|
|
108
|
+
for path, late_tasks in self.late_tasks.items():
|
|
109
|
+
app = app_store.get(path).app
|
|
110
|
+
late_tasks.sort(key=lambda x: x[1], reverse=True)
|
|
111
|
+
for record in late_tasks:
|
|
112
|
+
record[0](app)
|
|
113
|
+
|
|
114
|
+
def clear(self):
|
|
115
|
+
self.late_tasks.clear()
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
dep_store = DependencyStore()
|
|
119
|
+
app_store = AppStore()
|
|
120
|
+
task_store = TasStore()
|