dependency-injection-pattern 0.0.5__tar.gz → 0.0.7__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 (40) hide show
  1. {dependency_injection_pattern-0.0.5/src/dependency_injection_pattern.egg-info → dependency_injection_pattern-0.0.7}/PKG-INFO +125 -13
  2. dependency_injection_pattern-0.0.7/README.md +182 -0
  3. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/pyproject.toml +3 -2
  4. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7/src/dependency_injection_pattern.egg-info}/PKG-INFO +125 -13
  5. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/dependency_injection_pattern.egg-info/requires.txt +1 -1
  6. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/container.py +17 -2
  7. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/default_scope.py +1 -1
  8. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/tests/test_provider.py +20 -0
  9. dependency_injection_pattern-0.0.5/README.md +0 -71
  10. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/LICENSE +0 -0
  11. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/setup.cfg +0 -0
  12. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/dependency_injection_pattern.egg-info/SOURCES.txt +0 -0
  13. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/dependency_injection_pattern.egg-info/dependency_links.txt +0 -0
  14. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/dependency_injection_pattern.egg-info/top_level.txt +0 -0
  15. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/__init__.py +0 -0
  16. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/exceptions/add_exception.py +0 -0
  17. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/exceptions/container_sealed_error.py +0 -0
  18. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/exceptions/factory_provide_exception.py +0 -0
  19. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/exceptions/implementation_exception.py +0 -0
  20. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/exceptions/provide_exception.py +0 -0
  21. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/exceptions/seal_exception.py +0 -0
  22. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/factories/dependency.py +0 -0
  23. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/factories/factory.py +0 -0
  24. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/factories/scoped_factory.py +0 -0
  25. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/factories/singleton_factory.py +0 -0
  26. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/factories/transient_factory.py +0 -0
  27. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/factory.py +0 -0
  28. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/helpers.py +0 -0
  29. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/log.py +0 -0
  30. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/named_service.py +0 -0
  31. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/protocols/container.py +0 -0
  32. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/protocols/factory.py +0 -0
  33. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/protocols/provider.py +0 -0
  34. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/provider.py +0 -0
  35. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/scope.py +0 -0
  36. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/core/service_request.py +0 -0
  37. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/src/di/exceptions.py +0 -0
  38. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/tests/test_container.py +0 -0
  39. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/tests/test_examples.py +0 -0
  40. {dependency_injection_pattern-0.0.5 → dependency_injection_pattern-0.0.7}/tests/test_factory.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dependency-injection-pattern
3
- Version: 0.0.5
3
+ Version: 0.0.7
4
4
  Summary: Python implementation of the Dependency Injection pattern.
5
5
  Author-email: Anders Madsen <anders.madsen@alphavue.com>
6
6
  License-Expression: MIT
@@ -18,15 +18,16 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Programming Language :: Python
22
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
24
  Classifier: Topic :: Software Development :: Libraries
24
25
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.10
26
+ Requires-Python: <3.15,>=3.10
26
27
  Description-Content-Type: text/markdown
27
28
  License-File: LICENSE
28
29
  Requires-Dist: typing-utilities<0.2,>=0.1.0
29
- Requires-Dist: runtime-reflection-lite<0.2,>=0.1.2
30
+ Requires-Dist: runtime-reflection-lite<0.3,>=0.2.0
30
31
  Provides-Extra: test
31
32
  Requires-Dist: pytest>=8.3; extra == "test"
32
33
  Requires-Dist: pytest-cov>=6.1; extra == "test"
@@ -39,22 +40,133 @@ Dynamic: license-file
39
40
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dependency-injection-pattern)
40
41
  [![PyPI Downloads](https://static.pepy.tech/badge/dependency-injection-pattern/week)](https://pepy.tech/projects/dependency-injection-pattern)
41
42
 
42
- # dependency-injection-pattern
43
- > A Python implementation of the Dependency Injection pattern.
43
+ # Dependency Injection Pattern (Python)
44
44
 
45
- This project extends python with a real dependency injection or Inversion of Control (IoC) implementation, relying on typing and inspection to achieve a seamless injection of dependencies.
45
+ A typed dependency injection framework for Python — focused on clarity, structure, and maintainability.
46
46
 
47
- Though not normally associated with Python applications, dependency injection can greatly reduce time spent during development and testing. Usually, in a longrunning Python application or service, you would put the container registration inside the `__main_.py` file and similar code in test files, where service implementations can easily be substituted.
47
+ ## 🚀 Why this exists
48
48
 
49
- ## How it works
50
- When a service is requested, the provider looks it up in the factory dictionary, which then gets or creates a fitting instance. Any parameters needed for instantiation is supplied (injected) by the provider.
49
+ As Python applications grow, managing dependencies becomes increasingly difficult:
51
50
 
52
- A service implementation may be inferred by a service in which case the service constructor is used, and correspondingly a service may be inferred by the return type of an implementation function.
51
+ - Hidden dependencies make systems harder to understand
52
+ - Tight coupling reduces flexibility
53
+ - Testing requires complex setup and mocking
54
+ - Structure becomes inconsistent across modules
53
55
 
54
- ## Conventions
55
- - All dependencies are resolved when container is sealed, which implements a fail-fast pattern. Only exceptions are optional dependencies or dependencies with a default value.
56
+ Unlike many other ecosystems, Python lacks a widely adopted, structured approach to dependency injection.
57
+
58
+ 👉 This project aims to introduce **clear, typed dependency management** to Python applications.
59
+
60
+
61
+ ## ✨ Features
62
+
63
+ - 🧩 Strongly typed dependency injection
64
+ - 🧠 Explicit dependency definitions
65
+ - 🔄 Clear separation of concerns
66
+ - 🧪 Improved testability
67
+ - 🏗 Support for structured application architecture
68
+ - ⚡ Lightweight and Python-native design
69
+
70
+
71
+ ## 📦 Use cases
72
+
73
+ This library is particularly useful when building:
74
+
75
+ - Backend services
76
+ - Data engineering workflows
77
+ - ETL pipelines
78
+ - Modular applications
79
+ - Systems with complex dependency graphs
80
+
81
+ 👉 Especially valuable in projects where maintainability and scalability matter.
82
+
83
+
84
+ ## 🏗 Design philosophy
85
+
86
+ This project is built around a few key principles:
87
+
88
+ ### 1. Explicit over implicit
89
+ Dependencies should be visible and well-defined — not hidden in implementation details.
90
+
91
+ ### 2. Type-driven structure
92
+ Type hints are used as a foundation for defining and resolving dependencies.
93
+
94
+ ### 3. Maintainability over convenience
95
+ The goal is not to reduce lines of code — but to make systems easier to evolve.
96
+
97
+ ### 4. Minimal abstraction
98
+ The framework avoids unnecessary complexity and stays close to Python’s core concepts.
99
+
100
+
101
+ ## 🔗 What this enables
102
+
103
+ By introducing structured dependency injection, you get:
104
+
105
+ - Clear boundaries between components
106
+ - Easier reasoning about system design
107
+ - More predictable runtime behavior
108
+ - Improved ability to refactor safely
109
+
110
+ 👉 This is especially powerful in larger systems where complexity grows over time.
111
+
112
+
113
+ ## ⚖️ Trade-offs
114
+
115
+ | Focus ✅ | Not a goal ❌ |
116
+ |---------|--------------|
117
+ | Structured architecture | Magic-based auto-wiring |
118
+ | Type safety | Implicit dependency resolution |
119
+ | Clarity and control | Minimal configuration at all costs |
120
+
121
+ 👉 This library prioritizes **explicit design over automation**.
122
+
123
+
124
+ ## 🧠 Inspiration
125
+
126
+ Inspired by dependency injection patterns from ecosystems such as:
127
+
128
+ - C# / .NET (built-in DI container)
129
+ - Java (Spring, Guice)
130
+
131
+ But intentionally adapted to fit Python’s dynamic nature
132
+ without introducing heavy frameworks or unnecessary indirection.
133
+
134
+
135
+ ## 🎯 When to use
136
+
137
+ Use this library if you:
138
+
139
+ - Are building medium to large Python applications
140
+ - Want clearer separation between components
141
+ - Need better testability
142
+ - Prefer structured architecture over ad-hoc patterns
143
+
144
+
145
+ ## 🚫 When not to use
146
+
147
+ This library may be unnecessary if:
148
+
149
+ - Your project is small or short-lived
150
+ - Dependency complexity is minimal
151
+ - Simplicity is more important than structure
152
+
153
+
154
+ ## 🔗 Related projects
155
+
156
+ Part of a broader focus on:
157
+
158
+ - Clean architecture
159
+ - Runtime abstractions
160
+ - Developer tooling in Python
161
+
162
+ 👉 https://github.com/apmadsen
163
+
164
+
165
+
166
+ ## 🤝 Contributing
167
+
168
+ Feedback, ideas, and contributions are welcome!
56
169
 
57
- - Singleton services may not depend on scoped or transient services, as this would negate the "transient" or "scoped" part since a singleton service would keep a reference to it's dependencies indefinitely.
58
170
 
59
171
  ## Example:
60
172
 
@@ -0,0 +1,182 @@
1
+ [![Test](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test.yml)
2
+ [![Coverage](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test-coverage.yml/badge.svg)](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test-coverage.yml)
3
+ [![Stable Version](https://img.shields.io/pypi/v/dependency-injection-pattern?label=stable&sort=semver&color=blue)](https://github.com/apmadsen/dependency-injection-pattern/releases)
4
+ ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/dependency-injection-pattern?label=pre-release&include_prereleases&sort=semver&color=blue)
5
+ ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dependency-injection-pattern)
6
+ [![PyPI Downloads](https://static.pepy.tech/badge/dependency-injection-pattern/week)](https://pepy.tech/projects/dependency-injection-pattern)
7
+
8
+ # Dependency Injection Pattern (Python)
9
+
10
+ A typed dependency injection framework for Python — focused on clarity, structure, and maintainability.
11
+
12
+ ## 🚀 Why this exists
13
+
14
+ As Python applications grow, managing dependencies becomes increasingly difficult:
15
+
16
+ - Hidden dependencies make systems harder to understand
17
+ - Tight coupling reduces flexibility
18
+ - Testing requires complex setup and mocking
19
+ - Structure becomes inconsistent across modules
20
+
21
+ Unlike many other ecosystems, Python lacks a widely adopted, structured approach to dependency injection.
22
+
23
+ 👉 This project aims to introduce **clear, typed dependency management** to Python applications.
24
+
25
+
26
+ ## ✨ Features
27
+
28
+ - 🧩 Strongly typed dependency injection
29
+ - 🧠 Explicit dependency definitions
30
+ - 🔄 Clear separation of concerns
31
+ - 🧪 Improved testability
32
+ - 🏗 Support for structured application architecture
33
+ - ⚡ Lightweight and Python-native design
34
+
35
+
36
+ ## 📦 Use cases
37
+
38
+ This library is particularly useful when building:
39
+
40
+ - Backend services
41
+ - Data engineering workflows
42
+ - ETL pipelines
43
+ - Modular applications
44
+ - Systems with complex dependency graphs
45
+
46
+ 👉 Especially valuable in projects where maintainability and scalability matter.
47
+
48
+
49
+ ## 🏗 Design philosophy
50
+
51
+ This project is built around a few key principles:
52
+
53
+ ### 1. Explicit over implicit
54
+ Dependencies should be visible and well-defined — not hidden in implementation details.
55
+
56
+ ### 2. Type-driven structure
57
+ Type hints are used as a foundation for defining and resolving dependencies.
58
+
59
+ ### 3. Maintainability over convenience
60
+ The goal is not to reduce lines of code — but to make systems easier to evolve.
61
+
62
+ ### 4. Minimal abstraction
63
+ The framework avoids unnecessary complexity and stays close to Python’s core concepts.
64
+
65
+
66
+ ## 🔗 What this enables
67
+
68
+ By introducing structured dependency injection, you get:
69
+
70
+ - Clear boundaries between components
71
+ - Easier reasoning about system design
72
+ - More predictable runtime behavior
73
+ - Improved ability to refactor safely
74
+
75
+ 👉 This is especially powerful in larger systems where complexity grows over time.
76
+
77
+
78
+ ## ⚖️ Trade-offs
79
+
80
+ | Focus ✅ | Not a goal ❌ |
81
+ |---------|--------------|
82
+ | Structured architecture | Magic-based auto-wiring |
83
+ | Type safety | Implicit dependency resolution |
84
+ | Clarity and control | Minimal configuration at all costs |
85
+
86
+ 👉 This library prioritizes **explicit design over automation**.
87
+
88
+
89
+ ## 🧠 Inspiration
90
+
91
+ Inspired by dependency injection patterns from ecosystems such as:
92
+
93
+ - C# / .NET (built-in DI container)
94
+ - Java (Spring, Guice)
95
+
96
+ But intentionally adapted to fit Python’s dynamic nature
97
+ without introducing heavy frameworks or unnecessary indirection.
98
+
99
+
100
+ ## 🎯 When to use
101
+
102
+ Use this library if you:
103
+
104
+ - Are building medium to large Python applications
105
+ - Want clearer separation between components
106
+ - Need better testability
107
+ - Prefer structured architecture over ad-hoc patterns
108
+
109
+
110
+ ## 🚫 When not to use
111
+
112
+ This library may be unnecessary if:
113
+
114
+ - Your project is small or short-lived
115
+ - Dependency complexity is minimal
116
+ - Simplicity is more important than structure
117
+
118
+
119
+ ## 🔗 Related projects
120
+
121
+ Part of a broader focus on:
122
+
123
+ - Clean architecture
124
+ - Runtime abstractions
125
+ - Developer tooling in Python
126
+
127
+ 👉 https://github.com/apmadsen
128
+
129
+
130
+
131
+ ## 🤝 Contributing
132
+
133
+ Feedback, ideas, and contributions are welcome!
134
+
135
+
136
+ ## Example:
137
+
138
+ ```python
139
+ from logging import Logger
140
+ from di import Container
141
+
142
+ def get_logger() -> Logger:
143
+ return Logger("app")
144
+
145
+ class Service1:
146
+ def get_value(self) -> str:
147
+ return "Some value"
148
+
149
+ class Service2:
150
+ def __init__(self, service1: Service1, log: Logger):
151
+ self.service1 = service1
152
+ self.log = log
153
+
154
+ def get_value(self) -> str:
155
+ self.log.debug("Someone requested value...")
156
+ return f"Service1 returned: {self.service1.get_value()}"
157
+
158
+ class Application:
159
+ def __init__(self, service2: Service2, log: Logger):
160
+ self.service2 = service2
161
+ log.info("Application starting")
162
+
163
+ def get_value(self) -> str:
164
+ return f"Service2 returned: {self.service2.get_value()}"
165
+
166
+
167
+ container = Container()
168
+ container.add_singleton(get_logger)
169
+ container.add_transient(Service1)
170
+ container.add_transient(Service2)
171
+ container.add_transient(Application)
172
+
173
+ provider = container.provider() # container is sealed beyond this point
174
+
175
+ app = provider.provide(Application)
176
+
177
+ value = app.get_value() # => "Service2 returned: Service1 returned: Some value"
178
+ ```
179
+
180
+ ## Full documentation
181
+
182
+ [Go to documentation](https://github.com/apmadsen/dependency-injection-pattern/blob/main/docs/documentation.md)
@@ -22,6 +22,7 @@ classifiers = [
22
22
  "Programming Language :: Python :: 3.11",
23
23
  "Programming Language :: Python :: 3.12",
24
24
  "Programming Language :: Python :: 3.13",
25
+ "Programming Language :: Python :: 3.14",
25
26
  "Programming Language :: Python",
26
27
  "Topic :: Software Development :: Libraries :: Python Modules",
27
28
  "Topic :: Software Development :: Libraries",
@@ -29,9 +30,9 @@ classifiers = [
29
30
  ]
30
31
  dependencies = [
31
32
  "typing-utilities >= 0.1.0, < 0.2",
32
- "runtime-reflection-lite >= 0.1.2, < 0.2"
33
+ "runtime-reflection-lite >= 0.2.0, < 0.3"
33
34
  ]
34
- requires-python = ">=3.10"
35
+ requires-python = ">= 3.10, < 3.15"
35
36
 
36
37
  [project.urls]
37
38
  repository = "https://github.com/apmadsen/dependency-injection-pattern"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: dependency-injection-pattern
3
- Version: 0.0.5
3
+ Version: 0.0.7
4
4
  Summary: Python implementation of the Dependency Injection pattern.
5
5
  Author-email: Anders Madsen <anders.madsen@alphavue.com>
6
6
  License-Expression: MIT
@@ -18,15 +18,16 @@ Classifier: Programming Language :: Python :: 3.10
18
18
  Classifier: Programming Language :: Python :: 3.11
19
19
  Classifier: Programming Language :: Python :: 3.12
20
20
  Classifier: Programming Language :: Python :: 3.13
21
+ Classifier: Programming Language :: Python :: 3.14
21
22
  Classifier: Programming Language :: Python
22
23
  Classifier: Topic :: Software Development :: Libraries :: Python Modules
23
24
  Classifier: Topic :: Software Development :: Libraries
24
25
  Classifier: Typing :: Typed
25
- Requires-Python: >=3.10
26
+ Requires-Python: <3.15,>=3.10
26
27
  Description-Content-Type: text/markdown
27
28
  License-File: LICENSE
28
29
  Requires-Dist: typing-utilities<0.2,>=0.1.0
29
- Requires-Dist: runtime-reflection-lite<0.2,>=0.1.2
30
+ Requires-Dist: runtime-reflection-lite<0.3,>=0.2.0
30
31
  Provides-Extra: test
31
32
  Requires-Dist: pytest>=8.3; extra == "test"
32
33
  Requires-Dist: pytest-cov>=6.1; extra == "test"
@@ -39,22 +40,133 @@ Dynamic: license-file
39
40
  ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dependency-injection-pattern)
40
41
  [![PyPI Downloads](https://static.pepy.tech/badge/dependency-injection-pattern/week)](https://pepy.tech/projects/dependency-injection-pattern)
41
42
 
42
- # dependency-injection-pattern
43
- > A Python implementation of the Dependency Injection pattern.
43
+ # Dependency Injection Pattern (Python)
44
44
 
45
- This project extends python with a real dependency injection or Inversion of Control (IoC) implementation, relying on typing and inspection to achieve a seamless injection of dependencies.
45
+ A typed dependency injection framework for Python — focused on clarity, structure, and maintainability.
46
46
 
47
- Though not normally associated with Python applications, dependency injection can greatly reduce time spent during development and testing. Usually, in a longrunning Python application or service, you would put the container registration inside the `__main_.py` file and similar code in test files, where service implementations can easily be substituted.
47
+ ## 🚀 Why this exists
48
48
 
49
- ## How it works
50
- When a service is requested, the provider looks it up in the factory dictionary, which then gets or creates a fitting instance. Any parameters needed for instantiation is supplied (injected) by the provider.
49
+ As Python applications grow, managing dependencies becomes increasingly difficult:
51
50
 
52
- A service implementation may be inferred by a service in which case the service constructor is used, and correspondingly a service may be inferred by the return type of an implementation function.
51
+ - Hidden dependencies make systems harder to understand
52
+ - Tight coupling reduces flexibility
53
+ - Testing requires complex setup and mocking
54
+ - Structure becomes inconsistent across modules
53
55
 
54
- ## Conventions
55
- - All dependencies are resolved when container is sealed, which implements a fail-fast pattern. Only exceptions are optional dependencies or dependencies with a default value.
56
+ Unlike many other ecosystems, Python lacks a widely adopted, structured approach to dependency injection.
57
+
58
+ 👉 This project aims to introduce **clear, typed dependency management** to Python applications.
59
+
60
+
61
+ ## ✨ Features
62
+
63
+ - 🧩 Strongly typed dependency injection
64
+ - 🧠 Explicit dependency definitions
65
+ - 🔄 Clear separation of concerns
66
+ - 🧪 Improved testability
67
+ - 🏗 Support for structured application architecture
68
+ - ⚡ Lightweight and Python-native design
69
+
70
+
71
+ ## 📦 Use cases
72
+
73
+ This library is particularly useful when building:
74
+
75
+ - Backend services
76
+ - Data engineering workflows
77
+ - ETL pipelines
78
+ - Modular applications
79
+ - Systems with complex dependency graphs
80
+
81
+ 👉 Especially valuable in projects where maintainability and scalability matter.
82
+
83
+
84
+ ## 🏗 Design philosophy
85
+
86
+ This project is built around a few key principles:
87
+
88
+ ### 1. Explicit over implicit
89
+ Dependencies should be visible and well-defined — not hidden in implementation details.
90
+
91
+ ### 2. Type-driven structure
92
+ Type hints are used as a foundation for defining and resolving dependencies.
93
+
94
+ ### 3. Maintainability over convenience
95
+ The goal is not to reduce lines of code — but to make systems easier to evolve.
96
+
97
+ ### 4. Minimal abstraction
98
+ The framework avoids unnecessary complexity and stays close to Python’s core concepts.
99
+
100
+
101
+ ## 🔗 What this enables
102
+
103
+ By introducing structured dependency injection, you get:
104
+
105
+ - Clear boundaries between components
106
+ - Easier reasoning about system design
107
+ - More predictable runtime behavior
108
+ - Improved ability to refactor safely
109
+
110
+ 👉 This is especially powerful in larger systems where complexity grows over time.
111
+
112
+
113
+ ## ⚖️ Trade-offs
114
+
115
+ | Focus ✅ | Not a goal ❌ |
116
+ |---------|--------------|
117
+ | Structured architecture | Magic-based auto-wiring |
118
+ | Type safety | Implicit dependency resolution |
119
+ | Clarity and control | Minimal configuration at all costs |
120
+
121
+ 👉 This library prioritizes **explicit design over automation**.
122
+
123
+
124
+ ## 🧠 Inspiration
125
+
126
+ Inspired by dependency injection patterns from ecosystems such as:
127
+
128
+ - C# / .NET (built-in DI container)
129
+ - Java (Spring, Guice)
130
+
131
+ But intentionally adapted to fit Python’s dynamic nature
132
+ without introducing heavy frameworks or unnecessary indirection.
133
+
134
+
135
+ ## 🎯 When to use
136
+
137
+ Use this library if you:
138
+
139
+ - Are building medium to large Python applications
140
+ - Want clearer separation between components
141
+ - Need better testability
142
+ - Prefer structured architecture over ad-hoc patterns
143
+
144
+
145
+ ## 🚫 When not to use
146
+
147
+ This library may be unnecessary if:
148
+
149
+ - Your project is small or short-lived
150
+ - Dependency complexity is minimal
151
+ - Simplicity is more important than structure
152
+
153
+
154
+ ## 🔗 Related projects
155
+
156
+ Part of a broader focus on:
157
+
158
+ - Clean architecture
159
+ - Runtime abstractions
160
+ - Developer tooling in Python
161
+
162
+ 👉 https://github.com/apmadsen
163
+
164
+
165
+
166
+ ## 🤝 Contributing
167
+
168
+ Feedback, ideas, and contributions are welcome!
56
169
 
57
- - Singleton services may not depend on scoped or transient services, as this would negate the "transient" or "scoped" part since a singleton service would keep a reference to it's dependencies indefinitely.
58
170
 
59
171
  ## Example:
60
172
 
@@ -1,5 +1,5 @@
1
1
  typing-utilities<0.2,>=0.1.0
2
- runtime-reflection-lite<0.2,>=0.1.2
2
+ runtime-reflection-lite<0.3,>=0.2.0
3
3
 
4
4
  [test]
5
5
  pytest>=8.3
@@ -1,6 +1,6 @@
1
1
  from __future__ import annotations
2
2
  from typing import TypeVar, Callable, Any, cast, overload, TYPE_CHECKING
3
- from typingutils import is_type, isinstance_typing, get_optional_type
3
+ from typingutils import is_type, isinstance_typing, issubclass_typing, get_optional_type
4
4
  from types import FunctionType
5
5
  from weakref import ref
6
6
  from runtime.reflection.lite import get_signature
@@ -282,7 +282,13 @@ class Container:
282
282
  elif isinstance(service, type) and issubclass(service, Provider): # pyright: ignore[reportUnnecessaryIsInstance]
283
283
  return True
284
284
  elif is_type(service):
285
- return service in self.__factories
285
+ if service in self.__factories:
286
+ return True
287
+ else:
288
+ # check for factories of derived types
289
+ for provided_service, _ in self.__factories.items():
290
+ if issubclass_typing(provided_service, cast(type[Any], service)):
291
+ return True
286
292
 
287
293
  return False
288
294
 
@@ -317,6 +323,15 @@ class Container:
317
323
  factory = self.__factories[cast(type, svc)]
318
324
  break
319
325
 
326
+ # check for factories of derived types
327
+ for svc in [svc_type, service]: # pyright: ignore[reportUnknownVariableType]
328
+ if is_type(svc):
329
+ for provided_service, provided_factory in self.__factories.items():
330
+ if issubclass_typing(provided_service, cast(type[Any], svc)):
331
+ factory = provided_factory
332
+ break
333
+
334
+
320
335
  return factory, default, is_optional
321
336
 
322
337
  def _get(self, service: type[T] | ServiceRequest[T]) -> Factory[T] | T | None:
@@ -42,7 +42,7 @@ class DefaultScope(Scope):
42
42
  try:
43
43
  if is_context(service):
44
44
  LOG.debug(f"Disposing service '{get_service_name(service)}' on thread '{current_thread().name}'")
45
- getattr(implementation, "__exit__")()
45
+ getattr(implementation, "__exit__")(None, None, None)
46
46
 
47
47
  except Exception as ex: # pragma: no cover
48
48
  # swallow exception, as it's caught in the finalizer thread
@@ -134,3 +134,23 @@ def test_generic_service():
134
134
  provider.provide(GenericClass[str])
135
135
  provider.provide(GenericClass[int])
136
136
 
137
+
138
+
139
+ def test_derived_service():
140
+ container = Container()
141
+ container.add_singleton(Service)
142
+ provider = container.provider()
143
+
144
+ # the container only has a factory for the Service class which
145
+ # is derived from the IService class, but subclass matching
146
+ # makes sure that both types can be provided
147
+
148
+ assert provider.provides(IService)
149
+ svc = provider.provide(IService)
150
+ assert isinstance(svc, IService)
151
+ assert isinstance(svc, Service)
152
+
153
+ assert provider.provides(Service)
154
+ svc = provider.provide(Service)
155
+ assert isinstance(svc, IService)
156
+ assert isinstance(svc, Service)
@@ -1,71 +0,0 @@
1
- [![Test](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test.yml/badge.svg)](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test.yml)
2
- [![Coverage](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test-coverage.yml/badge.svg)](https://github.com/apmadsen/dependency-injection-pattern/actions/workflows/python-test-coverage.yml)
3
- [![Stable Version](https://img.shields.io/pypi/v/dependency-injection-pattern?label=stable&sort=semver&color=blue)](https://github.com/apmadsen/dependency-injection-pattern/releases)
4
- ![Pre-release Version](https://img.shields.io/github/v/release/apmadsen/dependency-injection-pattern?label=pre-release&include_prereleases&sort=semver&color=blue)
5
- ![PyPI - Python Version](https://img.shields.io/pypi/pyversions/dependency-injection-pattern)
6
- [![PyPI Downloads](https://static.pepy.tech/badge/dependency-injection-pattern/week)](https://pepy.tech/projects/dependency-injection-pattern)
7
-
8
- # dependency-injection-pattern
9
- > A Python implementation of the Dependency Injection pattern.
10
-
11
- This project extends python with a real dependency injection or Inversion of Control (IoC) implementation, relying on typing and inspection to achieve a seamless injection of dependencies.
12
-
13
- Though not normally associated with Python applications, dependency injection can greatly reduce time spent during development and testing. Usually, in a longrunning Python application or service, you would put the container registration inside the `__main_.py` file and similar code in test files, where service implementations can easily be substituted.
14
-
15
- ## How it works
16
- When a service is requested, the provider looks it up in the factory dictionary, which then gets or creates a fitting instance. Any parameters needed for instantiation is supplied (injected) by the provider.
17
-
18
- A service implementation may be inferred by a service in which case the service constructor is used, and correspondingly a service may be inferred by the return type of an implementation function.
19
-
20
- ## Conventions
21
- - All dependencies are resolved when container is sealed, which implements a fail-fast pattern. Only exceptions are optional dependencies or dependencies with a default value.
22
-
23
- - Singleton services may not depend on scoped or transient services, as this would negate the "transient" or "scoped" part since a singleton service would keep a reference to it's dependencies indefinitely.
24
-
25
- ## Example:
26
-
27
- ```python
28
- from logging import Logger
29
- from di import Container
30
-
31
- def get_logger() -> Logger:
32
- return Logger("app")
33
-
34
- class Service1:
35
- def get_value(self) -> str:
36
- return "Some value"
37
-
38
- class Service2:
39
- def __init__(self, service1: Service1, log: Logger):
40
- self.service1 = service1
41
- self.log = log
42
-
43
- def get_value(self) -> str:
44
- self.log.debug("Someone requested value...")
45
- return f"Service1 returned: {self.service1.get_value()}"
46
-
47
- class Application:
48
- def __init__(self, service2: Service2, log: Logger):
49
- self.service2 = service2
50
- log.info("Application starting")
51
-
52
- def get_value(self) -> str:
53
- return f"Service2 returned: {self.service2.get_value()}"
54
-
55
-
56
- container = Container()
57
- container.add_singleton(get_logger)
58
- container.add_transient(Service1)
59
- container.add_transient(Service2)
60
- container.add_transient(Application)
61
-
62
- provider = container.provider() # container is sealed beyond this point
63
-
64
- app = provider.provide(Application)
65
-
66
- value = app.get_value() # => "Service2 returned: Service1 returned: Some value"
67
- ```
68
-
69
- ## Full documentation
70
-
71
- [Go to documentation](https://github.com/apmadsen/dependency-injection-pattern/blob/main/docs/documentation.md)