aspyx 1.0.1__tar.gz → 1.2.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.

Potentially problematic release.


This version of aspyx might be problematic. Click here for more details.

Files changed (28) hide show
  1. {aspyx-1.0.1/src/aspyx.egg-info → aspyx-1.2.0}/PKG-INFO +95 -27
  2. {aspyx-1.0.1 → aspyx-1.2.0}/README.md +94 -26
  3. {aspyx-1.0.1 → aspyx-1.2.0}/pyproject.toml +1 -1
  4. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/di/__init__.py +7 -5
  5. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/di/aop/aop.py +2 -2
  6. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/di/configuration/__init__.py +4 -1
  7. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/di/configuration/configuration.py +4 -54
  8. aspyx-1.2.0/src/aspyx/di/configuration/env_configuration_source.py +55 -0
  9. aspyx-1.2.0/src/aspyx/di/configuration/yaml_configuration_source.py +26 -0
  10. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/di/di.py +260 -94
  11. aspyx-1.2.0/src/aspyx/di/util/__init__.py +8 -0
  12. aspyx-1.2.0/src/aspyx/di/util/stringbuilder.py +31 -0
  13. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/reflection/reflection.py +40 -6
  14. {aspyx-1.0.1 → aspyx-1.2.0/src/aspyx.egg-info}/PKG-INFO +95 -27
  15. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx.egg-info/SOURCES.txt +4 -0
  16. {aspyx-1.0.1 → aspyx-1.2.0}/tests/test_aop.py +14 -12
  17. {aspyx-1.0.1 → aspyx-1.2.0}/tests/test_configuration.py +38 -7
  18. {aspyx-1.0.1 → aspyx-1.2.0}/tests/test_di.py +53 -33
  19. {aspyx-1.0.1 → aspyx-1.2.0}/tests/test_di_cycle.py +8 -6
  20. {aspyx-1.0.1 → aspyx-1.2.0}/tests/test_proxy.py +4 -1
  21. {aspyx-1.0.1 → aspyx-1.2.0}/tests/test_reflection.py +10 -14
  22. {aspyx-1.0.1 → aspyx-1.2.0}/LICENSE +0 -0
  23. {aspyx-1.0.1 → aspyx-1.2.0}/setup.cfg +0 -0
  24. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/di/aop/__init__.py +0 -0
  25. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/reflection/__init__.py +0 -0
  26. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx/reflection/proxy.py +0 -0
  27. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx.egg-info/dependency_links.txt +0 -0
  28. {aspyx-1.0.1 → aspyx-1.2.0}/src/aspyx.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: aspyx
3
- Version: 1.0.1
3
+ Version: 1.2.0
4
4
  Summary: A DI and AOP library for Python
5
5
  Author-email: Andreas Ernst <andreas.ernst7@gmail.com>
6
6
  License: MIT License
@@ -34,8 +34,9 @@ Dynamic: license-file
34
34
 
35
35
  ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
36
36
  ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
37
- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspyx)
37
+ ![Python Versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)
38
38
  ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
39
+ ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
39
40
 
40
41
  ## Table of Contents
41
42
 
@@ -56,18 +57,18 @@ Dynamic: license-file
56
57
  - [AOP](#aop)
57
58
  - [Configuration](#configuration)
58
59
  - [Reflection](#reflection)
60
+ - [Version History](#version-history)
59
61
 
60
62
  # Introduction
61
63
 
62
64
  Aspyx is a small python libary, that adds support for both dependency injection and aop.
63
65
 
64
66
  The following features are supported
65
- - constructor injection
66
- - method injection
67
+ - constructor and setter injection
67
68
  - post processors
68
69
  - factory classes and methods
69
70
  - support for eager construction
70
- - support for singleton and reuqest scopes
71
+ - support for singleton and request scopes
71
72
  - possibilty to add custom scopes
72
73
  - lifecycle events methods
73
74
  - bundling of injectable object sets by environment classes including recursive imports and inheritance
@@ -156,7 +157,7 @@ Let's look at the details
156
157
 
157
158
  `pip install aspyx`
158
159
 
159
- The library is tested with Python version > 3.8
160
+ The library is tested with all Python version > 3.9
160
161
 
161
162
  Ready to go...
162
163
 
@@ -185,8 +186,15 @@ The decorator accepts the keyword arguments
185
186
  - `eager : boolean`
186
187
  if `True`, the container will create the instances automatically while booting the environment. This is the default.
187
188
  - `scope: str`
188
- the name of a scope which will determine how often instances will be created.
189
- `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
189
+ the name of a - registered - scope which will determine how often instances will be created.
190
+
191
+ The following scopes are implemented out of the box:
192
+ - `singleton`
193
+ objects are created once inside an environment and cached. This is the default.
194
+ - `request`
195
+ objects are created on every injection request
196
+ - `thread`
197
+ objects are created and cached with respect to the current thread.
190
198
 
191
199
  Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
192
200
 
@@ -304,7 +312,7 @@ Different decorators are implemented, that call methods with computed values
304
312
  the method is called with all resolved parameter types ( same as the constructor call)
305
313
  - `@inject_environment`
306
314
  the method is called with the creating environment as a single parameter
307
- - `@value()`
315
+ - `@inject_value()`
308
316
  the method is called with a resolved configuration value. Check the corresponding chapter
309
317
 
310
318
  **Example**:
@@ -325,9 +333,11 @@ class Foo:
325
333
 
326
334
  ## Lifecycle methods
327
335
 
328
- It is possible to mark specific lifecle methods.
336
+ It is possible to mark specific lifecyle methods.
329
337
  - `@on_init()`
330
338
  called after the constructor and all other injections.
339
+ - `@on_running()`
340
+ called an environment has initialized all eager objects.
331
341
  - `@on_destroy()`
332
342
  called during shutdown of the environment
333
343
 
@@ -445,7 +455,7 @@ Both add the fluent methods:
445
455
 
446
456
  The fluent methods `named`, `matches` and `of_type` can be called multiple times!
447
457
 
448
- **Example**:
458
+ **Example**: react on both `transactional` decorators on methods or classes
449
459
 
450
460
  ```python
451
461
  @injectable()
@@ -460,7 +470,7 @@ class TransactionAdvice:
460
470
 
461
471
  # Configuration
462
472
 
463
- It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
473
+ It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
464
474
 
465
475
  ```python
466
476
  @injectable()
@@ -468,11 +478,13 @@ class Foo:
468
478
  def __init__(self):
469
479
  pass
470
480
 
471
- @value("OS")
472
- def inject_value(self, os: str):
481
+ @inject_value("HOME")
482
+ def inject_home(self, os: str):
473
483
  ...
474
484
  ```
475
485
 
486
+ If required a coercion will be executed.
487
+
476
488
  This concept relies on a central object `ConfigurationManager` that stores the overall configuration values as provided by so called configuration sources that are defined as follows.
477
489
 
478
490
  ```python
@@ -484,14 +496,24 @@ class ConfigurationSource(ABC):
484
496
 
485
497
  @abstractmethod
486
498
  def load(self) -> dict:
487
- pass
488
499
  ```
489
500
 
490
501
  The `load` method is able to return a tree-like structure by returning a `dict`.
491
502
 
492
- As a default environment variables are already supported.
503
+ Configuration variables are retrieved with the method
504
+
505
+ ```python
506
+ def get(self, path: str, type: Type[T], default : Optional[T]=None) -> T:
507
+ ```
508
+
509
+ - `path`
510
+ a '.' separated path
511
+ - `type`
512
+ the desired type
513
+ - `default`
514
+ a default, if no value is registered
493
515
 
494
- Other sources can be added dynamically by just registering them.
516
+ Sources can be added dynamically by registering them.
495
517
 
496
518
  **Example**:
497
519
  ```python
@@ -511,6 +533,31 @@ class SampleConfigurationSource(ConfigurationSource):
511
533
  }
512
534
  ```
513
535
 
536
+ Two specific source are already implemented:
537
+ - `EnvConfigurationSource`
538
+ reads the os environment variables
539
+ - `YamlConfigurationSource`
540
+ reads a specific yaml file
541
+
542
+ Typically you create the required configuration sources in an environment class, e.g.
543
+
544
+ ```python
545
+ @environment()
546
+ class SampleEnvironment:
547
+ # constructor
548
+
549
+ def __init__(self):
550
+ pass
551
+
552
+ @create()
553
+ def create_env_source(self) -> EnvConfigurationSource:
554
+ return EnvConfigurationSource()
555
+
556
+ @create()
557
+ def create_yaml_source(self) -> YamlConfigurationSource:
558
+ return YamlConfigurationSource("config.yaml")
559
+ ```
560
+
514
561
  # Reflection
515
562
 
516
563
  As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
@@ -522,16 +569,24 @@ TypeDescriptor.for_type(<type>)
522
569
  ```
523
570
 
524
571
  it offers the methods
525
- - `get_methods(local=False)`
526
- - `get_method(name: str, local=False)`
527
- - `has_decorator(decorator) -> bool`
528
- - `get_decorator(decorator)`
572
+ - `get_methods(local=False)`
573
+ return a list of either local or overall methods
574
+ - `get_method(name: str, local=False)`
575
+ return a single either local or overall method
576
+ - `has_decorator(decorator: Callable) -> bool`
577
+ return `True`, if the class is decorated with the specified decorator
578
+ - `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
579
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
529
580
 
530
581
  The returned method descriptors offer:
531
- - `param_types`
532
- - `return_type`
533
- - `has_decorator(decorator)`
534
- - `get_decorator(decorator)`
582
+ - `param_types`
583
+ list of arg types
584
+ - `return_type`
585
+ the return type
586
+ - `has_decorator(decorator: Callable) -> bool`
587
+ return `True`, if the method is decorated with the specified decorator
588
+ - `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
589
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
535
590
 
536
591
  The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
537
592
 
@@ -539,16 +594,29 @@ Whenver you define a custom decorator, you will need to register it accordingly.
539
594
 
540
595
  **Example**:
541
596
  ```python
542
- def transactional():
597
+ def transactional(scope):
543
598
  def decorator(func):
544
- Decorators.add(func, transactional)
599
+ Decorators.add(func, transactional, scope) # also add _all_ parameters in order to cache them
545
600
  return func
546
601
 
547
602
  return decorator
548
603
  ```
549
604
 
550
605
 
606
+ # Version History
607
+
608
+ **1.0.1**
609
+
610
+ - some internal refactorings
611
+
612
+ **1.1.0**
613
+
614
+ - added `@on_running()` callback
615
+ - added `thread` scope
616
+
617
+ **1.2.0**
551
618
 
619
+ - added `YamlConfigurationSource`
552
620
 
553
621
 
554
622
 
@@ -2,8 +2,9 @@
2
2
 
3
3
  ![Pylint](https://github.com/coolsamson7/aspyx/actions/workflows/pylint.yml/badge.svg)
4
4
  ![Build Status](https://github.com/coolsamson7/aspyx/actions/workflows/ci.yml/badge.svg)
5
- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/aspyx)
5
+ ![Python Versions](https://img.shields.io/badge/python-3.9%20|%203.10%20|%203.11%20|%203.12-blue)
6
6
  ![License](https://img.shields.io/github/license/coolsamson7/aspyx)
7
+ ![coverage](https://img.shields.io/badge/coverage-94%25-brightgreen)
7
8
 
8
9
  ## Table of Contents
9
10
 
@@ -24,18 +25,18 @@
24
25
  - [AOP](#aop)
25
26
  - [Configuration](#configuration)
26
27
  - [Reflection](#reflection)
28
+ - [Version History](#version-history)
27
29
 
28
30
  # Introduction
29
31
 
30
32
  Aspyx is a small python libary, that adds support for both dependency injection and aop.
31
33
 
32
34
  The following features are supported
33
- - constructor injection
34
- - method injection
35
+ - constructor and setter injection
35
36
  - post processors
36
37
  - factory classes and methods
37
38
  - support for eager construction
38
- - support for singleton and reuqest scopes
39
+ - support for singleton and request scopes
39
40
  - possibilty to add custom scopes
40
41
  - lifecycle events methods
41
42
  - bundling of injectable object sets by environment classes including recursive imports and inheritance
@@ -124,7 +125,7 @@ Let's look at the details
124
125
 
125
126
  `pip install aspyx`
126
127
 
127
- The library is tested with Python version > 3.8
128
+ The library is tested with all Python version > 3.9
128
129
 
129
130
  Ready to go...
130
131
 
@@ -153,8 +154,15 @@ The decorator accepts the keyword arguments
153
154
  - `eager : boolean`
154
155
  if `True`, the container will create the instances automatically while booting the environment. This is the default.
155
156
  - `scope: str`
156
- the name of a scope which will determine how often instances will be created.
157
- `singleton` will create it only once - per environment -, while `request` will recreate it on every injection request. The default is `singleton`
157
+ the name of a - registered - scope which will determine how often instances will be created.
158
+
159
+ The following scopes are implemented out of the box:
160
+ - `singleton`
161
+ objects are created once inside an environment and cached. This is the default.
162
+ - `request`
163
+ objects are created on every injection request
164
+ - `thread`
165
+ objects are created and cached with respect to the current thread.
158
166
 
159
167
  Other scopes - e.g. session related scopes - can be defined dynamically. Please check the corresponding chapter.
160
168
 
@@ -272,7 +280,7 @@ Different decorators are implemented, that call methods with computed values
272
280
  the method is called with all resolved parameter types ( same as the constructor call)
273
281
  - `@inject_environment`
274
282
  the method is called with the creating environment as a single parameter
275
- - `@value()`
283
+ - `@inject_value()`
276
284
  the method is called with a resolved configuration value. Check the corresponding chapter
277
285
 
278
286
  **Example**:
@@ -293,9 +301,11 @@ class Foo:
293
301
 
294
302
  ## Lifecycle methods
295
303
 
296
- It is possible to mark specific lifecle methods.
304
+ It is possible to mark specific lifecyle methods.
297
305
  - `@on_init()`
298
306
  called after the constructor and all other injections.
307
+ - `@on_running()`
308
+ called an environment has initialized all eager objects.
299
309
  - `@on_destroy()`
300
310
  called during shutdown of the environment
301
311
 
@@ -413,7 +423,7 @@ Both add the fluent methods:
413
423
 
414
424
  The fluent methods `named`, `matches` and `of_type` can be called multiple times!
415
425
 
416
- **Example**:
426
+ **Example**: react on both `transactional` decorators on methods or classes
417
427
 
418
428
  ```python
419
429
  @injectable()
@@ -428,7 +438,7 @@ class TransactionAdvice:
428
438
 
429
439
  # Configuration
430
440
 
431
- It is possible to inject configuration values, by decorating methods with `@value(<name>)` given a configuration key.
441
+ It is possible to inject configuration values, by decorating methods with `@inject-value(<name>)` given a configuration key.
432
442
 
433
443
  ```python
434
444
  @injectable()
@@ -436,11 +446,13 @@ class Foo:
436
446
  def __init__(self):
437
447
  pass
438
448
 
439
- @value("OS")
440
- def inject_value(self, os: str):
449
+ @inject_value("HOME")
450
+ def inject_home(self, os: str):
441
451
  ...
442
452
  ```
443
453
 
454
+ If required a coercion will be executed.
455
+
444
456
  This concept relies on a central object `ConfigurationManager` that stores the overall configuration values as provided by so called configuration sources that are defined as follows.
445
457
 
446
458
  ```python
@@ -452,14 +464,24 @@ class ConfigurationSource(ABC):
452
464
 
453
465
  @abstractmethod
454
466
  def load(self) -> dict:
455
- pass
456
467
  ```
457
468
 
458
469
  The `load` method is able to return a tree-like structure by returning a `dict`.
459
470
 
460
- As a default environment variables are already supported.
471
+ Configuration variables are retrieved with the method
472
+
473
+ ```python
474
+ def get(self, path: str, type: Type[T], default : Optional[T]=None) -> T:
475
+ ```
476
+
477
+ - `path`
478
+ a '.' separated path
479
+ - `type`
480
+ the desired type
481
+ - `default`
482
+ a default, if no value is registered
461
483
 
462
- Other sources can be added dynamically by just registering them.
484
+ Sources can be added dynamically by registering them.
463
485
 
464
486
  **Example**:
465
487
  ```python
@@ -479,6 +501,31 @@ class SampleConfigurationSource(ConfigurationSource):
479
501
  }
480
502
  ```
481
503
 
504
+ Two specific source are already implemented:
505
+ - `EnvConfigurationSource`
506
+ reads the os environment variables
507
+ - `YamlConfigurationSource`
508
+ reads a specific yaml file
509
+
510
+ Typically you create the required configuration sources in an environment class, e.g.
511
+
512
+ ```python
513
+ @environment()
514
+ class SampleEnvironment:
515
+ # constructor
516
+
517
+ def __init__(self):
518
+ pass
519
+
520
+ @create()
521
+ def create_env_source(self) -> EnvConfigurationSource:
522
+ return EnvConfigurationSource()
523
+
524
+ @create()
525
+ def create_yaml_source(self) -> YamlConfigurationSource:
526
+ return YamlConfigurationSource("config.yaml")
527
+ ```
528
+
482
529
  # Reflection
483
530
 
484
531
  As the library heavily relies on type introspection of classes and methods, a utility class `TypeDescriptor` is available that covers type information on classes.
@@ -490,16 +537,24 @@ TypeDescriptor.for_type(<type>)
490
537
  ```
491
538
 
492
539
  it offers the methods
493
- - `get_methods(local=False)`
494
- - `get_method(name: str, local=False)`
495
- - `has_decorator(decorator) -> bool`
496
- - `get_decorator(decorator)`
540
+ - `get_methods(local=False)`
541
+ return a list of either local or overall methods
542
+ - `get_method(name: str, local=False)`
543
+ return a single either local or overall method
544
+ - `has_decorator(decorator: Callable) -> bool`
545
+ return `True`, if the class is decorated with the specified decorator
546
+ - `get_decorator(decorator) -> Optional[DecoratorDescriptor]`
547
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
497
548
 
498
549
  The returned method descriptors offer:
499
- - `param_types`
500
- - `return_type`
501
- - `has_decorator(decorator)`
502
- - `get_decorator(decorator)`
550
+ - `param_types`
551
+ list of arg types
552
+ - `return_type`
553
+ the return type
554
+ - `has_decorator(decorator: Callable) -> bool`
555
+ return `True`, if the method is decorated with the specified decorator
556
+ - `get_decorator(decorator: Callable) -> Optional[DecoratorDescriptor]`
557
+ return a descriptor covering the decorator. In addition to the callable, it also stores the supplied args in the `args` property
503
558
 
504
559
  The management of decorators in turn relies on another utility class `Decorators` that caches decorators.
505
560
 
@@ -507,16 +562,29 @@ Whenver you define a custom decorator, you will need to register it accordingly.
507
562
 
508
563
  **Example**:
509
564
  ```python
510
- def transactional():
565
+ def transactional(scope):
511
566
  def decorator(func):
512
- Decorators.add(func, transactional)
567
+ Decorators.add(func, transactional, scope) # also add _all_ parameters in order to cache them
513
568
  return func
514
569
 
515
570
  return decorator
516
571
  ```
517
572
 
518
573
 
574
+ # Version History
575
+
576
+ **1.0.1**
577
+
578
+ - some internal refactorings
579
+
580
+ **1.1.0**
581
+
582
+ - added `@on_running()` callback
583
+ - added `thread` scope
584
+
585
+ **1.2.0**
519
586
 
587
+ - added `YamlConfigurationSource`
520
588
 
521
589
 
522
590
 
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "aspyx"
7
- version = "1.0.1"
7
+ version = "1.2.0"
8
8
  description = "A DI and AOP library for Python"
9
9
  authors = [{ name = "Andreas Ernst", email = "andreas.ernst7@gmail.com" }]
10
10
  readme = "README.md"
@@ -1,12 +1,12 @@
1
1
  """
2
2
  This module provides dependency injection and aop capabilities for Python applications.
3
3
  """
4
- from .di import InjectorException, CallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, create, on_init, on_destroy, inject_environment, Factory, PostProcessor
4
+ from .di import DIException, AbstractCallableProcessor, LifecycleCallable, Lifecycle, Providers, Environment, ClassInstanceProvider, injectable, factory, environment, inject, order, create, on_init, on_running, on_destroy, inject_environment, Factory, PostProcessor
5
5
 
6
6
  # import something from the subpackages, so that teh decorators are executed
7
7
 
8
- from aspyx.di.configuration import ConfigurationManager
9
- from aspyx.di.aop import before
8
+ from .configuration import ConfigurationManager
9
+ from .aop import before
10
10
 
11
11
  imports = [ConfigurationManager, before]
12
12
 
@@ -19,14 +19,16 @@ __all__ = [
19
19
  "environment",
20
20
  "inject",
21
21
  "create",
22
+ "order",
22
23
 
23
24
  "on_init",
25
+ "on_running",
24
26
  "on_destroy",
25
27
  "inject_environment",
26
28
  "Factory",
27
29
  "PostProcessor",
28
- "CallableProcessor",
30
+ "AbstractCallableProcessor",
29
31
  "LifecycleCallable",
30
- "InjectorException",
32
+ "DIException",
31
33
  "Lifecycle"
32
34
  ]
@@ -533,10 +533,10 @@ class AdviceProcessor(PostProcessor):
533
533
  def process(self, instance: object, environment: Environment):
534
534
  join_point_dict = self.advice.join_points4(instance, environment)
535
535
 
536
- for member, join_points in join_point_dict.items():
536
+ for member, joinPoints in join_point_dict.items():
537
537
  Environment.logger.debug("add aspects for %s:%s", type(instance), member.__name__)
538
538
 
539
539
  def wrap(jp):
540
540
  return lambda *args, **kwargs: Invocation(member, jp).call(*args, **kwargs)
541
541
 
542
- setattr(instance, member.__name__, types.MethodType(wrap(join_points), instance))
542
+ setattr(instance, member.__name__, types.MethodType(wrap(joinPoints), instance))
@@ -1,11 +1,14 @@
1
1
  """
2
2
  Configuration value handling
3
3
  """
4
- from .configuration import ConfigurationManager, ConfigurationSource, EnvConfigurationSource, value
4
+ from .configuration import ConfigurationManager, ConfigurationSource, value
5
+ from .env_configuration_source import EnvConfigurationSource
6
+ from .yaml_configuration_source import YamlConfigurationSource
5
7
 
6
8
  __all__ = [
7
9
  "ConfigurationManager",
8
10
  "ConfigurationSource",
9
11
  "EnvConfigurationSource",
12
+ "YamlConfigurationSource",
10
13
  "value"
11
14
  ]
@@ -4,11 +4,9 @@ Configuration handling module.
4
4
  from __future__ import annotations
5
5
 
6
6
  from abc import ABC, abstractmethod
7
- import os
8
7
  from typing import Optional, Type, TypeVar
9
- from dotenv import load_dotenv
10
8
 
11
- from aspyx.di import injectable, Environment, CallableProcessor, LifecycleCallable, Lifecycle
9
+ from aspyx.di import injectable, Environment, LifecycleCallable, Lifecycle
12
10
  from aspyx.di.di import order, inject
13
11
  from aspyx.reflection import Decorators, DecoratorDescriptor, TypeDescriptor
14
12
 
@@ -67,7 +65,7 @@ class ConfigurationManager:
67
65
 
68
66
  def get(self, path: str, type: Type[T], default : Optional[T]=None) -> T:
69
67
  """
70
- Get a configuration value by path and type, with optional coercion.
68
+ Retrieve a configuration value by path and type, with optional coercion.
71
69
  Arguments:
72
70
  path (str): The path to the configuration value, e.g. "database.host".
73
71
  type (Type[T]): The expected type.
@@ -119,54 +117,6 @@ class ConfigurationSource(ABC):
119
117
  return the configuration values of this source as a dictionary.
120
118
  """
121
119
 
122
- @injectable()
123
- class EnvConfigurationSource(ConfigurationSource):
124
- """
125
- EnvConfigurationSource loads all environment variables.
126
- """
127
-
128
- __slots__ = []
129
-
130
- # constructor
131
-
132
- def __init__(self):
133
- super().__init__()
134
-
135
- load_dotenv()
136
-
137
- # implement
138
-
139
- def load(self) -> dict:
140
- def merge_dicts(a, b):
141
- """Recursively merges b into a"""
142
- for key, value in b.items():
143
- if isinstance(value, dict) and key in a and isinstance(a[key], dict):
144
- merge_dicts(a[key], value)
145
- else:
146
- a[key] = value
147
- return a
148
-
149
- def explode_key(key, value):
150
- """Explodes keys with '.' or '/' into nested dictionaries"""
151
- parts = key.replace('/', '.').split('.')
152
- d = current = {}
153
- for part in parts[:-1]:
154
- current[part] = {}
155
- current = current[part]
156
- current[parts[-1]] = value
157
- return d
158
-
159
- exploded = {}
160
-
161
- for key, value in os.environ.items():
162
- if '.' in key or '/' in key:
163
- partial = explode_key(key, value)
164
- merge_dicts(exploded, partial)
165
- else:
166
- exploded[key] = value
167
-
168
- return exploded
169
-
170
120
  # decorator
171
121
 
172
122
  def value(key: str, default=None):
@@ -188,8 +138,8 @@ def value(key: str, default=None):
188
138
  @injectable()
189
139
  @order(9)
190
140
  class ConfigurationLifecycleCallable(LifecycleCallable):
191
- def __init__(self, processor: CallableProcessor, manager: ConfigurationManager):
192
- super().__init__(value, processor, Lifecycle.ON_INIT)
141
+ def __init__(self, manager: ConfigurationManager):
142
+ super().__init__(value, Lifecycle.ON_INJECT)
193
143
 
194
144
  self.manager = manager
195
145