fastapi-startkit 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.
Files changed (75) hide show
  1. fastapi_startkit-0.1.0/PKG-INFO +13 -0
  2. fastapi_startkit-0.1.0/fastapi_startkit/__init__.py +3 -0
  3. fastapi_startkit-0.1.0/fastapi_startkit/application.py +40 -0
  4. fastapi_startkit-0.1.0/fastapi_startkit/configuration/Configuration.py +80 -0
  5. fastapi_startkit-0.1.0/fastapi_startkit/configuration/__init__.py +2 -0
  6. fastapi_startkit-0.1.0/fastapi_startkit/configuration/helpers.py +5 -0
  7. fastapi_startkit-0.1.0/fastapi_startkit/configuration/providers/ConfigurationProvider.py +16 -0
  8. fastapi_startkit-0.1.0/fastapi_startkit/configuration/providers/__init__.py +1 -0
  9. fastapi_startkit-0.1.0/fastapi_startkit/container/__init__.py +1 -0
  10. fastapi_startkit-0.1.0/fastapi_startkit/container/container.py +494 -0
  11. fastapi_startkit-0.1.0/fastapi_startkit/environment/environment.py +76 -0
  12. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/DD.py +38 -0
  13. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/ExceptionHandler.py +76 -0
  14. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/__init__.py +38 -0
  15. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/exceptionite/__init__.py +0 -0
  16. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/exceptionite/blocks.py +101 -0
  17. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/exceptionite/controllers.py +13 -0
  18. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/exceptionite/solutions.py +66 -0
  19. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/exceptionite/tabs.py +19 -0
  20. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/exceptions.py +218 -0
  21. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/handlers/DumpExceptionHandler.py +104 -0
  22. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/handlers/HttpExceptionHandler.py +28 -0
  23. fastapi_startkit-0.1.0/fastapi_startkit/exceptions/handlers/ModelNotFoundHandler.py +13 -0
  24. fastapi_startkit-0.1.0/fastapi_startkit/facades/Auth.py +5 -0
  25. fastapi_startkit-0.1.0/fastapi_startkit/facades/Auth.pyi +32 -0
  26. fastapi_startkit-0.1.0/fastapi_startkit/facades/Broadcast.py +5 -0
  27. fastapi_startkit-0.1.0/fastapi_startkit/facades/Cache.py +5 -0
  28. fastapi_startkit-0.1.0/fastapi_startkit/facades/Config.py +5 -0
  29. fastapi_startkit-0.1.0/fastapi_startkit/facades/Config.pyi +14 -0
  30. fastapi_startkit-0.1.0/fastapi_startkit/facades/Dump.py +5 -0
  31. fastapi_startkit-0.1.0/fastapi_startkit/facades/Dump.pyi +26 -0
  32. fastapi_startkit-0.1.0/fastapi_startkit/facades/Facade.py +5 -0
  33. fastapi_startkit-0.1.0/fastapi_startkit/facades/Gate.py +5 -0
  34. fastapi_startkit-0.1.0/fastapi_startkit/facades/Gate.pyi +32 -0
  35. fastapi_startkit-0.1.0/fastapi_startkit/facades/Hash.py +5 -0
  36. fastapi_startkit-0.1.0/fastapi_startkit/facades/Hash.pyi +28 -0
  37. fastapi_startkit-0.1.0/fastapi_startkit/facades/Loader.py +5 -0
  38. fastapi_startkit-0.1.0/fastapi_startkit/facades/Loader.pyi +30 -0
  39. fastapi_startkit-0.1.0/fastapi_startkit/facades/Mail.py +5 -0
  40. fastapi_startkit-0.1.0/fastapi_startkit/facades/Mail.pyi +14 -0
  41. fastapi_startkit-0.1.0/fastapi_startkit/facades/Notification.py +5 -0
  42. fastapi_startkit-0.1.0/fastapi_startkit/facades/Notification.pyi +25 -0
  43. fastapi_startkit-0.1.0/fastapi_startkit/facades/Queue.py +5 -0
  44. fastapi_startkit-0.1.0/fastapi_startkit/facades/Queue.pyi +10 -0
  45. fastapi_startkit-0.1.0/fastapi_startkit/facades/RateLimiter.py +5 -0
  46. fastapi_startkit-0.1.0/fastapi_startkit/facades/RateLimiter.pyi +43 -0
  47. fastapi_startkit-0.1.0/fastapi_startkit/facades/Request.py +5 -0
  48. fastapi_startkit-0.1.0/fastapi_startkit/facades/Request.pyi +88 -0
  49. fastapi_startkit-0.1.0/fastapi_startkit/facades/Response.py +5 -0
  50. fastapi_startkit-0.1.0/fastapi_startkit/facades/Response.pyi +68 -0
  51. fastapi_startkit-0.1.0/fastapi_startkit/facades/Session.py +5 -0
  52. fastapi_startkit-0.1.0/fastapi_startkit/facades/Session.pyi +59 -0
  53. fastapi_startkit-0.1.0/fastapi_startkit/facades/Storage.py +5 -0
  54. fastapi_startkit-0.1.0/fastapi_startkit/facades/Storage.pyi +12 -0
  55. fastapi_startkit-0.1.0/fastapi_startkit/facades/Url.py +5 -0
  56. fastapi_startkit-0.1.0/fastapi_startkit/facades/Url.pyi +22 -0
  57. fastapi_startkit-0.1.0/fastapi_startkit/facades/View.py +5 -0
  58. fastapi_startkit-0.1.0/fastapi_startkit/facades/View.pyi +54 -0
  59. fastapi_startkit-0.1.0/fastapi_startkit/facades/__init__.py +19 -0
  60. fastapi_startkit-0.1.0/fastapi_startkit/loader/Loader.py +78 -0
  61. fastapi_startkit-0.1.0/fastapi_startkit/loader/__init__.py +1 -0
  62. fastapi_startkit-0.1.0/fastapi_startkit/providers/ConfigurationProvider.py +13 -0
  63. fastapi_startkit-0.1.0/fastapi_startkit/providers/Provider.py +14 -0
  64. fastapi_startkit-0.1.0/fastapi_startkit/providers/__init__.py +4 -0
  65. fastapi_startkit-0.1.0/fastapi_startkit/utils/__init__.py +0 -0
  66. fastapi_startkit-0.1.0/fastapi_startkit/utils/collections.py +545 -0
  67. fastapi_startkit-0.1.0/fastapi_startkit/utils/console.py +39 -0
  68. fastapi_startkit-0.1.0/fastapi_startkit/utils/data/mime.types +1863 -0
  69. fastapi_startkit-0.1.0/fastapi_startkit/utils/filesystem.py +100 -0
  70. fastapi_startkit-0.1.0/fastapi_startkit/utils/http.py +101 -0
  71. fastapi_startkit-0.1.0/fastapi_startkit/utils/location.py +90 -0
  72. fastapi_startkit-0.1.0/fastapi_startkit/utils/str.py +120 -0
  73. fastapi_startkit-0.1.0/fastapi_startkit/utils/structures.py +97 -0
  74. fastapi_startkit-0.1.0/fastapi_startkit/utils/time.py +58 -0
  75. fastapi_startkit-0.1.0/pyproject.toml +16 -0
@@ -0,0 +1,13 @@
1
+ Metadata-Version: 2.4
2
+ Name: fastapi-startkit
3
+ Version: 0.1.0
4
+ Summary: Core Masonite components (Container, Config, Loader)
5
+ Author: Bedram Tamang
6
+ Author-email: tmgbedu@gmail.com
7
+ Requires-Python: >=3.12,<4.0
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Programming Language :: Python :: 3.12
10
+ Classifier: Programming Language :: Python :: 3.13
11
+ Classifier: Programming Language :: Python :: 3.14
12
+ Requires-Dist: dotty-dict (>=1.3.1)
13
+ Requires-Dist: fastapi[standard] (>=0.124.4,<0.125.0)
@@ -0,0 +1,3 @@
1
+ from .application import Application
2
+
3
+ __all__ = ["Application"]
@@ -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,2 @@
1
+ from .helpers import config
2
+ from .Configuration import Configuration
@@ -0,0 +1,5 @@
1
+ from ..facades import Config
2
+
3
+
4
+ def config(key, default=None):
5
+ return Config.get(key, 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)