appctx 0.2.0__tar.gz → 0.2.2__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.
appctx-0.2.2/PKG-INFO ADDED
@@ -0,0 +1,272 @@
1
+ Metadata-Version: 2.4
2
+ Name: appctx
3
+ Version: 0.2.2
4
+ Summary: Spring style dependency injection for python
5
+ Author-email: wssccc <wssccc@qq.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/wssccc/appctx
8
+ Project-URL: Repository, https://github.com/wssccc/appctx
9
+ Project-URL: Bug Tracker, https://github.com/wssccc/appctx/issues
10
+ Project-URL: Documentation, https://github.com/wssccc/appctx#readme
11
+ Classifier: Development Status :: 3 - Alpha
12
+ Classifier: Intended Audience :: Developers
13
+ Classifier: Programming Language :: Python :: 3.8
14
+ Classifier: Programming Language :: Python :: 3.9
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
18
+ Requires-Python: >=3.8
19
+ Description-Content-Type: text/markdown
20
+ Provides-Extra: dev
21
+ Requires-Dist: pytest>=7.0; extra == "dev"
22
+ Requires-Dist: pytest-cov>=4.0; extra == "dev"
23
+ Requires-Dist: black>=22.0; extra == "dev"
24
+ Requires-Dist: flake8>=5.0; extra == "dev"
25
+ Requires-Dist: mypy>=1.0; extra == "dev"
26
+ Requires-Dist: isort>=5.0; extra == "dev"
27
+
28
+ # AppCtx
29
+
30
+ Spring-style dependency injection for Python
31
+
32
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://python.org)
33
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/wssccc/appctx/blob/main/LICENSE)
34
+ [![PyPI Version](https://img.shields.io/pypi/v/appctx.svg)](https://pypi.org/project/appctx/)
35
+
36
+ ## Overview
37
+
38
+ AppCtx is a lightweight dependency injection (DI) container for Python, inspired by the Spring Framework. It uses decorator-based bean registration and auto-wiring via type annotations to manage dependencies in a clean, testable way.
39
+
40
+ **Python Requirements**: 3.8+ | **License**: MIT
41
+
42
+ ### Features
43
+
44
+ - 🚀 **Decorator-based registration** — `@bean` / `@component` to register, `@post_construct` for lifecycle hooks
45
+ - 🔄 **Auto-wiring** — Dependencies resolved from type annotations automatically
46
+ - 🔍 **Circular dependency detection** — Caught at startup, not runtime
47
+ - 📦 **Lightweight** — Zero mandatory dependencies beyond stdlib
48
+ - 🐍 **Pythonic API** — Feels natural in Python, not a Java port
49
+ - 🏗️ **Multiple contexts** — Isolated containers for different scopes
50
+
51
+ ## Installation
52
+
53
+ ```bash
54
+ pip install appctx
55
+ ```
56
+
57
+ ## Quick Start
58
+
59
+ ### 1. Define and register beans
60
+
61
+ Use `@bean` on functions that create your objects. Dependencies are declared via type annotations:
62
+
63
+ ```python
64
+ from appctx import bean, get_bean, refresh
65
+
66
+ class DatabaseService:
67
+ def __init__(self, connection_string: str = "sqlite:///default.db"):
68
+ self.connection_string = connection_string
69
+
70
+ def connect(self):
71
+ return f"Connected to {self.connection_string}"
72
+
73
+ class UserService:
74
+ def __init__(self, db: DatabaseService):
75
+ self.db = db
76
+
77
+ def get_user(self, user_id: int):
78
+ return f"User {user_id} from {self.db.connect()}"
79
+
80
+ # Register beans
81
+ @bean
82
+ def database_service():
83
+ return DatabaseService("postgresql://localhost/myapp")
84
+
85
+ @bean
86
+ def user_service(db: DatabaseService): # Auto-injected by type
87
+ return UserService(db)
88
+ ```
89
+
90
+ ### 2. Initialize and use
91
+
92
+ ```python
93
+ # Initialize the container — resolves all dependencies
94
+ refresh()
95
+
96
+ # Retrieve the root application object (the only place you should call get_bean)
97
+ user_svc = get_bean(UserService)
98
+ print(user_svc.get_user(123))
99
+ ```
100
+
101
+ > **Note:** `get_bean()` is only needed at the **entry point** to bootstrap your application. All other dependencies are injected automatically via constructor or function parameters — just like Spring.
102
+
103
+ ### Class-style beans
104
+
105
+ Decorate classes directly — the container instantiates them with resolved dependencies:
106
+
107
+ ```python
108
+ from appctx import bean, get_bean, refresh
109
+
110
+ @bean
111
+ class EmailService:
112
+ def __init__(self):
113
+ self.server = "smtp.example.com"
114
+
115
+ def send_email(self, to: str, subject: str):
116
+ return f"Email sent to {to} via {self.server}"
117
+
118
+ @bean
119
+ class NotificationService:
120
+ def __init__(self, email: EmailService):
121
+ self.email = email
122
+
123
+ def notify(self, user: str, message: str):
124
+ return self.email.send_email(user, "Notification", message)
125
+
126
+ refresh()
127
+
128
+ # Entry point — the only place you should call get_bean
129
+ notification_svc = get_bean(NotificationService)
130
+ print(notification_svc.notify("user@example.com", "Hello World!"))
131
+ ```
132
+
133
+ ### Post-construct lifecycle
134
+
135
+ Use `@post_construct` to run initialization logic after all beans are created:
136
+
137
+ ```python
138
+ from appctx import post_construct
139
+
140
+ class DatabaseService:
141
+ def __init__(self):
142
+ self.connection = None
143
+
144
+ @post_construct
145
+ def init(self):
146
+ self.connection = create_connection() # Called automatically after construction
147
+
148
+ @bean
149
+ def database_service():
150
+ return DatabaseService()
151
+ ```
152
+
153
+ > **Note:** `@post_construct` methods run after **all** beans are created, so you can safely reference other beans during initialization.
154
+
155
+ ### How It Works
156
+
157
+ 1. **Bean Registration**: Use the `@bean` decorator to register functions/classes that create beans
158
+ 2. **Auto-wiring**: Dependencies are resolved from type annotations and injected automatically — no manual `get_bean()` calls needed
159
+ 3. **Container Initialization**: Call `refresh()` to instantiate all beans in dependency order
160
+ 4. **Entry Point**: Use `get_bean()` **once** at the application root to retrieve the assembled object graph
161
+
162
+ ## Core Concepts
163
+
164
+ | Concept | Description |
165
+ |---|---|
166
+ | **Bean** | An object managed by the container |
167
+ | **ApplicationContext** | The DI container that holds and wires beans |
168
+ | **`@bean`** | Decorator that registers a function/class as a bean factory (alias: `@component`) |
169
+ | **`refresh()`** | Initializes the container, instantiates all beans in dependency order |
170
+ | **`get_bean(T)`** | Retrieve a bean by type or name (low-level; prefer auto-wiring) |
171
+ | **`get_beans(T)`** | Retrieve all beans of a given type (low-level; prefer auto-wiring) |
172
+
173
+ ### Dependency Resolution Rules
174
+
175
+ 1. **Positional arguments** → resolved by type annotation
176
+ 2. **Keyword-only arguments** (`*, name`) → resolved by parameter name, falls back to default
177
+ 3. **`**kwargs`** → receives all remaining beans not consumed by other parameters
178
+
179
+ ## Global Default Context
180
+
181
+ AppCtx provides a **global default context** for convenience, similar to Spring's application context. The top-level functions (`bean`, `refresh`, `get_bean`, `add`) operate on this shared instance:
182
+
183
+ ```python
184
+ from appctx import bean, refresh, get_bean
185
+
186
+ # These all use the same global ApplicationContext behind the scenes
187
+ @bean
188
+ def my_service():
189
+ return MyService()
190
+
191
+ refresh()
192
+ app = get_bean(MyService)
193
+ ```
194
+
195
+ ### Custom Context (Isolation)
196
+
197
+ For libraries or tests that need isolation, create your own `ApplicationContext`:
198
+
199
+ ```python
200
+ from appctx import ApplicationContext
201
+
202
+ ctx = ApplicationContext()
203
+
204
+ @ctx.bean
205
+ def my_service():
206
+ return MyService()
207
+
208
+ ctx.refresh()
209
+ app = ctx.get_bean(MyService)
210
+ ```
211
+
212
+ > **Note:** `@bean` on the global context registers beans immediately when the module is imported. For custom contexts, use `@ctx.bean` to register against that specific instance.
213
+
214
+ ## Organizing Beans Across Modules
215
+
216
+ AppCtx does **not** perform automatic package scanning. Instead, beans are registered at import time — when Python loads a module, any `@bean` decorators execute and register with the target context.
217
+
218
+ To wire beans across multiple modules, simply **import** them before calling `refresh()`:
219
+
220
+ ```python
221
+ # main.py
222
+ from appctx import bean, refresh, get_bean
223
+
224
+ # Import modules so their @bean decorators fire
225
+ import myapp.services.user # registers UserService
226
+ import myapp.services.email # registers EmailService
227
+ import myapp.config # registers ConfigService
228
+
229
+ refresh()
230
+ app = get_bean(UserService)
231
+ ```
232
+
233
+ You can also use `add()` to register a plain function or class (without the `@bean` decorator):
234
+
235
+ ```python
236
+ from appctx import add
237
+
238
+ def some_service():
239
+ return SomeService()
240
+
241
+ add(some_service) # Equivalent to @bean on some_service
242
+ ```
243
+
244
+ > **Note:** For modules, simply importing them is sufficient — the `@bean` decorators fire at import time. `add(module)` is available as a semantic marker but the import alone triggers registration.
245
+
246
+ ## Best Practices
247
+
248
+ 1. **Rely on auto-wiring, avoid `get_bean`** — Like Spring, let the container inject dependencies via function/constructor parameters. Reserve `get_bean()` only for bootstrapping the root application object.
249
+ 2. **Use type annotations** — They are the primary mechanism for dependency resolution. Always annotate positional parameters.
250
+ 3. **Single responsibility per bean** — Each bean should have one clear purpose.
251
+ 4. **Prefer constructor injection** — Dependencies go in `__init__` parameters, not hidden inside methods.
252
+ 5. **Keep beans stateless when possible** — Makes testing and reasoning easier.
253
+ 6. **Use `@post_construct` for initialization** — Not `__init__` side-effects. Post-construct runs after all beans exist, allowing cross-bean setup.
254
+ 7. **Separate configuration from logic** — Use dedicated config beans instead of hardcoding values.
255
+ 8. **Design for testability** — Beans that accept dependencies via constructor are trivially mockable in tests.
256
+
257
+ ## Documentation
258
+
259
+ - **[API Reference](docs/api-reference.md)** — Full API: decorators, container ops, dependency resolution, error handling
260
+ - **[Development Guide](docs/development.md)** — Setup, testing, code quality, contributing
261
+ - **[Release Guide](RELEASE.md)** — Release process for maintainers
262
+ - **[Changelog](CHANGELOG.md)** — Version history
263
+
264
+ ## License
265
+
266
+ MIT — see [LICENSE](https://github.com/wssccc/appctx/blob/main/LICENSE).
267
+
268
+ ## Links
269
+
270
+ - **Repository**: [github.com/wssccc/appctx](https://github.com/wssccc/appctx)
271
+ - **PyPI**: [pypi.org/project/appctx](https://pypi.org/project/appctx/)
272
+ - **Issues**: [github.com/wssccc/appctx/issues](https://github.com/wssccc/appctx/issues)
appctx-0.2.2/README.md ADDED
@@ -0,0 +1,245 @@
1
+ # AppCtx
2
+
3
+ Spring-style dependency injection for Python
4
+
5
+ [![Python Version](https://img.shields.io/badge/python-3.8%2B-blue.svg)](https://python.org)
6
+ [![License](https://img.shields.io/badge/license-MIT-green.svg)](https://github.com/wssccc/appctx/blob/main/LICENSE)
7
+ [![PyPI Version](https://img.shields.io/pypi/v/appctx.svg)](https://pypi.org/project/appctx/)
8
+
9
+ ## Overview
10
+
11
+ AppCtx is a lightweight dependency injection (DI) container for Python, inspired by the Spring Framework. It uses decorator-based bean registration and auto-wiring via type annotations to manage dependencies in a clean, testable way.
12
+
13
+ **Python Requirements**: 3.8+ | **License**: MIT
14
+
15
+ ### Features
16
+
17
+ - 🚀 **Decorator-based registration** — `@bean` / `@component` to register, `@post_construct` for lifecycle hooks
18
+ - 🔄 **Auto-wiring** — Dependencies resolved from type annotations automatically
19
+ - 🔍 **Circular dependency detection** — Caught at startup, not runtime
20
+ - 📦 **Lightweight** — Zero mandatory dependencies beyond stdlib
21
+ - 🐍 **Pythonic API** — Feels natural in Python, not a Java port
22
+ - 🏗️ **Multiple contexts** — Isolated containers for different scopes
23
+
24
+ ## Installation
25
+
26
+ ```bash
27
+ pip install appctx
28
+ ```
29
+
30
+ ## Quick Start
31
+
32
+ ### 1. Define and register beans
33
+
34
+ Use `@bean` on functions that create your objects. Dependencies are declared via type annotations:
35
+
36
+ ```python
37
+ from appctx import bean, get_bean, refresh
38
+
39
+ class DatabaseService:
40
+ def __init__(self, connection_string: str = "sqlite:///default.db"):
41
+ self.connection_string = connection_string
42
+
43
+ def connect(self):
44
+ return f"Connected to {self.connection_string}"
45
+
46
+ class UserService:
47
+ def __init__(self, db: DatabaseService):
48
+ self.db = db
49
+
50
+ def get_user(self, user_id: int):
51
+ return f"User {user_id} from {self.db.connect()}"
52
+
53
+ # Register beans
54
+ @bean
55
+ def database_service():
56
+ return DatabaseService("postgresql://localhost/myapp")
57
+
58
+ @bean
59
+ def user_service(db: DatabaseService): # Auto-injected by type
60
+ return UserService(db)
61
+ ```
62
+
63
+ ### 2. Initialize and use
64
+
65
+ ```python
66
+ # Initialize the container — resolves all dependencies
67
+ refresh()
68
+
69
+ # Retrieve the root application object (the only place you should call get_bean)
70
+ user_svc = get_bean(UserService)
71
+ print(user_svc.get_user(123))
72
+ ```
73
+
74
+ > **Note:** `get_bean()` is only needed at the **entry point** to bootstrap your application. All other dependencies are injected automatically via constructor or function parameters — just like Spring.
75
+
76
+ ### Class-style beans
77
+
78
+ Decorate classes directly — the container instantiates them with resolved dependencies:
79
+
80
+ ```python
81
+ from appctx import bean, get_bean, refresh
82
+
83
+ @bean
84
+ class EmailService:
85
+ def __init__(self):
86
+ self.server = "smtp.example.com"
87
+
88
+ def send_email(self, to: str, subject: str):
89
+ return f"Email sent to {to} via {self.server}"
90
+
91
+ @bean
92
+ class NotificationService:
93
+ def __init__(self, email: EmailService):
94
+ self.email = email
95
+
96
+ def notify(self, user: str, message: str):
97
+ return self.email.send_email(user, "Notification", message)
98
+
99
+ refresh()
100
+
101
+ # Entry point — the only place you should call get_bean
102
+ notification_svc = get_bean(NotificationService)
103
+ print(notification_svc.notify("user@example.com", "Hello World!"))
104
+ ```
105
+
106
+ ### Post-construct lifecycle
107
+
108
+ Use `@post_construct` to run initialization logic after all beans are created:
109
+
110
+ ```python
111
+ from appctx import post_construct
112
+
113
+ class DatabaseService:
114
+ def __init__(self):
115
+ self.connection = None
116
+
117
+ @post_construct
118
+ def init(self):
119
+ self.connection = create_connection() # Called automatically after construction
120
+
121
+ @bean
122
+ def database_service():
123
+ return DatabaseService()
124
+ ```
125
+
126
+ > **Note:** `@post_construct` methods run after **all** beans are created, so you can safely reference other beans during initialization.
127
+
128
+ ### How It Works
129
+
130
+ 1. **Bean Registration**: Use the `@bean` decorator to register functions/classes that create beans
131
+ 2. **Auto-wiring**: Dependencies are resolved from type annotations and injected automatically — no manual `get_bean()` calls needed
132
+ 3. **Container Initialization**: Call `refresh()` to instantiate all beans in dependency order
133
+ 4. **Entry Point**: Use `get_bean()` **once** at the application root to retrieve the assembled object graph
134
+
135
+ ## Core Concepts
136
+
137
+ | Concept | Description |
138
+ |---|---|
139
+ | **Bean** | An object managed by the container |
140
+ | **ApplicationContext** | The DI container that holds and wires beans |
141
+ | **`@bean`** | Decorator that registers a function/class as a bean factory (alias: `@component`) |
142
+ | **`refresh()`** | Initializes the container, instantiates all beans in dependency order |
143
+ | **`get_bean(T)`** | Retrieve a bean by type or name (low-level; prefer auto-wiring) |
144
+ | **`get_beans(T)`** | Retrieve all beans of a given type (low-level; prefer auto-wiring) |
145
+
146
+ ### Dependency Resolution Rules
147
+
148
+ 1. **Positional arguments** → resolved by type annotation
149
+ 2. **Keyword-only arguments** (`*, name`) → resolved by parameter name, falls back to default
150
+ 3. **`**kwargs`** → receives all remaining beans not consumed by other parameters
151
+
152
+ ## Global Default Context
153
+
154
+ AppCtx provides a **global default context** for convenience, similar to Spring's application context. The top-level functions (`bean`, `refresh`, `get_bean`, `add`) operate on this shared instance:
155
+
156
+ ```python
157
+ from appctx import bean, refresh, get_bean
158
+
159
+ # These all use the same global ApplicationContext behind the scenes
160
+ @bean
161
+ def my_service():
162
+ return MyService()
163
+
164
+ refresh()
165
+ app = get_bean(MyService)
166
+ ```
167
+
168
+ ### Custom Context (Isolation)
169
+
170
+ For libraries or tests that need isolation, create your own `ApplicationContext`:
171
+
172
+ ```python
173
+ from appctx import ApplicationContext
174
+
175
+ ctx = ApplicationContext()
176
+
177
+ @ctx.bean
178
+ def my_service():
179
+ return MyService()
180
+
181
+ ctx.refresh()
182
+ app = ctx.get_bean(MyService)
183
+ ```
184
+
185
+ > **Note:** `@bean` on the global context registers beans immediately when the module is imported. For custom contexts, use `@ctx.bean` to register against that specific instance.
186
+
187
+ ## Organizing Beans Across Modules
188
+
189
+ AppCtx does **not** perform automatic package scanning. Instead, beans are registered at import time — when Python loads a module, any `@bean` decorators execute and register with the target context.
190
+
191
+ To wire beans across multiple modules, simply **import** them before calling `refresh()`:
192
+
193
+ ```python
194
+ # main.py
195
+ from appctx import bean, refresh, get_bean
196
+
197
+ # Import modules so their @bean decorators fire
198
+ import myapp.services.user # registers UserService
199
+ import myapp.services.email # registers EmailService
200
+ import myapp.config # registers ConfigService
201
+
202
+ refresh()
203
+ app = get_bean(UserService)
204
+ ```
205
+
206
+ You can also use `add()` to register a plain function or class (without the `@bean` decorator):
207
+
208
+ ```python
209
+ from appctx import add
210
+
211
+ def some_service():
212
+ return SomeService()
213
+
214
+ add(some_service) # Equivalent to @bean on some_service
215
+ ```
216
+
217
+ > **Note:** For modules, simply importing them is sufficient — the `@bean` decorators fire at import time. `add(module)` is available as a semantic marker but the import alone triggers registration.
218
+
219
+ ## Best Practices
220
+
221
+ 1. **Rely on auto-wiring, avoid `get_bean`** — Like Spring, let the container inject dependencies via function/constructor parameters. Reserve `get_bean()` only for bootstrapping the root application object.
222
+ 2. **Use type annotations** — They are the primary mechanism for dependency resolution. Always annotate positional parameters.
223
+ 3. **Single responsibility per bean** — Each bean should have one clear purpose.
224
+ 4. **Prefer constructor injection** — Dependencies go in `__init__` parameters, not hidden inside methods.
225
+ 5. **Keep beans stateless when possible** — Makes testing and reasoning easier.
226
+ 6. **Use `@post_construct` for initialization** — Not `__init__` side-effects. Post-construct runs after all beans exist, allowing cross-bean setup.
227
+ 7. **Separate configuration from logic** — Use dedicated config beans instead of hardcoding values.
228
+ 8. **Design for testability** — Beans that accept dependencies via constructor are trivially mockable in tests.
229
+
230
+ ## Documentation
231
+
232
+ - **[API Reference](docs/api-reference.md)** — Full API: decorators, container ops, dependency resolution, error handling
233
+ - **[Development Guide](docs/development.md)** — Setup, testing, code quality, contributing
234
+ - **[Release Guide](RELEASE.md)** — Release process for maintainers
235
+ - **[Changelog](CHANGELOG.md)** — Version history
236
+
237
+ ## License
238
+
239
+ MIT — see [LICENSE](https://github.com/wssccc/appctx/blob/main/LICENSE).
240
+
241
+ ## Links
242
+
243
+ - **Repository**: [github.com/wssccc/appctx](https://github.com/wssccc/appctx)
244
+ - **PyPI**: [pypi.org/project/appctx](https://pypi.org/project/appctx/)
245
+ - **Issues**: [github.com/wssccc/appctx/issues](https://github.com/wssccc/appctx/issues)
@@ -5,7 +5,7 @@ This package provides a lightweight dependency injection container
5
5
  inspired by the Spring Framework for Java.
6
6
  """
7
7
 
8
- __version__ = "0.2.0"
8
+ __version__ = "0.2.2"
9
9
  __author__ = "wssccc"
10
10
  __email__ = "wssccc@qq.com"
11
11
 
@@ -35,8 +35,8 @@ class ApplicationContext:
35
35
  """Get all beans of a specific type."""
36
36
  return self.bean_types_map[_type]
37
37
 
38
- def _instantiate(self, bean_def: Any) -> Optional[Any]:
39
- """Instantiate a bean definition."""
38
+ def _instantiate(self, bean_def: Any) -> bool:
39
+ """Instantiate a bean definition. Returns True on success, False if dependencies are not ready."""
40
40
  is_class = inspect.isclass(bean_def)
41
41
  if is_class:
42
42
  spec = inspect.getfullargspec(bean_def.__init__)
@@ -53,8 +53,8 @@ class ApplicationContext:
53
53
  self.bean_names_map[bean_def.__name__] = obj
54
54
  self.bean_types_map[type(obj)].append(obj)
55
55
 
56
- return obj
57
- return None
56
+ return True
57
+ return False
58
58
 
59
59
  def _call_post_construct(self, obj: Any) -> None:
60
60
  """Call post_construct methods on the object if they exist."""