fastapi-startkit 0.1.0__py3-none-any.whl
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_startkit/__init__.py +3 -0
- fastapi_startkit/application.py +40 -0
- fastapi_startkit/configuration/Configuration.py +80 -0
- fastapi_startkit/configuration/__init__.py +2 -0
- fastapi_startkit/configuration/helpers.py +5 -0
- fastapi_startkit/configuration/providers/ConfigurationProvider.py +16 -0
- fastapi_startkit/configuration/providers/__init__.py +1 -0
- fastapi_startkit/container/__init__.py +1 -0
- fastapi_startkit/container/container.py +494 -0
- fastapi_startkit/environment/environment.py +76 -0
- fastapi_startkit/exceptions/DD.py +38 -0
- fastapi_startkit/exceptions/ExceptionHandler.py +76 -0
- fastapi_startkit/exceptions/__init__.py +38 -0
- fastapi_startkit/exceptions/exceptionite/__init__.py +0 -0
- fastapi_startkit/exceptions/exceptionite/blocks.py +101 -0
- fastapi_startkit/exceptions/exceptionite/controllers.py +13 -0
- fastapi_startkit/exceptions/exceptionite/solutions.py +66 -0
- fastapi_startkit/exceptions/exceptionite/tabs.py +19 -0
- fastapi_startkit/exceptions/exceptions.py +218 -0
- fastapi_startkit/exceptions/handlers/DumpExceptionHandler.py +104 -0
- fastapi_startkit/exceptions/handlers/HttpExceptionHandler.py +28 -0
- fastapi_startkit/exceptions/handlers/ModelNotFoundHandler.py +13 -0
- fastapi_startkit/facades/Auth.py +5 -0
- fastapi_startkit/facades/Auth.pyi +32 -0
- fastapi_startkit/facades/Broadcast.py +5 -0
- fastapi_startkit/facades/Cache.py +5 -0
- fastapi_startkit/facades/Config.py +5 -0
- fastapi_startkit/facades/Config.pyi +14 -0
- fastapi_startkit/facades/Dump.py +5 -0
- fastapi_startkit/facades/Dump.pyi +26 -0
- fastapi_startkit/facades/Facade.py +5 -0
- fastapi_startkit/facades/Gate.py +5 -0
- fastapi_startkit/facades/Gate.pyi +32 -0
- fastapi_startkit/facades/Hash.py +5 -0
- fastapi_startkit/facades/Hash.pyi +28 -0
- fastapi_startkit/facades/Loader.py +5 -0
- fastapi_startkit/facades/Loader.pyi +30 -0
- fastapi_startkit/facades/Mail.py +5 -0
- fastapi_startkit/facades/Mail.pyi +14 -0
- fastapi_startkit/facades/Notification.py +5 -0
- fastapi_startkit/facades/Notification.pyi +25 -0
- fastapi_startkit/facades/Queue.py +5 -0
- fastapi_startkit/facades/Queue.pyi +10 -0
- fastapi_startkit/facades/RateLimiter.py +5 -0
- fastapi_startkit/facades/RateLimiter.pyi +43 -0
- fastapi_startkit/facades/Request.py +5 -0
- fastapi_startkit/facades/Request.pyi +88 -0
- fastapi_startkit/facades/Response.py +5 -0
- fastapi_startkit/facades/Response.pyi +68 -0
- fastapi_startkit/facades/Session.py +5 -0
- fastapi_startkit/facades/Session.pyi +59 -0
- fastapi_startkit/facades/Storage.py +5 -0
- fastapi_startkit/facades/Storage.pyi +12 -0
- fastapi_startkit/facades/Url.py +5 -0
- fastapi_startkit/facades/Url.pyi +22 -0
- fastapi_startkit/facades/View.py +5 -0
- fastapi_startkit/facades/View.pyi +54 -0
- fastapi_startkit/facades/__init__.py +19 -0
- fastapi_startkit/loader/Loader.py +78 -0
- fastapi_startkit/loader/__init__.py +1 -0
- fastapi_startkit/providers/ConfigurationProvider.py +13 -0
- fastapi_startkit/providers/Provider.py +14 -0
- fastapi_startkit/providers/__init__.py +4 -0
- fastapi_startkit/utils/__init__.py +0 -0
- fastapi_startkit/utils/collections.py +545 -0
- fastapi_startkit/utils/console.py +39 -0
- fastapi_startkit/utils/data/mime.types +1863 -0
- fastapi_startkit/utils/filesystem.py +100 -0
- fastapi_startkit/utils/http.py +101 -0
- fastapi_startkit/utils/location.py +90 -0
- fastapi_startkit/utils/str.py +120 -0
- fastapi_startkit/utils/structures.py +97 -0
- fastapi_startkit/utils/time.py +58 -0
- fastapi_startkit-0.1.0.dist-info/METADATA +13 -0
- fastapi_startkit-0.1.0.dist-info/RECORD +76 -0
- fastapi_startkit-0.1.0.dist-info/WHEEL +4 -0
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
from dotenv import load_dotenv
|
|
2
|
+
from fastapi import FastAPI
|
|
3
|
+
from .container import Container
|
|
4
|
+
from .environment.environment import LoadEnvironment
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class Application(Container):
|
|
8
|
+
def __init__(self, base_path: str = None, providers=None):
|
|
9
|
+
self.base_path: str = base_path
|
|
10
|
+
self.providers = providers if providers else []
|
|
11
|
+
|
|
12
|
+
self.load_environment()
|
|
13
|
+
self.register_providers()
|
|
14
|
+
|
|
15
|
+
self.fastapi = FastAPI()
|
|
16
|
+
self.load_providers()
|
|
17
|
+
|
|
18
|
+
def register_providers(self):
|
|
19
|
+
for provider_class in self.providers:
|
|
20
|
+
provider = provider_class(self)
|
|
21
|
+
provider.register()
|
|
22
|
+
return self
|
|
23
|
+
|
|
24
|
+
def load_providers(self):
|
|
25
|
+
for provider in self.providers:
|
|
26
|
+
self.resolve(provider.boot)
|
|
27
|
+
return self
|
|
28
|
+
|
|
29
|
+
def use_fastapi(self, fastapi: FastAPI):
|
|
30
|
+
self.fastapi = fastapi
|
|
31
|
+
return self
|
|
32
|
+
|
|
33
|
+
def get(self, path: str):
|
|
34
|
+
return self.fastapi.get(path)
|
|
35
|
+
|
|
36
|
+
def __call__(self):
|
|
37
|
+
return self.fastapi
|
|
38
|
+
|
|
39
|
+
def load_environment(self):
|
|
40
|
+
LoadEnvironment(base_path=self.base_path)
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
from fastapi_startkit_foundation.loader import Loader
|
|
2
|
+
from ..utils.structures import data
|
|
3
|
+
from ..exceptions import InvalidConfigurationLocation, InvalidConfigurationSetup
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class Configuration:
|
|
7
|
+
# Foundation configuration keys
|
|
8
|
+
reserved_keys = [
|
|
9
|
+
"application",
|
|
10
|
+
"auth",
|
|
11
|
+
"broadcast",
|
|
12
|
+
"cache",
|
|
13
|
+
"database",
|
|
14
|
+
"filesystem",
|
|
15
|
+
"mail",
|
|
16
|
+
"notification",
|
|
17
|
+
"providers",
|
|
18
|
+
"queue",
|
|
19
|
+
"session",
|
|
20
|
+
]
|
|
21
|
+
|
|
22
|
+
def __init__(self, application):
|
|
23
|
+
self.application = application
|
|
24
|
+
self._config = data()
|
|
25
|
+
|
|
26
|
+
def load(self):
|
|
27
|
+
"""At boot load configuration from all files and store them in here."""
|
|
28
|
+
config_root = self.application.make("config.location")
|
|
29
|
+
for module_name, module in Loader().get_modules(
|
|
30
|
+
config_root, raise_exception=True
|
|
31
|
+
).items():
|
|
32
|
+
params = Loader().get_parameters(module)
|
|
33
|
+
for name, value in params.items():
|
|
34
|
+
self._config[f"{module_name}.{name.lower()}"] = value
|
|
35
|
+
|
|
36
|
+
# check loaded configuration
|
|
37
|
+
if not self._config.get("application"):
|
|
38
|
+
raise InvalidConfigurationLocation(
|
|
39
|
+
f"Config directory {config_root} does not contain required configuration files."
|
|
40
|
+
)
|
|
41
|
+
|
|
42
|
+
def merge_with(self, path, external_config):
|
|
43
|
+
"""Merge external config at key with project config at same key. It's especially
|
|
44
|
+
useful in Masonite packages in order to merge the configuration default package with
|
|
45
|
+
the package configuration which can be published in project.
|
|
46
|
+
|
|
47
|
+
This functions disallow merging configuration using foundation configuration keys
|
|
48
|
+
(such as 'application').
|
|
49
|
+
"""
|
|
50
|
+
if path in self.reserved_keys:
|
|
51
|
+
raise InvalidConfigurationSetup(
|
|
52
|
+
f"{path} is a reserved configuration key name. Please use an other key."
|
|
53
|
+
)
|
|
54
|
+
if isinstance(external_config, str):
|
|
55
|
+
# config is a path and should be loaded
|
|
56
|
+
params = Loader.get_parameters(external_config)
|
|
57
|
+
else:
|
|
58
|
+
params = external_config
|
|
59
|
+
base_config = {name.lower(): value for name, value in params.items()}
|
|
60
|
+
merged_config = {**base_config, **self.get(path, {})}
|
|
61
|
+
self.set(path, merged_config)
|
|
62
|
+
|
|
63
|
+
def set(self, path, value):
|
|
64
|
+
self._config[path] = value
|
|
65
|
+
|
|
66
|
+
def has(self, path):
|
|
67
|
+
return path in self._config
|
|
68
|
+
|
|
69
|
+
def all(self):
|
|
70
|
+
return self._config
|
|
71
|
+
|
|
72
|
+
def get(self, path, default=None):
|
|
73
|
+
try:
|
|
74
|
+
config_at_path = self._config[path]
|
|
75
|
+
if isinstance(config_at_path, dict):
|
|
76
|
+
return data(config_at_path)
|
|
77
|
+
else:
|
|
78
|
+
return config_at_path
|
|
79
|
+
except KeyError:
|
|
80
|
+
return default
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
from ...providers import Provider
|
|
2
|
+
|
|
3
|
+
from ..Configuration import Configuration
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
class ConfigurationProvider(Provider):
|
|
7
|
+
def __init__(self, application):
|
|
8
|
+
self.application = application
|
|
9
|
+
|
|
10
|
+
def register(self):
|
|
11
|
+
config = Configuration(self.application)
|
|
12
|
+
config.load()
|
|
13
|
+
self.application.bind("config", config)
|
|
14
|
+
|
|
15
|
+
def boot(self):
|
|
16
|
+
pass
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .ConfigurationProvider import ConfigurationProvider
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
from .container import Container
|
|
@@ -0,0 +1,494 @@
|
|
|
1
|
+
"""Core of the IOC Container."""
|
|
2
|
+
|
|
3
|
+
import inspect
|
|
4
|
+
|
|
5
|
+
from ..exceptions import (
|
|
6
|
+
ContainerError,
|
|
7
|
+
MissingContainerBindingNotFound,
|
|
8
|
+
StrictContainerException,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Container:
|
|
13
|
+
"""Core of the Service Container.
|
|
14
|
+
|
|
15
|
+
Performs bindings and resolving of objects to and from the container.
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
objects = {}
|
|
19
|
+
strict = False
|
|
20
|
+
override = True
|
|
21
|
+
resolve_parameters = {}
|
|
22
|
+
remember = False
|
|
23
|
+
_hooks = {
|
|
24
|
+
"make": {},
|
|
25
|
+
"bind": {},
|
|
26
|
+
"resolve": {},
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
swaps = {}
|
|
30
|
+
_remembered = {}
|
|
31
|
+
|
|
32
|
+
def bind(self, name, class_obj):
|
|
33
|
+
"""Bind classes into the container with a key value pair.
|
|
34
|
+
|
|
35
|
+
Arguments:
|
|
36
|
+
name {string} -- Name of the key you want to bind the object to
|
|
37
|
+
class_obj {object} -- The object you want to bind
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
self
|
|
41
|
+
"""
|
|
42
|
+
if inspect.ismodule(class_obj):
|
|
43
|
+
raise StrictContainerException(
|
|
44
|
+
"Cannot bind module '{}' with key '{}' into the container".format(
|
|
45
|
+
class_obj, name
|
|
46
|
+
)
|
|
47
|
+
)
|
|
48
|
+
if self.strict and name in self.objects:
|
|
49
|
+
raise StrictContainerException(
|
|
50
|
+
"You cannot override a key inside a strict container"
|
|
51
|
+
)
|
|
52
|
+
|
|
53
|
+
if self.override or name not in self.objects:
|
|
54
|
+
self.fire_hook("bind", name, class_obj)
|
|
55
|
+
self.objects.update({name: class_obj})
|
|
56
|
+
|
|
57
|
+
return self
|
|
58
|
+
|
|
59
|
+
def unbind(self, name):
|
|
60
|
+
"""Unbind classes from the container from a key.
|
|
61
|
+
|
|
62
|
+
Arguments:
|
|
63
|
+
name {string} -- Name of the key you want to bind the object to
|
|
64
|
+
|
|
65
|
+
Returns:
|
|
66
|
+
self
|
|
67
|
+
"""
|
|
68
|
+
|
|
69
|
+
if name not in self.objects:
|
|
70
|
+
return False
|
|
71
|
+
|
|
72
|
+
del self.objects[name]
|
|
73
|
+
|
|
74
|
+
return self
|
|
75
|
+
|
|
76
|
+
def simple(self, obj):
|
|
77
|
+
"""Easy way to bind classes into the container by using passing the object only.
|
|
78
|
+
|
|
79
|
+
Automatically generates the key for the binding process.
|
|
80
|
+
|
|
81
|
+
Arguments:
|
|
82
|
+
class_obj {object} -- The object you want to bind
|
|
83
|
+
|
|
84
|
+
Returns:
|
|
85
|
+
self
|
|
86
|
+
"""
|
|
87
|
+
self.bind(obj if inspect.isclass(obj) else obj.__class__, obj)
|
|
88
|
+
return self
|
|
89
|
+
|
|
90
|
+
def singleton(self, name, class_obj):
|
|
91
|
+
"""Register a shared binding in the container.
|
|
92
|
+
|
|
93
|
+
Arguments:
|
|
94
|
+
name {string} -- Name of the key you want to bind the object to
|
|
95
|
+
class_obj {object} -- The object you want to bind
|
|
96
|
+
"""
|
|
97
|
+
obj = self.resolve(class_obj)
|
|
98
|
+
self.bind(name, obj)
|
|
99
|
+
|
|
100
|
+
def make(self, name, *arguments):
|
|
101
|
+
"""Retrieve a class from the container by key.
|
|
102
|
+
|
|
103
|
+
Arguments:
|
|
104
|
+
name {string} -- Key in the container that you want to get.
|
|
105
|
+
|
|
106
|
+
Raises:
|
|
107
|
+
MissingContainerBindingNotFound -- Raised if the key is not in the container.
|
|
108
|
+
|
|
109
|
+
Returns:
|
|
110
|
+
object -- Returns the object that is fetched.
|
|
111
|
+
"""
|
|
112
|
+
|
|
113
|
+
if name in self.objects:
|
|
114
|
+
obj = self.objects[name]
|
|
115
|
+
self.fire_hook("make", name, obj)
|
|
116
|
+
if inspect.isclass(obj):
|
|
117
|
+
obj = self.resolve(obj, *arguments)
|
|
118
|
+
return obj
|
|
119
|
+
elif name in self.swaps:
|
|
120
|
+
return self.swaps.get(name)
|
|
121
|
+
elif inspect.isclass(name):
|
|
122
|
+
try:
|
|
123
|
+
obj = self._find_obj(name)
|
|
124
|
+
self.fire_hook("make", name, obj)
|
|
125
|
+
if inspect.isclass(obj):
|
|
126
|
+
obj = self.resolve(obj, *arguments)
|
|
127
|
+
except MissingContainerBindingNotFound:
|
|
128
|
+
# If we don't find a bound object already in the container,
|
|
129
|
+
# we can go ahead and fall back on a simple resolve method.
|
|
130
|
+
# This allows resolving dependencies without explicit
|
|
131
|
+
# bindings.
|
|
132
|
+
obj = self.resolve(name, *arguments)
|
|
133
|
+
return obj
|
|
134
|
+
|
|
135
|
+
raise MissingContainerBindingNotFound(
|
|
136
|
+
"{0} key was not found in the container".format(name)
|
|
137
|
+
)
|
|
138
|
+
|
|
139
|
+
def has(self, name):
|
|
140
|
+
"""Check if a key exists in the container.
|
|
141
|
+
|
|
142
|
+
Arguments:
|
|
143
|
+
name {string} -- Key you want to check for in the container
|
|
144
|
+
|
|
145
|
+
Returns:
|
|
146
|
+
bool
|
|
147
|
+
"""
|
|
148
|
+
if isinstance(name, str):
|
|
149
|
+
return name in self.objects
|
|
150
|
+
else:
|
|
151
|
+
try:
|
|
152
|
+
self._find_obj(name)
|
|
153
|
+
return True
|
|
154
|
+
except MissingContainerBindingNotFound:
|
|
155
|
+
return False
|
|
156
|
+
|
|
157
|
+
def helper(self):
|
|
158
|
+
"""Add a helper to create builtin functions.
|
|
159
|
+
|
|
160
|
+
Used to more simply return
|
|
161
|
+
instances of this class when building helpers.
|
|
162
|
+
|
|
163
|
+
Returns:
|
|
164
|
+
self
|
|
165
|
+
"""
|
|
166
|
+
return self
|
|
167
|
+
|
|
168
|
+
def resolve(self, obj, *resolving_arguments):
|
|
169
|
+
"""Takes an object such as a function or class method and resolves it's
|
|
170
|
+
parameters from objects in the container.
|
|
171
|
+
|
|
172
|
+
Arguments:
|
|
173
|
+
obj {object} -- The object you want to resolve objects from via this container.
|
|
174
|
+
|
|
175
|
+
Returns:
|
|
176
|
+
object -- The object you tried resolving but with the correct dependencies injected.
|
|
177
|
+
"""
|
|
178
|
+
objects = []
|
|
179
|
+
passing_arguments = list(resolving_arguments)
|
|
180
|
+
if self.remember and obj in self._remembered:
|
|
181
|
+
objects = self._remembered[obj]
|
|
182
|
+
try:
|
|
183
|
+
return obj(*objects)
|
|
184
|
+
except TypeError as e:
|
|
185
|
+
raise ContainerError(str(e))
|
|
186
|
+
elif (
|
|
187
|
+
self.remember
|
|
188
|
+
and not passing_arguments
|
|
189
|
+
and inspect.ismethod(obj)
|
|
190
|
+
and "{}.{}.{}".format(
|
|
191
|
+
obj.__module__, obj.__self__.__class__.__name__, obj.__name__
|
|
192
|
+
)
|
|
193
|
+
in self._remembered
|
|
194
|
+
):
|
|
195
|
+
location = "{}.{}.{}".format(
|
|
196
|
+
obj.__module__, obj.__self__.__class__.__name__, obj.__name__
|
|
197
|
+
)
|
|
198
|
+
objects = self._remembered[location]
|
|
199
|
+
try:
|
|
200
|
+
return obj(*objects)
|
|
201
|
+
except TypeError as e:
|
|
202
|
+
raise ContainerError(str(e))
|
|
203
|
+
else:
|
|
204
|
+
for _, value in self.get_parameters(obj):
|
|
205
|
+
if type(value.annotation) in (str, int, dict, list, tuple) or value.annotation in (str, int, dict, list, tuple):
|
|
206
|
+
# Ignore any times a user is simply type hinting a parameter like (parameter:str or parameter:"str").
|
|
207
|
+
# In this case we don't want to resolve anything but we do want
|
|
208
|
+
# to insert any passing arguments we passed in
|
|
209
|
+
try:
|
|
210
|
+
objects.append(passing_arguments.pop(0))
|
|
211
|
+
except IndexError:
|
|
212
|
+
pass
|
|
213
|
+
|
|
214
|
+
continue
|
|
215
|
+
if ":" in str(value):
|
|
216
|
+
try:
|
|
217
|
+
param = self._find_annotated_parameter(value)
|
|
218
|
+
except ContainerError:
|
|
219
|
+
# This allows resolving dependencies without explicit bindings.
|
|
220
|
+
# See `self.make()`.
|
|
221
|
+
param = value.annotation
|
|
222
|
+
if inspect.isclass(param):
|
|
223
|
+
if resolving_arguments:
|
|
224
|
+
param = self.resolve(param, *resolving_arguments)
|
|
225
|
+
else:
|
|
226
|
+
param = self.resolve(param)
|
|
227
|
+
objects.append(param)
|
|
228
|
+
elif "self" in str(value):
|
|
229
|
+
objects.append(obj)
|
|
230
|
+
elif "=" in str(value):
|
|
231
|
+
objects.append(value.default)
|
|
232
|
+
elif "*" in str(value):
|
|
233
|
+
continue
|
|
234
|
+
elif self.resolve_parameters:
|
|
235
|
+
objects.append(self._find_parameter(value))
|
|
236
|
+
elif resolving_arguments:
|
|
237
|
+
try:
|
|
238
|
+
objects.append(passing_arguments.pop(0))
|
|
239
|
+
except IndexError:
|
|
240
|
+
raise ContainerError(
|
|
241
|
+
"Not enough dependencies passed. Resolving object needs {} dependencies.".format(
|
|
242
|
+
len(inspect.signature(obj).parameters)
|
|
243
|
+
)
|
|
244
|
+
)
|
|
245
|
+
else:
|
|
246
|
+
raise ContainerError(
|
|
247
|
+
"This container is not set to resolve parameters. You can set this in the container"
|
|
248
|
+
" constructor using the 'resolve_parameters=True' keyword argument."
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
if self.remember:
|
|
252
|
+
if not inspect.ismethod(obj):
|
|
253
|
+
self._remembered[obj] = objects
|
|
254
|
+
else:
|
|
255
|
+
signature = "{}.{}.{}".format(
|
|
256
|
+
obj.__module__, obj.__self__.__class__.__name__, obj.__name__
|
|
257
|
+
)
|
|
258
|
+
self._remembered[signature] = objects
|
|
259
|
+
return obj(*objects)
|
|
260
|
+
|
|
261
|
+
def collect(self, search):
|
|
262
|
+
"""Fetch a dictionary of objects using a search query.
|
|
263
|
+
|
|
264
|
+
Arguments:
|
|
265
|
+
search {string|object} -- The string or object you want to search for.
|
|
266
|
+
|
|
267
|
+
Raises:
|
|
268
|
+
AttributeError -- Thrown if there is no wildcard in the search string
|
|
269
|
+
|
|
270
|
+
Returns:
|
|
271
|
+
dict -- Returns a dictionary of collected objects and their key bindings.
|
|
272
|
+
"""
|
|
273
|
+
providers = {}
|
|
274
|
+
if isinstance(search, str):
|
|
275
|
+
# Search Can Be:
|
|
276
|
+
# '*ExceptionHook'
|
|
277
|
+
# 'Sentry*'
|
|
278
|
+
# 'Sentry*Hook'
|
|
279
|
+
for key, value in self.objects.items():
|
|
280
|
+
if isinstance(key, str):
|
|
281
|
+
if search.startswith("*"):
|
|
282
|
+
if key.endswith(search.split("*")[1]):
|
|
283
|
+
providers.update({key: value})
|
|
284
|
+
elif search.endswith("*"):
|
|
285
|
+
if key.startswith(search.split("*")[0]):
|
|
286
|
+
providers.update({key: value})
|
|
287
|
+
elif "*" in search:
|
|
288
|
+
split_search = search.split("*")
|
|
289
|
+
if key.startswith(split_search[0]) and key.endswith(
|
|
290
|
+
split_search[1]
|
|
291
|
+
):
|
|
292
|
+
providers.update({key: value})
|
|
293
|
+
else:
|
|
294
|
+
raise AttributeError(
|
|
295
|
+
"There is no '*' in your collection search"
|
|
296
|
+
)
|
|
297
|
+
else:
|
|
298
|
+
for provider_key, provider_class in self.objects.items():
|
|
299
|
+
if (
|
|
300
|
+
inspect.isclass(provider_class)
|
|
301
|
+
and issubclass(provider_class, search)
|
|
302
|
+
) or isinstance(provider_class, search):
|
|
303
|
+
providers.update({provider_key: provider_class})
|
|
304
|
+
|
|
305
|
+
return providers
|
|
306
|
+
|
|
307
|
+
def _find_annotated_parameter(self, parameter):
|
|
308
|
+
"""Find a given annotation in the container.
|
|
309
|
+
|
|
310
|
+
Arguments:
|
|
311
|
+
parameter {object} -- The object to find in the container.
|
|
312
|
+
|
|
313
|
+
Raises:
|
|
314
|
+
ContainerError -- Thrown when the dependency is not found in the container.
|
|
315
|
+
|
|
316
|
+
Returns:
|
|
317
|
+
object -- Returns the object found in the container.
|
|
318
|
+
"""
|
|
319
|
+
if isinstance(parameter.annotation, str):
|
|
320
|
+
return
|
|
321
|
+
if parameter.annotation in self.swaps:
|
|
322
|
+
obj = self.swaps[parameter.annotation]
|
|
323
|
+
if callable(obj):
|
|
324
|
+
return self.swaps[parameter.annotation](parameter.annotation, self)
|
|
325
|
+
return obj
|
|
326
|
+
|
|
327
|
+
for _, provider_class in self.objects.items():
|
|
328
|
+
if (
|
|
329
|
+
parameter.annotation == provider_class
|
|
330
|
+
or parameter.annotation == provider_class.__class__
|
|
331
|
+
):
|
|
332
|
+
obj = provider_class
|
|
333
|
+
self.fire_hook("resolve", parameter, obj)
|
|
334
|
+
|
|
335
|
+
return obj
|
|
336
|
+
elif (
|
|
337
|
+
inspect.isclass(provider_class)
|
|
338
|
+
and issubclass(provider_class, parameter.annotation)
|
|
339
|
+
or issubclass(provider_class.__class__, parameter.annotation)
|
|
340
|
+
):
|
|
341
|
+
obj = provider_class
|
|
342
|
+
self.fire_hook("resolve", parameter, obj)
|
|
343
|
+
return obj
|
|
344
|
+
|
|
345
|
+
raise ContainerError(
|
|
346
|
+
"The dependency with the {0} annotation could not be resolved by the container".format(
|
|
347
|
+
parameter
|
|
348
|
+
)
|
|
349
|
+
)
|
|
350
|
+
|
|
351
|
+
def get_parameters(self, obj):
|
|
352
|
+
return inspect.signature(obj).parameters.items()
|
|
353
|
+
|
|
354
|
+
def _find_parameter(self, keyword):
|
|
355
|
+
"""Find a parameter in the container.
|
|
356
|
+
|
|
357
|
+
Arguments:
|
|
358
|
+
parameter {inspect.Paramater} -- Parameter to search for.
|
|
359
|
+
|
|
360
|
+
Raises:
|
|
361
|
+
ContainerError -- Thrown when the dependency is not found in the container.
|
|
362
|
+
|
|
363
|
+
Returns:
|
|
364
|
+
object -- Returns the object found in the container
|
|
365
|
+
"""
|
|
366
|
+
parameter = str(keyword)
|
|
367
|
+
|
|
368
|
+
if parameter != "self" and parameter in self.objects:
|
|
369
|
+
obj = self.objects[parameter]
|
|
370
|
+
self.fire_hook("resolve", parameter, obj)
|
|
371
|
+
return obj
|
|
372
|
+
elif "=" in parameter:
|
|
373
|
+
return keyword.default
|
|
374
|
+
|
|
375
|
+
raise ContainerError(
|
|
376
|
+
"The parameter dependency with the key of {0} could not be found in the container".format(
|
|
377
|
+
parameter
|
|
378
|
+
)
|
|
379
|
+
)
|
|
380
|
+
|
|
381
|
+
def on_bind(self, key, obj):
|
|
382
|
+
"""Set some listeners for when a specific key or class in binded to the container.
|
|
383
|
+
|
|
384
|
+
Arguments:
|
|
385
|
+
key {string|object} -- The key can be a string or an object to listen for
|
|
386
|
+
obj {object} -- Should be a function or class method
|
|
387
|
+
|
|
388
|
+
Returns:
|
|
389
|
+
self
|
|
390
|
+
"""
|
|
391
|
+
return self._bind_hook("bind", key, obj)
|
|
392
|
+
|
|
393
|
+
def on_make(self, key, obj):
|
|
394
|
+
"""Set some listeners for when a specific key or class is made from the container.
|
|
395
|
+
|
|
396
|
+
Arguments:
|
|
397
|
+
key {string|object} -- The key can be a string or an object to listen for
|
|
398
|
+
obj {object} -- Should be a function or class method
|
|
399
|
+
|
|
400
|
+
Returns:
|
|
401
|
+
self
|
|
402
|
+
"""
|
|
403
|
+
return self._bind_hook("make", key, obj)
|
|
404
|
+
|
|
405
|
+
def on_resolve(self, key, obj):
|
|
406
|
+
"""Set some listeners for when a specific key or class is resolved from the container.
|
|
407
|
+
|
|
408
|
+
Arguments:
|
|
409
|
+
key {string|object} -- The key can be a string or an object to listen for
|
|
410
|
+
obj {object} -- Should be a function or class method
|
|
411
|
+
|
|
412
|
+
Returns:
|
|
413
|
+
self
|
|
414
|
+
"""
|
|
415
|
+
return self._bind_hook("resolve", key, obj)
|
|
416
|
+
|
|
417
|
+
def swap(self, obj, callback):
|
|
418
|
+
self.swaps.update({obj: callback})
|
|
419
|
+
return self
|
|
420
|
+
|
|
421
|
+
def fire_hook(self, action, key, obj):
|
|
422
|
+
"""Fire a specific hook based on a key or object.
|
|
423
|
+
|
|
424
|
+
Arguments:
|
|
425
|
+
action {string} -- Should be the action to fire (bind|make|resolve)
|
|
426
|
+
key {string|object} -- The key can be a string or an object to listen for
|
|
427
|
+
obj {object} -- Should be a function or class method
|
|
428
|
+
|
|
429
|
+
Returns:
|
|
430
|
+
None
|
|
431
|
+
"""
|
|
432
|
+
if (
|
|
433
|
+
str(key) in self._hooks[action]
|
|
434
|
+
or inspect.isclass(obj)
|
|
435
|
+
and obj in self._hooks[action]
|
|
436
|
+
or obj.__class__ in self._hooks[action]
|
|
437
|
+
):
|
|
438
|
+
|
|
439
|
+
for _, hook_list in self._hooks[action].items():
|
|
440
|
+
for hook_obj in hook_list:
|
|
441
|
+
hook_obj(obj, self)
|
|
442
|
+
|
|
443
|
+
def _bind_hook(self, hook, key, obj):
|
|
444
|
+
"""Internal method used to abstract away the logic for binding an
|
|
445
|
+
listener to the container hooks.
|
|
446
|
+
|
|
447
|
+
Arguments:
|
|
448
|
+
hook {string} -- The hook you want to listen for (bind|make|resolve)
|
|
449
|
+
key {string|object} -- The key to save for the listener
|
|
450
|
+
obj {object} -- Should be a function or class method
|
|
451
|
+
|
|
452
|
+
Returns:
|
|
453
|
+
self
|
|
454
|
+
"""
|
|
455
|
+
if key in self._hooks[hook]:
|
|
456
|
+
self._hooks[hook][key].append(obj)
|
|
457
|
+
else:
|
|
458
|
+
self._hooks[hook].update({key: [obj]})
|
|
459
|
+
return self
|
|
460
|
+
|
|
461
|
+
def _find_obj(self, obj):
|
|
462
|
+
"""Find an object in the container.
|
|
463
|
+
|
|
464
|
+
Arguments:
|
|
465
|
+
obj {object} -- Any object in the container
|
|
466
|
+
|
|
467
|
+
Raises:
|
|
468
|
+
MissingContainerBindingNotFound -- Raised when the object cannot be found.
|
|
469
|
+
|
|
470
|
+
Returns:
|
|
471
|
+
object -- Returns the object in the container
|
|
472
|
+
"""
|
|
473
|
+
for _, provider_class in self.objects.items():
|
|
474
|
+
if obj in (provider_class, provider_class.__class__):
|
|
475
|
+
return_obj = provider_class
|
|
476
|
+
self.fire_hook("resolve", obj, return_obj)
|
|
477
|
+
return return_obj
|
|
478
|
+
elif (
|
|
479
|
+
inspect.isclass(provider_class)
|
|
480
|
+
and issubclass(provider_class, obj)
|
|
481
|
+
or issubclass(provider_class.__class__, obj)
|
|
482
|
+
):
|
|
483
|
+
return_obj = provider_class
|
|
484
|
+
self.fire_hook("resolve", obj, return_obj)
|
|
485
|
+
return return_obj
|
|
486
|
+
|
|
487
|
+
raise MissingContainerBindingNotFound(
|
|
488
|
+
"The dependency with the {0} annotation could not be resolved by the container".format(
|
|
489
|
+
obj
|
|
490
|
+
)
|
|
491
|
+
)
|
|
492
|
+
|
|
493
|
+
def __contains__(self, obj):
|
|
494
|
+
return self.has(obj)
|