fastapi-boot 0.0.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -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,131 @@
1
+ Metadata-Version: 2.1
2
+ Name: fastapi_boot
3
+ Version: 0.0.1
4
+ Summary: FastAPI development toolkit
5
+ Author: hfdy0935
6
+ Author-email:
7
+ Project-URL: Repository, https://github.com/hfdy0935/fastapi_boot
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
+ &emsp;&emsp;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
+ # FooController.py
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
+ # main.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 import (
103
+ Bean,
104
+ Inject,
105
+ Injectable,
106
+ ExceptionHandler,
107
+ OnAppProvided,
108
+ provide_app,
109
+ use_dep,
110
+ use_http_middleware,
111
+ use_ws_middleware,
112
+ HTTPMiddleware,
113
+ Controller,
114
+ Delete,
115
+ Get,
116
+ Head,
117
+ Options,
118
+ Patch,
119
+ Post,
120
+ Prefix,
121
+ Put,
122
+ Req,
123
+ Trace,
124
+ WS,
125
+ Autowired,
126
+ Component,
127
+ Repository,
128
+ Service,
129
+ )
130
+
131
+ ```
@@ -0,0 +1,115 @@
1
+ # FastAPI Boot
2
+
3
+ &emsp;&emsp;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
+ # FooController.py
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
+ # main.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 import (
87
+ Bean,
88
+ Inject,
89
+ Injectable,
90
+ ExceptionHandler,
91
+ OnAppProvided,
92
+ provide_app,
93
+ use_dep,
94
+ use_http_middleware,
95
+ use_ws_middleware,
96
+ HTTPMiddleware,
97
+ Controller,
98
+ Delete,
99
+ Get,
100
+ Head,
101
+ Options,
102
+ Patch,
103
+ Post,
104
+ Prefix,
105
+ Put,
106
+ Req,
107
+ Trace,
108
+ WS,
109
+ Autowired,
110
+ Component,
111
+ Repository,
112
+ Service,
113
+ )
114
+
115
+ ```
@@ -0,0 +1,268 @@
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
+ name_info=f"with name '{name}'" if name is not None else ''
33
+ raise DependencyNotFoundException(f"Dependency '{tp}' {name_info} not found")
34
+
35
+
36
+ def inject_params_deps(app_record: AppRecord, params: list[Parameter]):
37
+ """find dependencies of params
38
+ Args:
39
+ app_record (AppRecord)
40
+ params (list[Parameter]): param list without self
41
+ """
42
+ params_dict = {}
43
+ for param in params:
44
+ # 1. with default
45
+ if param.default != _empty:
46
+ params_dict.update({param.name: param.default})
47
+ else:
48
+ # 2. no default
49
+ if param.annotation == _empty:
50
+ # 2.1 not annotation
51
+ raise InjectFailException(
52
+ f'The annotation of param {param.name} is missing, add an annotation or give it a default value'
53
+ )
54
+ # 2.2. with annotation
55
+ if get_origin(param.annotation) == Annotated:
56
+ # 2.2.1 Annotated
57
+ tp, name, *_ = get_args(param.annotation)
58
+ if isinstance(name, str):
59
+ # 2.2.1.1 name is str, as dependency's name to _inject
60
+ params_dict.update({param.name: _inject(app_record, tp, name)})
61
+ else:
62
+ # 2.2.1.2 name is not str
63
+ params_dict.update({param.name: _inject(app_record, tp)})
64
+ else:
65
+ # 2.2.2 other
66
+ params_dict.update({param.name: _inject(app_record, param.annotation)})
67
+ return params_dict
68
+
69
+
70
+ # ------------------------------------------------------- Bean ------------------------------------------------------- #
71
+
72
+
73
+ def collect_bean(app_record: AppRecord, func: Callable, name: str | None = None):
74
+ """
75
+ 1. run function decorated by Bean decorator
76
+ 2. add the result to deps_store
77
+
78
+ Args:
79
+ app_record (AppRecord)
80
+ func (Callable): func
81
+ name (str | None, optional): name of dep
82
+ """
83
+ params: list[Parameter] = list(signature(func).parameters.values())
84
+ return_annotations = signature(func).return_annotation
85
+ instance = func(**inject_params_deps(app_record, params))
86
+ tp = return_annotations if return_annotations != _empty else type(instance)
87
+ if name is None:
88
+ dep_store.add_dep_by_type(TypeDepRecord(tp, instance))
89
+ else:
90
+ dep_store.add_dep_by_name(NameDepRecord(tp, instance, name))
91
+
92
+
93
+ @overload
94
+ def Bean(value: str): ...
95
+ @overload
96
+ def Bean(value: Callable[..., T]): ...
97
+ @no_type_check
98
+ def Bean(value: str | Callable[..., T]) -> Callable[..., T]:
99
+ """A decorator, will collect the return value of the func decorated by Bean
100
+ # Example
101
+ 1. collect by `type`
102
+ ```python
103
+ @dataclass
104
+ class Foo:
105
+ bar: str
106
+
107
+ @Bean
108
+ def _():
109
+ return Foo('baz')
110
+ ```
111
+
112
+ 2. collect by `name`
113
+ ```python
114
+ class User(BaseModel):
115
+ name: str = Field(max_length=20)
116
+ age: int = Field(gt=0)
117
+
118
+ @Bean('user1')
119
+ def _():
120
+ return User(name='zs', age=20)
121
+
122
+ @Bean('user2)
123
+ def _():
124
+ return User(name='zs', age=21)
125
+ ```
126
+ """
127
+ app_record = app_store.get(get_call_filename())
128
+
129
+ if callable(value):
130
+ collect_bean(app_record, value)
131
+ return value
132
+ else:
133
+
134
+ def wrapper(func: Callable[..., T]) -> Callable[..., T]:
135
+ collect_bean(app_record, func, value)
136
+ return func
137
+
138
+ return wrapper
139
+
140
+
141
+ # ---------------------------------------------------- Injectable ---------------------------------------------------- #
142
+ def inject_init_deps_and_get_instance(app_record: AppRecord, cls: type[T]) -> T:
143
+ """_inject cls's __init__ params and get params deps"""
144
+ old_params = list(signature(cls.__init__).parameters.values())[1:] # self
145
+ new_params = [
146
+ i for i in old_params if i.kind not in (Parameter.VAR_KEYWORD, Parameter.VAR_POSITIONAL)
147
+ ] # *args、**kwargs
148
+ return cls(**inject_params_deps(app_record, new_params))
149
+
150
+
151
+ def collect_dep(app_record: AppRecord, cls: type, name: str | None = None):
152
+ """init class decorated by Inject decorator and collect it's instance as dependency"""
153
+ if hasattr(cls.__init__, '__globals__'):
154
+ cls.__init__.__globals__[cls.__name__] = cls # avoid error when getting cls in __init__ method
155
+ instance = inject_init_deps_and_get_instance(app_record, cls)
156
+ if name is None:
157
+ dep_store.add_dep_by_type(TypeDepRecord(cls, instance))
158
+ else:
159
+ dep_store.add_dep_by_name(NameDepRecord(cls, instance, name))
160
+
161
+
162
+ @overload
163
+ def Injectable(value: str): ...
164
+ @overload
165
+ def Injectable(value: type[T]): ...
166
+
167
+
168
+ @no_type_check
169
+ def Injectable(value: str | type[T]) -> type[T]:
170
+ """decorate a class and collect it's instance as a dependency
171
+ # Example
172
+ ```python
173
+ @Injectable
174
+ class Foo:...
175
+
176
+ @Injectable('bar1')
177
+ class Bar:...
178
+ ```
179
+
180
+ """
181
+ app_record = app_store.get((get_call_filename()))
182
+ if isclass(value):
183
+ collect_dep(app_record, value)
184
+ return value
185
+ else:
186
+
187
+ def wrapper(cls: type[T]):
188
+ collect_dep(app_record, cls, value)
189
+ return cls
190
+
191
+ return wrapper
192
+
193
+
194
+ # ------------------------------------------------------ Inject ------------------------------------------------------ #
195
+ class AtUsable(type):
196
+ """support @"""
197
+
198
+ def __matmul__(self: type['Inject'], other: type[T]) -> T:
199
+ filename = get_call_filename()
200
+ app_record = app_store.get(filename)
201
+ return _inject(app_record, other, self.latest_named_deps_record.get(filename))
202
+
203
+ def __rmatmul__(self: type['Inject'], other: type[T]) -> T:
204
+ filename = get_call_filename()
205
+ app_record = app_store.get(filename)
206
+ return _inject(app_record, other, self.latest_named_deps_record.get(filename))
207
+
208
+
209
+ class Inject(Generic[T], metaclass=AtUsable):
210
+ """inject dependency anywhere
211
+ # Example
212
+ - inject by **type**
213
+ ```python
214
+ a = Inject(Foo)
215
+ b = Inject @ Foo
216
+ c = Foo @ Inject
217
+
218
+ @Injectable
219
+ class Bar:
220
+ a = Inject(Foo)
221
+ b = Inject @ Foo
222
+ c = Foo @ Inject
223
+
224
+ def __init__(self,ia: Foo, ic: Foo):
225
+ self.ia = ia
226
+ self.ib = Inject @ Foo
227
+ self.ic = ic
228
+ ```
229
+
230
+ - inject by **name**
231
+ ```python
232
+ a = Inject(Foo, 'foo1')
233
+ b = Inject.Qualifier('foo2') @ Foo
234
+ c = Foo @ Inject.Qualifier('foo3')
235
+
236
+ @Injectable
237
+ class Bar:
238
+ a = Inject(Foo, 'foo1')
239
+ b = Inject.Qualifier('foo2') @ Foo
240
+ c = Foo @ Inject.Qualifier('foo3')
241
+
242
+ def __init__(self,ia: Annotated[Foo, 'foo1'], ic: Annotated[Foo, 'foo3']):
243
+ self.ia = ia
244
+ self.ib = Inject.Qualifier('foo2') @ Foo
245
+ self.ic = ic
246
+ ```
247
+ """
248
+
249
+ latest_named_deps_record: dict[str, str | None] = {}
250
+
251
+ def __new__(cls, tp: type[T], name: str | None = None) -> T:
252
+ """Inject(Type, name = None)"""
253
+ filename = get_call_filename()
254
+ cls.latest_named_deps_record.update({filename: name})
255
+ app_record = app_store.get(filename)
256
+ res = _inject(app_record, tp, name)
257
+ cls.latest_named_deps_record.update({filename: None}) # set name None
258
+ return res
259
+
260
+ @classmethod
261
+ def Qualifier(cls, name: str) -> type['Inject']:
262
+ """Inject.Qualifier(name)"""
263
+ filename = get_call_filename()
264
+
265
+ class Cls(cls):
266
+ latest_named_deps_record: dict[str, str] = {filename: name}
267
+
268
+ return Cls
@@ -0,0 +1,18 @@
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 (
9
+ ExceptionHandler,
10
+ OnAppProvided,
11
+ provide_app,
12
+ use_dep,
13
+ use_http_middleware,
14
+ use_ws_middleware,
15
+ HTTPMiddleware,
16
+ )
17
+ from .routing import Controller, Delete, Get, Head, Options, Patch, Post, Prefix, Put, Req, Trace
18
+ from .routing import WebSocket as WS
@@ -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
+
84
+ def get(self, path: str) -> AppRecord:
85
+ path = path[0].upper() + path[1:]
86
+ for k, v in self.app_dic.items():
87
+ if path.startswith(k):
88
+ return v
89
+ raise AppNotFoundException(f'Can"t find app of "{path}"')
90
+
91
+ def clear(self):
92
+ self.app_dic.clear()
93
+
94
+
95
+ class TaskStore:
96
+ def __init__(self):
97
+ # will be called after the app becomes available
98
+ self.late_tasks: dict[str, list[tuple[Callable[[FastAPI], None], int]]] = {}
99
+
100
+ def add_late_task(self, path: str, task: Callable[[FastAPI], None], priority: int):
101
+ if curr_tasks := self.late_tasks.get(path):
102
+ self.late_tasks.update({path: [*curr_tasks, (task, priority)]})
103
+ else:
104
+ self.late_tasks.update({path: [(task, priority)]})
105
+
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 = TaskStore()