py-autowired 0.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.
- py_autowired-0.2.0/CHANGELOG.md +8 -0
- py_autowired-0.2.0/KULLANIM_KILAVUZU_TR.md +108 -0
- py_autowired-0.2.0/LICENSE +21 -0
- py_autowired-0.2.0/MANIFEST.in +6 -0
- py_autowired-0.2.0/PKG-INFO +78 -0
- py_autowired-0.2.0/README.md +47 -0
- py_autowired-0.2.0/USAGE_GUIDE_EN.md +102 -0
- py_autowired-0.2.0/examples/README.md +51 -0
- py_autowired-0.2.0/examples/console_app/main.py +56 -0
- py_autowired-0.2.0/examples/django_app/manage.py +25 -0
- py_autowired-0.2.0/examples/django_app/runtime_di_demo/settings.py +6 -0
- py_autowired-0.2.0/examples/django_app/runtime_di_demo/urls.py +9 -0
- py_autowired-0.2.0/examples/django_app/runtime_di_demo/views.py +45 -0
- py_autowired-0.2.0/examples/fastapi_app/app.py +78 -0
- py_autowired-0.2.0/examples/flask_app/app.py +64 -0
- py_autowired-0.2.0/examples/requirements.txt +4 -0
- py_autowired-0.2.0/examples/shared/application/use_cases/greetings/greeting_service.py +23 -0
- py_autowired-0.2.0/examples/shared/bootstrap/dependency_injection/composition.py +35 -0
- py_autowired-0.2.0/examples/shared/domain/contracts/formatting/message_formatter.py +7 -0
- py_autowired-0.2.0/examples/shared/domain/contracts/messaging/message_repository.py +7 -0
- py_autowired-0.2.0/examples/shared/domain/contracts/metadata/runtime_label_provider.py +7 -0
- py_autowired-0.2.0/examples/shared/infrastructure/persistence/adapters/memory/repositories/message_repository.py +26 -0
- py_autowired-0.2.0/examples/shared/infrastructure/presentation/formatting/turkish_message_formatter.py +27 -0
- py_autowired-0.2.0/examples/shared/infrastructure/runtime/environment/metadata/providers/runtime_label_provider.py +22 -0
- py_autowired-0.2.0/pyproject.toml +43 -0
- py_autowired-0.2.0/setup.cfg +4 -0
- py_autowired-0.2.0/src/py_autowired/autowired.py +775 -0
- py_autowired-0.2.0/src/py_autowired/container.py +521 -0
- py_autowired-0.2.0/src/py_autowired.egg-info/PKG-INFO +78 -0
- py_autowired-0.2.0/src/py_autowired.egg-info/SOURCES.txt +33 -0
- py_autowired-0.2.0/src/py_autowired.egg-info/dependency_links.txt +1 -0
- py_autowired-0.2.0/src/py_autowired.egg-info/requires.txt +11 -0
- py_autowired-0.2.0/src/py_autowired.egg-info/top_level.txt +1 -0
- py_autowired-0.2.0/tests/test_autowired.py +93 -0
- py_autowired-0.2.0/tests/test_examples.py +156 -0
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.2.0
|
|
4
|
+
|
|
5
|
+
- Restored the original runtime injection rule: self.x_instance = None.
|
|
6
|
+
- Removed provider-based and application-side resolve usage.
|
|
7
|
+
- Reduced the runtime package to autowired.py and container.py.
|
|
8
|
+
- Added layered Console, FastAPI, Flask and Django examples.
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
# py-autowired Türkçe Kullanım Kılavuzu
|
|
2
|
+
|
|
3
|
+
## Temel kural
|
|
4
|
+
|
|
5
|
+
Runtime bağımlılığı constructor parametresi veya manuel container çağrısı değildir:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
class SiparisServisi:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.siparis_repository_instance = None
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`auto_inject()`, alan adındaki `siparis_repository` bölümünü kayıtlı
|
|
14
|
+
`ISiparisRepository` veya `SiparisRepository` tipiyle eşleştirir ve nesneyi
|
|
15
|
+
çalışma anında yerleştirir.
|
|
16
|
+
|
|
17
|
+
## Kurulum
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install py-autowired
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
## Kayıt ve başlangıç
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
from py_autowired.autowired import auto_inject
|
|
27
|
+
from py_autowired.container import Container
|
|
28
|
+
|
|
29
|
+
container = Container()
|
|
30
|
+
container.register_singleton(IClock, SystemClock)
|
|
31
|
+
container.register_scoped(IUserRepository, SqliteUserRepository)
|
|
32
|
+
container.register_transient(UserService)
|
|
33
|
+
|
|
34
|
+
# Uygulama başlangıcında yalnız bir kez:
|
|
35
|
+
auto_inject(container, root_dir="/uygulamanin/mutlak/yolu")
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
`auto_inject()` çağrısından önce ilgili soyut ve somut sınıflar import edilmiş
|
|
39
|
+
ve kayıtlar tamamlanmış olmalıdır. Enjekte edilecek uygulama nesnelerini bu
|
|
40
|
+
çağrıdan sonra oluşturun.
|
|
41
|
+
|
|
42
|
+
## Yaşam süreleri
|
|
43
|
+
|
|
44
|
+
- `register_singleton`: Uygulama süresince aynı nesne.
|
|
45
|
+
- `register_scoped`: Bir scope içinde aynı, sonraki scope'ta yeni nesne.
|
|
46
|
+
- `register_transient`: Her enjeksiyonda yeni nesne.
|
|
47
|
+
- `register_instance`: Önceden oluşturulmuş nesneyi kullanır.
|
|
48
|
+
- `register_factory`: Parametresiz factory ile nesne oluşturur.
|
|
49
|
+
|
|
50
|
+
Console, Flask ve Django gibi senkron yollarda:
|
|
51
|
+
|
|
52
|
+
```python
|
|
53
|
+
with container.create_scope():
|
|
54
|
+
controller = UserController()
|
|
55
|
+
result = controller.execute()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
FastAPI gibi asenkron yollarda:
|
|
59
|
+
|
|
60
|
+
```python
|
|
61
|
+
async with container.create_scope():
|
|
62
|
+
controller = UserController()
|
|
63
|
+
result = await controller.execute()
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## Katmanlı mimari
|
|
67
|
+
|
|
68
|
+
- Domain: soyut repository/port.
|
|
69
|
+
- Infrastructure: somut repository/adaptör.
|
|
70
|
+
- Application: `self.repository_instance = None` kullanan servis.
|
|
71
|
+
- Composition: kayıtlar ve tek `auto_inject()` çağrısı.
|
|
72
|
+
- Presentation: Console, FastAPI, Flask veya Django controller/view.
|
|
73
|
+
|
|
74
|
+
Sunum ve uygulama katmanlarında manuel bağımlılık çözümleme çağrısı kullanılmaz. Ayrıntılı, çalışan örnekler `examples/` klasöründedir.
|
|
75
|
+
|
|
76
|
+
## İsim eşleştirme
|
|
77
|
+
|
|
78
|
+
Alan mutlaka küçük harfle başlamalı ve `_instance` ile bitmelidir:
|
|
79
|
+
|
|
80
|
+
- `user_repository_instance` → `IUserRepository`
|
|
81
|
+
- `payment_service_instance` → `PaymentService`
|
|
82
|
+
- `audit_instance` → `ABS_Audit`
|
|
83
|
+
|
|
84
|
+
Bir ad birden fazla kayda uyuyorsa modül yakınlığı kullanılır. Belirsiz kayıtları
|
|
85
|
+
önlemek için servis adlarını açık ve benzersiz tutun.
|
|
86
|
+
|
|
87
|
+
## Kırmızı çizgi
|
|
88
|
+
|
|
89
|
+
Tek geçerli bağımlılık bildirimi: self.repository_instance = None.
|
|
90
|
+
|
|
91
|
+
## IDLE ile tek tuşla çalıştırma
|
|
92
|
+
|
|
93
|
+
Terminal komutu yazmanız gerekmez. IDLE içinde aşağıdaki ana dosyalardan birini açın ve **Run > Run Module (F5)** seçin:
|
|
94
|
+
|
|
95
|
+
- Console: `examples/console_app/main.py`
|
|
96
|
+
- FastAPI: `examples/fastapi_app/app.py` — `http://127.0.0.1:8101/di-demo/Ayhan`
|
|
97
|
+
- Flask: `examples/flask_app/app.py` — `http://127.0.0.1:8102/di-demo/Ayhan`
|
|
98
|
+
- Django: `examples/django_app/manage.py` — `http://127.0.0.1:8103/di-demo/Ayhan`
|
|
99
|
+
|
|
100
|
+
Web örnekleri için `examples/requirements.txt` içindeki isteğe bağlı framework bağımlılıklarının bir kez kurulmuş olması gerekir. Kaynak klasöründen çalıştırırken ayrıca `PYTHONPATH` ayarlamanız gerekmez.
|
|
101
|
+
|
|
102
|
+
## Derin klasör kanıtı
|
|
103
|
+
|
|
104
|
+
`examples/shared` bilinçli olarak düz tutulmamıştır. Domain sözleşmeleri, use-case, repository, formatter, runtime sağlayıcı ve composition root farklı ve iç içe klasörlerdedir. Çalışan enjeksiyon zinciri şöyledir:
|
|
105
|
+
|
|
106
|
+
`Controller -> GreetingService -> MemoryMessageRepository -> TurkishMessageFormatter -> RuntimeLabelProvider`
|
|
107
|
+
|
|
108
|
+
Bu zincirdeki her bağımlılık yalnız `self.<ad>_instance = None` bildirimiyle yerleştirilir. Böylece örnekler, klasör ve modül derinliği arttığında da `auto_inject()` mekanizmasının çalıştığını doğrular.
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ayhan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: py-autowired
|
|
3
|
+
Version: 0.2.0
|
|
4
|
+
Summary: Runtime dependency injection through self.x_instance = None
|
|
5
|
+
Author: Ayhan
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Keywords: dependency-injection,autowiring,fastapi,flask,django
|
|
8
|
+
Classifier: Development Status :: 3 - Alpha
|
|
9
|
+
Classifier: Intended Audience :: Developers
|
|
10
|
+
Classifier: Programming Language :: Python :: 3
|
|
11
|
+
Classifier: Programming Language :: Python :: 3 :: Only
|
|
12
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
13
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
14
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.14
|
|
17
|
+
Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
|
|
18
|
+
Requires-Python: >=3.10
|
|
19
|
+
Description-Content-Type: text/markdown
|
|
20
|
+
License-File: LICENSE
|
|
21
|
+
Provides-Extra: dev
|
|
22
|
+
Requires-Dist: build>=1.2; extra == "dev"
|
|
23
|
+
Requires-Dist: pytest>=8; extra == "dev"
|
|
24
|
+
Requires-Dist: twine>=6; extra == "dev"
|
|
25
|
+
Provides-Extra: examples
|
|
26
|
+
Requires-Dist: django>=5.1; extra == "examples"
|
|
27
|
+
Requires-Dist: fastapi>=0.115; extra == "examples"
|
|
28
|
+
Requires-Dist: flask>=3.1; extra == "examples"
|
|
29
|
+
Requires-Dist: uvicorn>=0.30; extra == "examples"
|
|
30
|
+
Dynamic: license-file
|
|
31
|
+
|
|
32
|
+
# py-autowired
|
|
33
|
+
|
|
34
|
+
py-autowired injects runtime dependencies into fields declared with one rule:
|
|
35
|
+
|
|
36
|
+
```python
|
|
37
|
+
class UserService:
|
|
38
|
+
def __init__(self):
|
|
39
|
+
self.user_repository_instance = None
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
Application code contains no manual dependency-resolution calls. Register
|
|
43
|
+
the services once and call `auto_inject()` once during startup.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
from py_autowired.autowired import auto_inject
|
|
47
|
+
from py_autowired.container import Container
|
|
48
|
+
|
|
49
|
+
container = Container()
|
|
50
|
+
container.register_scoped(IUserRepository, SqliteUserRepository)
|
|
51
|
+
container.register_transient(UserService)
|
|
52
|
+
auto_inject(container, root_dir="/absolute/path/to/project")
|
|
53
|
+
|
|
54
|
+
with container.create_scope():
|
|
55
|
+
service = UserService()
|
|
56
|
+
print(service.user_repository_instance)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Runtime package
|
|
60
|
+
|
|
61
|
+
The installed runtime contains only:
|
|
62
|
+
|
|
63
|
+
```text
|
|
64
|
+
py_autowired/
|
|
65
|
+
autowired.py
|
|
66
|
+
container.py
|
|
67
|
+
```
|
|
68
|
+
|
|
69
|
+
Python namespace packages make `__init__.py` unnecessary.
|
|
70
|
+
|
|
71
|
+
## Frameworks
|
|
72
|
+
|
|
73
|
+
The same core works with Console, FastAPI/ASGI, Flask/WSGI and Django. Framework
|
|
74
|
+
examples are under `examples/`; each uses domain, application, infrastructure,
|
|
75
|
+
composition and presentation layers without manual dependency resolution.
|
|
76
|
+
|
|
77
|
+
See [KULLANIM_KILAVUZU_TR.md](KULLANIM_KILAVUZU_TR.md) and
|
|
78
|
+
[USAGE_GUIDE_EN.md](USAGE_GUIDE_EN.md).
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# py-autowired
|
|
2
|
+
|
|
3
|
+
py-autowired injects runtime dependencies into fields declared with one rule:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
class UserService:
|
|
7
|
+
def __init__(self):
|
|
8
|
+
self.user_repository_instance = None
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
Application code contains no manual dependency-resolution calls. Register
|
|
12
|
+
the services once and call `auto_inject()` once during startup.
|
|
13
|
+
|
|
14
|
+
```python
|
|
15
|
+
from py_autowired.autowired import auto_inject
|
|
16
|
+
from py_autowired.container import Container
|
|
17
|
+
|
|
18
|
+
container = Container()
|
|
19
|
+
container.register_scoped(IUserRepository, SqliteUserRepository)
|
|
20
|
+
container.register_transient(UserService)
|
|
21
|
+
auto_inject(container, root_dir="/absolute/path/to/project")
|
|
22
|
+
|
|
23
|
+
with container.create_scope():
|
|
24
|
+
service = UserService()
|
|
25
|
+
print(service.user_repository_instance)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
## Runtime package
|
|
29
|
+
|
|
30
|
+
The installed runtime contains only:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
py_autowired/
|
|
34
|
+
autowired.py
|
|
35
|
+
container.py
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
Python namespace packages make `__init__.py` unnecessary.
|
|
39
|
+
|
|
40
|
+
## Frameworks
|
|
41
|
+
|
|
42
|
+
The same core works with Console, FastAPI/ASGI, Flask/WSGI and Django. Framework
|
|
43
|
+
examples are under `examples/`; each uses domain, application, infrastructure,
|
|
44
|
+
composition and presentation layers without manual dependency resolution.
|
|
45
|
+
|
|
46
|
+
See [KULLANIM_KILAVUZU_TR.md](KULLANIM_KILAVUZU_TR.md) and
|
|
47
|
+
[USAGE_GUIDE_EN.md](USAGE_GUIDE_EN.md).
|
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# py-autowired English Usage Guide
|
|
2
|
+
|
|
3
|
+
## Core rule
|
|
4
|
+
|
|
5
|
+
Declare every runtime dependency as a field ending in `_instance`:
|
|
6
|
+
|
|
7
|
+
```python
|
|
8
|
+
class OrderService:
|
|
9
|
+
def __init__(self):
|
|
10
|
+
self.order_repository_instance = None
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
`auto_inject()` matches `order_repository` with a registered
|
|
14
|
+
`IOrderRepository` or `OrderRepository` and assigns the object at runtime.
|
|
15
|
+
Application code does not resolve dependencies manually.
|
|
16
|
+
|
|
17
|
+
## Setup
|
|
18
|
+
|
|
19
|
+
```bash
|
|
20
|
+
pip install py-autowired
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
```python
|
|
24
|
+
from py_autowired.autowired import auto_inject
|
|
25
|
+
from py_autowired.container import Container
|
|
26
|
+
|
|
27
|
+
container = Container()
|
|
28
|
+
container.register_singleton(IClock, SystemClock)
|
|
29
|
+
container.register_scoped(IUserRepository, SqliteUserRepository)
|
|
30
|
+
container.register_transient(UserService)
|
|
31
|
+
|
|
32
|
+
# Exactly once during startup:
|
|
33
|
+
auto_inject(container, root_dir="/absolute/path/to/application")
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Import implementations and complete registrations before `auto_inject()`.
|
|
37
|
+
Create injectable application objects only after that call.
|
|
38
|
+
|
|
39
|
+
## Lifetimes
|
|
40
|
+
|
|
41
|
+
- `register_singleton`: one object for the application lifetime.
|
|
42
|
+
- `register_scoped`: one object per active scope.
|
|
43
|
+
- `register_transient`: a new object per injection.
|
|
44
|
+
- `register_instance`: use an existing object.
|
|
45
|
+
- `register_factory`: create objects through a zero-argument factory.
|
|
46
|
+
|
|
47
|
+
Synchronous request or operation:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
with container.create_scope():
|
|
51
|
+
result = UserController().execute()
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Asynchronous request or operation:
|
|
55
|
+
|
|
56
|
+
```python
|
|
57
|
+
async with container.create_scope():
|
|
58
|
+
result = await UserController().execute()
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
## Layered architecture
|
|
62
|
+
|
|
63
|
+
- Domain: repository/port abstractions.
|
|
64
|
+
- Infrastructure: concrete repositories/adapters.
|
|
65
|
+
- Application: services declaring `self.repository_instance = None`.
|
|
66
|
+
- Composition: registrations and the single `auto_inject()` call.
|
|
67
|
+
- Presentation: Console, FastAPI, Flask or Django controller/view.
|
|
68
|
+
|
|
69
|
+
Presentation and application layers contain no manual dependency-resolution calls. Runnable examples are available under `examples/`.
|
|
70
|
+
|
|
71
|
+
## Naming
|
|
72
|
+
|
|
73
|
+
Fields start with a lowercase letter and end with `_instance`:
|
|
74
|
+
|
|
75
|
+
- `user_repository_instance` → `IUserRepository`
|
|
76
|
+
- `payment_service_instance` → `PaymentService`
|
|
77
|
+
- `audit_instance` → `ABS_Audit`
|
|
78
|
+
|
|
79
|
+
Keep service names unique to avoid ambiguous name matching.
|
|
80
|
+
|
|
81
|
+
## Non-negotiable pattern
|
|
82
|
+
|
|
83
|
+
The only valid dependency declaration is: self.repository_instance = None.
|
|
84
|
+
|
|
85
|
+
## One-click run from IDLE
|
|
86
|
+
|
|
87
|
+
No terminal command is required. Open one of these entry-point files in IDLE and choose **Run > Run Module (F5)**:
|
|
88
|
+
|
|
89
|
+
- Console: `examples/console_app/main.py`
|
|
90
|
+
- FastAPI: `examples/fastapi_app/app.py` — `http://127.0.0.1:8101/di-demo/Ayhan`
|
|
91
|
+
- Flask: `examples/flask_app/app.py` — `http://127.0.0.1:8102/di-demo/Ayhan`
|
|
92
|
+
- Django: `examples/django_app/manage.py` — `http://127.0.0.1:8103/di-demo/Ayhan`
|
|
93
|
+
|
|
94
|
+
Install the optional framework dependencies listed in `examples/requirements.txt` once before running the web examples. No manual `PYTHONPATH` setting is needed when running from the source tree.
|
|
95
|
+
|
|
96
|
+
## Deep-folder proof
|
|
97
|
+
|
|
98
|
+
`examples/shared` is deliberately not flat. Domain contracts, use case, repository, formatter, runtime provider, and composition root live in separate nested folders. The working injection chain is:
|
|
99
|
+
|
|
100
|
+
`Controller -> GreetingService -> MemoryMessageRepository -> TurkishMessageFormatter -> RuntimeLabelProvider`
|
|
101
|
+
|
|
102
|
+
Every dependency in this chain is populated only from a `self.<name>_instance = None` declaration. The examples therefore verify that `auto_inject()` continues to work as folder and module depth increases.
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
# Deep layered examples
|
|
2
|
+
|
|
3
|
+
All four presentation technologies use the same deliberately deep application tree. The depth is intentional: it proves that `auto_inject()` discovers and patches runtime dependencies across nested folders instead of working only when classes share one directory.
|
|
4
|
+
|
|
5
|
+
```text
|
|
6
|
+
examples/
|
|
7
|
+
shared/
|
|
8
|
+
domain/contracts/
|
|
9
|
+
messaging/message_repository.py
|
|
10
|
+
formatting/message_formatter.py
|
|
11
|
+
metadata/runtime_label_provider.py
|
|
12
|
+
application/use_cases/greetings/
|
|
13
|
+
greeting_service.py
|
|
14
|
+
infrastructure/
|
|
15
|
+
persistence/adapters/memory/repositories/message_repository.py
|
|
16
|
+
presentation/formatting/turkish_message_formatter.py
|
|
17
|
+
runtime/environment/metadata/providers/runtime_label_provider.py
|
|
18
|
+
bootstrap/dependency_injection/
|
|
19
|
+
composition.py
|
|
20
|
+
console_app/main.py
|
|
21
|
+
fastapi_app/app.py
|
|
22
|
+
flask_app/app.py
|
|
23
|
+
django_app/manage.py
|
|
24
|
+
```
|
|
25
|
+
|
|
26
|
+
The injected chain crosses all those layers:
|
|
27
|
+
|
|
28
|
+
```text
|
|
29
|
+
Presentation controller
|
|
30
|
+
-> GreetingService
|
|
31
|
+
-> MemoryMessageRepository
|
|
32
|
+
-> TurkishMessageFormatter
|
|
33
|
+
-> RuntimeLabelProvider
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
Every consumer declares dependencies only as `self.<name>_instance = None`. Application and presentation code contains no manual dependency-resolution calls. No `__init__.py` files are required for this namespace-package example.
|
|
37
|
+
|
|
38
|
+
## Run directly from IDLE or an editor
|
|
39
|
+
|
|
40
|
+
Open one of these entry-point files and choose **Run > Run Module (F5)** in IDLE:
|
|
41
|
+
|
|
42
|
+
- `console_app/main.py`: prints the complete chain's result in the IDLE shell.
|
|
43
|
+
- `fastapi_app/app.py`: starts at `http://127.0.0.1:8101/di-demo/Ayhan`.
|
|
44
|
+
- `flask_app/app.py`: starts at `http://127.0.0.1:8102/di-demo/Ayhan`.
|
|
45
|
+
- `django_app/manage.py`: starts at `http://127.0.0.1:8103/di-demo/Ayhan`.
|
|
46
|
+
|
|
47
|
+
No terminal command or manual `PYTHONPATH` setting is required. Install the optional framework dependencies from `examples/requirements.txt` once before running the web examples.
|
|
48
|
+
|
|
49
|
+
## Visible proof in every output
|
|
50
|
+
|
|
51
|
+
Console prints every chain level line by line. FastAPI, Flask, and Django return the same five entries in the `injection_trace` JSON field. Each entry includes `module`, `module_depth`, `class`, `injected_field`, `injected_type`, `method`, `result`, and `instance_id`, so successful deep auto-injection is directly observable instead of inferred from one greeting.
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import sys
|
|
2
|
+
from pathlib import Path
|
|
3
|
+
|
|
4
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
5
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
6
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
7
|
+
|
|
8
|
+
from examples.shared.bootstrap.dependency_injection.composition import configure_dependencies
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ConsoleController:
|
|
12
|
+
def __init__(self):
|
|
13
|
+
self.greeting_service_instance = None
|
|
14
|
+
|
|
15
|
+
def execute(self, name: str) -> dict[str, object]:
|
|
16
|
+
message = self.greeting_service_instance.greet(name)
|
|
17
|
+
controller_trace = {
|
|
18
|
+
"chain_level": 1,
|
|
19
|
+
"module_depth": len(type(self).__module__.split(".")),
|
|
20
|
+
"module": type(self).__module__,
|
|
21
|
+
"class": type(self).__name__,
|
|
22
|
+
"injected_field": "greeting_service_instance",
|
|
23
|
+
"injected_type": type(self.greeting_service_instance).__name__,
|
|
24
|
+
"method": "execute()",
|
|
25
|
+
"result": message,
|
|
26
|
+
"instance_id": id(self),
|
|
27
|
+
}
|
|
28
|
+
return {
|
|
29
|
+
"message": message,
|
|
30
|
+
"injection_trace": [
|
|
31
|
+
controller_trace,
|
|
32
|
+
*self.greeting_service_instance.injection_trace(name),
|
|
33
|
+
],
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
def main() -> None:
|
|
38
|
+
container = configure_dependencies()
|
|
39
|
+
with container.create_scope():
|
|
40
|
+
result = ConsoleController().execute("Console")
|
|
41
|
+
print(result["message"])
|
|
42
|
+
print("\nAUTO_INJECT DERINLIK ZINCIRI")
|
|
43
|
+
for step in result["injection_trace"]:
|
|
44
|
+
print(
|
|
45
|
+
f"[{step['chain_level']}] {step['module']} "
|
|
46
|
+
f"(modul derinligi={step['module_depth']})"
|
|
47
|
+
)
|
|
48
|
+
print(
|
|
49
|
+
f" {step['class']}.{step['method']} -> {step['result']} "
|
|
50
|
+
f"| inject: {step['injected_field']} = {step['injected_type']} "
|
|
51
|
+
f"| instance_id={step['instance_id']}"
|
|
52
|
+
)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
if __name__ == "__main__":
|
|
56
|
+
main()
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import os
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
6
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
7
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
8
|
+
|
|
9
|
+
os.environ.setdefault("DJANGO_SETTINGS_MODULE", "examples.django_app.runtime_di_demo.settings")
|
|
10
|
+
|
|
11
|
+
from django.core.management import execute_from_command_line
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
def main() -> None:
|
|
15
|
+
arguments = sys.argv
|
|
16
|
+
if len(arguments) == 1:
|
|
17
|
+
arguments = [arguments[0], "runserver", "127.0.0.1:8103", "--noreload"]
|
|
18
|
+
print("py-autowired: 5 katmanli Runtime DI demosu")
|
|
19
|
+
print("Tarayicida acin: http://127.0.0.1:8103/di-demo/Ayhan")
|
|
20
|
+
print("JSON cevabinda injection_trace alanini inceleyin.")
|
|
21
|
+
execute_from_command_line(arguments)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
if __name__ == "__main__":
|
|
25
|
+
main()
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
from django.http import HttpResponse, JsonResponse
|
|
2
|
+
|
|
3
|
+
from examples.shared.bootstrap.dependency_injection.composition import configure_dependencies
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
DEMO_HOME_HTML = """<!doctype html>
|
|
7
|
+
<html lang="tr"><head><meta charset="utf-8"><title>py-autowired DI Demo</title></head>
|
|
8
|
+
<body><h1>py-autowired Runtime DI Demo</h1>
|
|
9
|
+
<p>Beş katmanlı auto-inject zincirini JSON olarak görmek için bağlantıyı açın.</p>
|
|
10
|
+
<p><a href="/di-demo/Ayhan">/di-demo/Ayhan</a></p></body></html>"""
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class DjangoController:
|
|
14
|
+
def __init__(self):
|
|
15
|
+
self.greeting_service_instance = None
|
|
16
|
+
|
|
17
|
+
def execute(self, name: str) -> dict[str, object]:
|
|
18
|
+
message = self.greeting_service_instance.greet(name)
|
|
19
|
+
return {
|
|
20
|
+
"message": message,
|
|
21
|
+
"injection_trace": [
|
|
22
|
+
{
|
|
23
|
+
"chain_level": 1,
|
|
24
|
+
"module_depth": len(type(self).__module__.split(".")),
|
|
25
|
+
"module": type(self).__module__,
|
|
26
|
+
"class": type(self).__name__,
|
|
27
|
+
"injected_field": "greeting_service_instance",
|
|
28
|
+
"injected_type": type(self.greeting_service_instance).__name__,
|
|
29
|
+
"method": "execute()",
|
|
30
|
+
"result": message,
|
|
31
|
+
"instance_id": id(self),
|
|
32
|
+
},
|
|
33
|
+
*self.greeting_service_instance.injection_trace(name),
|
|
34
|
+
],
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def index(_request):
|
|
39
|
+
return HttpResponse(DEMO_HOME_HTML)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
def di_demo(_request, name: str):
|
|
43
|
+
container = configure_dependencies()
|
|
44
|
+
with container.create_scope():
|
|
45
|
+
return JsonResponse(DjangoController().execute(name))
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
from contextlib import asynccontextmanager
|
|
2
|
+
import sys
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
PROJECT_ROOT = Path(__file__).resolve().parents[2]
|
|
6
|
+
if str(PROJECT_ROOT) not in sys.path:
|
|
7
|
+
sys.path.insert(0, str(PROJECT_ROOT))
|
|
8
|
+
|
|
9
|
+
from fastapi import FastAPI
|
|
10
|
+
from fastapi.responses import HTMLResponse
|
|
11
|
+
from examples.shared.bootstrap.dependency_injection.composition import configure_dependencies
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
DEMO_HOME_HTML = """<!doctype html>
|
|
15
|
+
<html lang="tr"><head><meta charset="utf-8"><title>py-autowired DI Demo</title></head>
|
|
16
|
+
<body><h1>py-autowired Runtime DI Demo</h1>
|
|
17
|
+
<p>Beş katmanlı auto-inject zincirini JSON olarak görmek için bağlantıyı açın.</p>
|
|
18
|
+
<p><a href="/di-demo/Ayhan">/di-demo/Ayhan</a></p></body></html>"""
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class FastApiController:
|
|
22
|
+
def __init__(self):
|
|
23
|
+
self.greeting_service_instance = None
|
|
24
|
+
|
|
25
|
+
def execute(self, name: str) -> dict[str, object]:
|
|
26
|
+
message = self.greeting_service_instance.greet(name)
|
|
27
|
+
return {
|
|
28
|
+
"message": message,
|
|
29
|
+
"injection_trace": [
|
|
30
|
+
{
|
|
31
|
+
"chain_level": 1,
|
|
32
|
+
"module_depth": len(type(self).__module__.split(".")),
|
|
33
|
+
"module": type(self).__module__,
|
|
34
|
+
"class": type(self).__name__,
|
|
35
|
+
"injected_field": "greeting_service_instance",
|
|
36
|
+
"injected_type": type(self.greeting_service_instance).__name__,
|
|
37
|
+
"method": "execute()",
|
|
38
|
+
"result": message,
|
|
39
|
+
"instance_id": id(self),
|
|
40
|
+
},
|
|
41
|
+
*self.greeting_service_instance.injection_trace(name),
|
|
42
|
+
],
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
@asynccontextmanager
|
|
47
|
+
async def lifespan(_app: FastAPI):
|
|
48
|
+
configure_dependencies()
|
|
49
|
+
yield
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
app = FastAPI(lifespan=lifespan)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
@app.get("/", response_class=HTMLResponse)
|
|
56
|
+
async def index():
|
|
57
|
+
return HTMLResponse(DEMO_HOME_HTML)
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
@app.get("/di-demo/{name}")
|
|
61
|
+
async def di_demo(name: str):
|
|
62
|
+
container = configure_dependencies()
|
|
63
|
+
async with container.create_scope():
|
|
64
|
+
return FastApiController().execute(name)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
@app.get("/hello/{name}", include_in_schema=False)
|
|
68
|
+
async def hello(name: str):
|
|
69
|
+
return await di_demo(name)
|
|
70
|
+
|
|
71
|
+
|
|
72
|
+
if __name__ == "__main__":
|
|
73
|
+
import uvicorn
|
|
74
|
+
|
|
75
|
+
print("py-autowired: 5 katmanli Runtime DI demosu")
|
|
76
|
+
print("Tarayicida acin: http://127.0.0.1:8101/di-demo/Ayhan")
|
|
77
|
+
print("JSON cevabinda injection_trace alanini inceleyin.")
|
|
78
|
+
uvicorn.run(app, host="127.0.0.1", port=8101)
|