pico-ioc 2.2.0__tar.gz → 2.2.1__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 (139) hide show
  1. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/CHANGELOG.md +14 -0
  2. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/PKG-INFO +2 -2
  3. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/README.md +1 -1
  4. pico_ioc-2.2.1/docs/README.md +142 -0
  5. pico_ioc-2.2.1/docs/overview.md +5 -0
  6. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/mkdocs.yml +0 -4
  7. pico_ioc-2.2.1/src/pico_ioc/_version.py +1 -0
  8. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/aop.py +13 -11
  9. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/container.py +1 -1
  10. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/scope.py +14 -6
  11. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc.egg-info/PKG-INFO +2 -2
  12. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc.egg-info/SOURCES.txt +15 -1
  13. pico_ioc-2.2.1/tests/test_aop_coverage.py +72 -0
  14. pico_ioc-2.2.1/tests/test_aop_extended.py +573 -0
  15. pico_ioc-2.2.1/tests/test_bugfixes_v221.py +396 -0
  16. pico_ioc-2.2.1/tests/test_config_builder.py +119 -0
  17. pico_ioc-2.2.1/tests/test_config_registrar_coverage.py +49 -0
  18. pico_ioc-2.2.1/tests/test_config_runtime_coverage.py +75 -0
  19. pico_ioc-2.2.1/tests/test_container_coverage.py +57 -0
  20. pico_ioc-2.2.1/tests/test_container_coverage_boost.py +558 -0
  21. pico_ioc-2.2.1/tests/test_container_extended.py +590 -0
  22. pico_ioc-2.2.1/tests/test_container_integrated_coverage.py +319 -0
  23. pico_ioc-2.2.1/tests/test_event_bus_coverage_boost.py +470 -0
  24. pico_ioc-2.2.1/tests/test_event_bus_extended.py +434 -0
  25. pico_ioc-2.2.1/tests/test_locator.py +163 -0
  26. pico_ioc-2.2.1/tests/test_scope_coverage.py +66 -0
  27. pico_ioc-2.2.0/docs/README.md +0 -154
  28. pico_ioc-2.2.0/docs/overview.md +0 -191
  29. pico_ioc-2.2.0/src/pico_ioc/_version.py +0 -1
  30. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/.coveragerc +0 -0
  31. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/.github/workflows/ci.yml +0 -0
  32. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/.github/workflows/docs.yml +0 -0
  33. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/.github/workflows/publish-to-pypi.yml +0 -0
  34. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/LICENSE +0 -0
  35. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/MANIFEST.in +0 -0
  36. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/LEARN.md +0 -0
  37. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/README.md +0 -0
  38. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0001-async-native.md +0 -0
  39. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0002-tree-based-configuration.md +0 -0
  40. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0003-context-aware-scopes.md +0 -0
  41. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0004-observability.md +0 -0
  42. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0005-aop.md +0 -0
  43. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0006-eager-validation.md +0 -0
  44. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0007-event_bus.md +0 -0
  45. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0008-circular-dependencies.md +0 -0
  46. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0009-flexible-provides.md +0 -0
  47. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0010-unified-configuration.md +0 -0
  48. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/adr/adr-0011-custom-scanners.md +0 -0
  49. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/README.md +0 -0
  50. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/aop-interceptors.md +0 -0
  51. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/aop-proxies.md +0 -0
  52. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/async-resolution.md +0 -0
  53. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/conditional-binding.md +0 -0
  54. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/custom-scanners.md +0 -0
  55. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/event-bus.md +0 -0
  56. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/health-checks.md +0 -0
  57. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/advanced-features/self-injection.md +0 -0
  58. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/README.md +0 -0
  59. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/analysis-api.md +0 -0
  60. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/constants.md +0 -0
  61. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/container-context-api.md +0 -0
  62. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/container.md +0 -0
  63. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/decorators.md +0 -0
  64. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/event_bus.md +0 -0
  65. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/exceptions.md +0 -0
  66. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/glossary.md +0 -0
  67. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/protocols.md +0 -0
  68. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/api-reference/provider-selector.md +0 -0
  69. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/architecture/README.md +0 -0
  70. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/architecture/comparison.md +0 -0
  71. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/architecture/design-principles.md +0 -0
  72. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/architecture/docs-assets.md +0 -0
  73. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/architecture/internals.md +0 -0
  74. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/architecture/runtime-model-scheduling.md +0 -0
  75. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/README.md +0 -0
  76. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-aop-feature-toggle.md +0 -0
  77. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-aop-profiling.md +0 -0
  78. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-aop-security.md +0 -0
  79. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-aop-structured-logging.md +0 -0
  80. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-cli-app.md +0 -0
  81. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-config-overrides.md +0 -0
  82. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-cqrs.md +0 -0
  83. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-hot-reload.md +0 -0
  84. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/cookbook/pattern-multi-tenant.md +0 -0
  85. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/getting-started.md +0 -0
  86. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/javascripts/extra.js +0 -0
  87. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/observability/README.md +0 -0
  88. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/observability/container-context.md +0 -0
  89. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/observability/exporting-graph.md +0 -0
  90. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/observability/observers-metrics.md +0 -0
  91. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/observability/resolution-graph.md +0 -0
  92. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/requirements.txt +0 -0
  93. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/stylesheets/extra.css +0 -0
  94. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/user-guide/README.md +0 -0
  95. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/user-guide/configuration-basic.md +0 -0
  96. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/user-guide/configuration-binding.md +0 -0
  97. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/user-guide/core-concepts.md +0 -0
  98. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/user-guide/qualifiers-lists.md +0 -0
  99. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/user-guide/scopes-lifecycle.md +0 -0
  100. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/docs/user-guide/testing.md +0 -0
  101. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/pyproject.toml +0 -0
  102. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/setup.cfg +0 -0
  103. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/__init__.py +0 -0
  104. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/analysis.py +0 -0
  105. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/api.py +0 -0
  106. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/component_scanner.py +0 -0
  107. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/config_builder.py +0 -0
  108. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/config_registrar.py +0 -0
  109. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/config_runtime.py +0 -0
  110. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/constants.py +0 -0
  111. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/decorators.py +0 -0
  112. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/dependency_validator.py +0 -0
  113. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/event_bus.py +0 -0
  114. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/exceptions.py +0 -0
  115. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/factory.py +0 -0
  116. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/locator.py +0 -0
  117. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/provider_selector.py +0 -0
  118. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc/registrar.py +0 -0
  119. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc.egg-info/dependency_links.txt +0 -0
  120. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc.egg-info/requires.txt +0 -0
  121. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/src/pico_ioc.egg-info/top_level.txt +0 -0
  122. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_aop.py +0 -0
  123. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_collection_injection.py +0 -0
  124. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_config_value.py +0 -0
  125. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_configured.py +0 -0
  126. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_container_context.py +0 -0
  127. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_container_runtime.py +0 -0
  128. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_container_self_injection.py +0 -0
  129. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_custom_scanner.py +0 -0
  130. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_event_bus.py +0 -0
  131. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_pico_extends.py +0 -0
  132. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_pico_integration.py +0 -0
  133. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_protocol_resolution_and_graph.py +0 -0
  134. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_provides_module_functions.py +0 -0
  135. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_provides_static_methods.py +0 -0
  136. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_proxy_unit.py +0 -0
  137. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_resolution_graph.py +0 -0
  138. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tests/test_scope.py +0 -0
  139. {pico_ioc-2.2.0 → pico_ioc-2.2.1}/tox.ini +0 -0
@@ -7,6 +7,20 @@ and this project adheres to Semantic Versioning (https://semver.org/spec/v2.0.ht
7
7
 
8
8
  ---
9
9
 
10
+ ## [2.2.1] - 2026-02-03
11
+
12
+ ### Fixed 🧩
13
+
14
+ - **DOT Graph Export**: Fixed incorrect variable reference in `export_graph()` where dependency edges used `{child}` instead of `{cid}`, causing malformed DOT output when visualizing the dependency graph.
15
+ - **Race Condition in Lazy Proxy**: Fixed a race condition in `UnifiedComponentProxy._async_init_if_needed()` where concurrent async access could trigger duplicate object creation. The fix implements proper double-check locking and moves `@configure` execution outside the critical section to prevent deadlocks.
16
+ - **Silent Cleanup Failures**: Replaced silent `except Exception: pass` blocks in `ScopedCaches` cleanup methods with proper logging. Cleanup method failures are now logged at WARNING level, aiding debugging of resource leaks without crashing the application.
17
+
18
+ ### Internal 🔧
19
+
20
+ - Added `logging` import and `_logger` instance to `scope.py` for structured error reporting during component cleanup.
21
+
22
+ ---
23
+
10
24
  ## [2.2.0] - 2025-11-29
11
25
 
12
26
  ### Added ✨
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pico-ioc
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: A minimalist, zero-dependency Inversion of Control (IoC) container for Python.
5
5
  Author-email: David Perez Cabrera <dperezcabrera@gmail.com>
6
6
  License: MIT License
@@ -347,7 +347,7 @@ tox
347
347
 
348
348
  ## 🧾 Changelog
349
349
 
350
- See [CHANGELOG.md](https://www.google.com/search?q=./CHANGELOG.md) — Significant redesigns and features in v2.0+.
350
+ See [CHANGELOG.md](./CHANGELOG.md) — Significant redesigns and features in v2.0+.
351
351
 
352
352
  -----
353
353
 
@@ -297,7 +297,7 @@ tox
297
297
 
298
298
  ## 🧾 Changelog
299
299
 
300
- See [CHANGELOG.md](https://www.google.com/search?q=./CHANGELOG.md) — Significant redesigns and features in v2.0+.
300
+ See [CHANGELOG.md](./CHANGELOG.md) — Significant redesigns and features in v2.0+.
301
301
 
302
302
  -----
303
303
 
@@ -0,0 +1,142 @@
1
+ # Pico-IoC Documentation
2
+
3
+ `pico-ioc` is a lightweight, async-native Inversion of Control (IoC) container for Python 3.10+. It brings enterprise-grade dependency injection, configuration binding, and AOP to the Python ecosystem.
4
+
5
+ ---
6
+
7
+ ## Quick Install
8
+
9
+ ```bash
10
+ pip install pico-ioc
11
+
12
+ # Optional: YAML configuration support
13
+ pip install pico-ioc[yaml]
14
+ ```
15
+
16
+ ---
17
+
18
+ ## 30-Second Example
19
+
20
+ ```python
21
+ from pico_ioc import component, init
22
+
23
+ @component
24
+ class Database:
25
+ def query(self) -> str:
26
+ return "data from DB"
27
+
28
+ @component
29
+ class UserService:
30
+ def __init__(self, db: Database): # Auto-injected
31
+ self.db = db
32
+
33
+ def get_users(self) -> str:
34
+ return self.db.query()
35
+
36
+ # Initialize and use
37
+ container = init(modules=[__name__])
38
+ service = container.get(UserService)
39
+ print(service.get_users()) # "data from DB"
40
+ ```
41
+
42
+ ---
43
+
44
+ ## Key Features
45
+
46
+ | Feature | Description |
47
+ |---------|-------------|
48
+ | **Async-Native** | Full `async`/`await` support: `aget()`, `__ainit__`, async `@cleanup` |
49
+ | **Unified Configuration** | `@configured` maps ENV vars or YAML/JSON to dataclasses |
50
+ | **Fail-Fast Validation** | All wiring errors detected at `init()`, not runtime |
51
+ | **AOP Interceptors** | `@intercepted_by` for logging, caching, security |
52
+ | **Scoped Lifecycles** | singleton, prototype, request, session, transaction |
53
+ | **Observable** | Built-in stats, health checks, dependency graph export |
54
+
55
+ ---
56
+
57
+ ## Documentation Structure
58
+
59
+ | # | Section | Description | Link |
60
+ |---|---------|-------------|------|
61
+ | 1 | **Getting Started** | 5-minute tutorial | [getting-started.md](./getting-started.md) |
62
+ | 2 | **User Guide** | Core concepts, configuration, scopes, testing | [user-guide/](./user-guide/README.md) |
63
+ | 3 | **Advanced Features** | Async, AOP, Event Bus, conditional binding | [advanced-features/](./advanced-features/README.md) |
64
+ | 4 | **Observability** | Metrics, tracing, graph export | [observability/](./observability/README.md) |
65
+ | 5 | **Cookbook** | Real-world patterns (multi-tenant, CQRS, etc.) | [cookbook/](./cookbook/README.md) |
66
+ | 6 | **Architecture** | Design principles, internals | [architecture/](./architecture/README.md) |
67
+ | 7 | **API Reference** | Decorators, exceptions, protocols | [api-reference/](./api-reference/README.md) |
68
+ | 8 | **FAQ** | Common questions and solutions | [faq.md](./faq.md) |
69
+ | 9 | **Examples** | Complete runnable applications | [examples/](./examples/README.md) |
70
+
71
+ ---
72
+
73
+ ## Core APIs at a Glance
74
+
75
+ ### Registration
76
+
77
+ ```python
78
+ from pico_ioc import component, factory, provides
79
+
80
+ @component # Register a class
81
+ class MyService: ...
82
+
83
+ @factory # Group related providers
84
+ class ClientFactory:
85
+ @provides(RedisClient) # Provide third-party types
86
+ def build_redis(self, config: RedisConfig) -> RedisClient:
87
+ return RedisClient(config.url)
88
+ ```
89
+
90
+ ### Configuration
91
+
92
+ ```python
93
+ from dataclasses import dataclass
94
+ from pico_ioc import configured, configuration, init
95
+
96
+ @configured(prefix="DB_")
97
+ @dataclass
98
+ class DBConfig:
99
+ host: str = "localhost"
100
+ port: int = 5432
101
+
102
+ container = init(
103
+ modules=[__name__],
104
+ config=configuration() # Reads from ENV: DB_HOST, DB_PORT
105
+ )
106
+ ```
107
+
108
+ ### Async Support
109
+
110
+ ```python
111
+ @component
112
+ class AsyncService:
113
+ async def __ainit__(self):
114
+ self.conn = await open_connection()
115
+
116
+ @cleanup
117
+ async def close(self):
118
+ await self.conn.close()
119
+
120
+ # Resolve async components
121
+ service = await container.aget(AsyncService)
122
+
123
+ # Cleanup
124
+ await container.ashutdown()
125
+ ```
126
+
127
+ ### Testing with Overrides
128
+
129
+ ```python
130
+ container = init(
131
+ modules=[__name__],
132
+ overrides={Database: FakeDatabase()} # Replace for tests
133
+ )
134
+ ```
135
+
136
+ ---
137
+
138
+ ## Next Steps
139
+
140
+ 1. **New to pico-ioc?** Start with the [Getting Started](./getting-started.md) tutorial
141
+ 2. **Coming from Spring/Guice?** Check the [Architecture Comparison](./architecture/comparison.md)
142
+ 3. **Building a real app?** See the [Cookbook](./cookbook/README.md) patterns
@@ -0,0 +1,5 @@
1
+ # Overview
2
+
3
+ This page has been merged into the main documentation.
4
+
5
+ **Please see: [Documentation Home](./README.md)**
@@ -146,10 +146,6 @@ nav:
146
146
  - Custom Scanners: advanced-features/custom-scanners.md
147
147
  - Self Injection: advanced-features/self-injection.md
148
148
 
149
- - Integrations:
150
- - Overview: integrations.md
151
- - Celery: integrations-celery.md
152
-
153
149
  - Observability:
154
150
  - observability/README.md
155
151
  - Container Context: observability/container-context.md
@@ -0,0 +1 @@
1
+ __version__ = '2.2.1'
@@ -145,23 +145,25 @@ class UnifiedComponentProxy:
145
145
  return
146
146
 
147
147
  lock = object.__getattribute__(self, "_lock")
148
- tgt = object.__getattribute__(self, "_target")
149
- if tgt is not None:
150
- return
151
148
 
152
- creator = object.__getattribute__(self, "_creator")
153
- container = object.__getattribute__(self, "_container")
154
-
155
- tgt = creator()
156
-
149
+ with lock:
150
+ # Double-check inside lock to prevent race condition
151
+ tgt = object.__getattribute__(self, "_target")
152
+ if tgt is not None:
153
+ return
154
+
155
+ creator = object.__getattribute__(self, "_creator")
156
+ container = object.__getattribute__(self, "_container")
157
+
158
+ tgt = creator()
159
+ object.__setattr__(self, "_target", tgt)
160
+
161
+ # Run configure methods outside the lock to avoid blocking
157
162
  if container and hasattr(container, "_run_configure_methods"):
158
163
  res = container._run_configure_methods(tgt)
159
164
  if inspect.isawaitable(res):
160
165
  await res
161
166
 
162
- with lock:
163
- object.__setattr__(self, "_target", tgt)
164
-
165
167
  def _scope_signature(self) -> Tuple[Any, ...]:
166
168
  container = object.__getattribute__(self, "_container")
167
169
  key = object.__getattribute__(self, "_component_key")
@@ -438,7 +438,7 @@ class PicoContainer:
438
438
  pid = _node_id(parent)
439
439
  for child in deps:
440
440
  cid = _node_id(child)
441
- lines.append(f" {pid} -> {child};")
441
+ lines.append(f" {pid} -> {cid};")
442
442
 
443
443
  lines.append("}")
444
444
 
@@ -1,9 +1,12 @@
1
1
  import contextvars
2
2
  import inspect
3
+ import logging
3
4
  from typing import Any, Dict, Optional, Tuple
4
5
  from collections import OrderedDict
5
6
  from .exceptions import ScopeError
6
7
 
8
+ _logger = logging.getLogger(__name__)
9
+
7
10
  class ScopeProtocol:
8
11
  def get_id(self) -> Any | None: ...
9
12
 
@@ -108,10 +111,15 @@ class ScopedCaches:
108
111
  if meta.get("cleanup", False):
109
112
  try:
110
113
  m()
111
- except Exception:
112
- pass
113
- except Exception:
114
- pass
114
+ except Exception as e:
115
+ _logger.warning(
116
+ "Cleanup method %s.%s failed: %s",
117
+ type(obj).__name__,
118
+ getattr(m, "__name__", "<unknown>"),
119
+ e
120
+ )
121
+ except Exception as e:
122
+ _logger.debug("Failed to inspect object for cleanup: %s", e)
115
123
 
116
124
  def cleanup_scope(self, scope_name: str, scope_id: Any) -> None:
117
125
  bucket = self._by_scope.get(scope_name)
@@ -123,8 +131,8 @@ class ScopedCaches:
123
131
  try:
124
132
  for _, obj in container.items():
125
133
  self._cleanup_object(obj)
126
- except Exception:
127
- pass
134
+ except Exception as e:
135
+ _logger.debug("Failed to cleanup container: %s", e)
128
136
 
129
137
  def for_scope(self, scopes: ScopeManager, scope: str) -> ComponentContainer:
130
138
  if scope == "singleton":
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pico-ioc
3
- Version: 2.2.0
3
+ Version: 2.2.1
4
4
  Summary: A minimalist, zero-dependency Inversion of Control (IoC) container for Python.
5
5
  Author-email: David Perez Cabrera <dperezcabrera@gmail.com>
6
6
  License: MIT License
@@ -347,7 +347,7 @@ tox
347
347
 
348
348
  ## 🧾 Changelog
349
349
 
350
- See [CHANGELOG.md](https://www.google.com/search?q=./CHANGELOG.md) — Significant redesigns and features in v2.0+.
350
+ See [CHANGELOG.md](./CHANGELOG.md) — Significant redesigns and features in v2.0+.
351
351
 
352
352
  -----
353
353
 
@@ -102,14 +102,27 @@ src/pico_ioc.egg-info/dependency_links.txt
102
102
  src/pico_ioc.egg-info/requires.txt
103
103
  src/pico_ioc.egg-info/top_level.txt
104
104
  tests/test_aop.py
105
+ tests/test_aop_coverage.py
106
+ tests/test_aop_extended.py
107
+ tests/test_bugfixes_v221.py
105
108
  tests/test_collection_injection.py
109
+ tests/test_config_builder.py
110
+ tests/test_config_registrar_coverage.py
111
+ tests/test_config_runtime_coverage.py
106
112
  tests/test_config_value.py
107
113
  tests/test_configured.py
108
114
  tests/test_container_context.py
115
+ tests/test_container_coverage.py
116
+ tests/test_container_coverage_boost.py
117
+ tests/test_container_extended.py
118
+ tests/test_container_integrated_coverage.py
109
119
  tests/test_container_runtime.py
110
120
  tests/test_container_self_injection.py
111
121
  tests/test_custom_scanner.py
112
122
  tests/test_event_bus.py
123
+ tests/test_event_bus_coverage_boost.py
124
+ tests/test_event_bus_extended.py
125
+ tests/test_locator.py
113
126
  tests/test_pico_extends.py
114
127
  tests/test_pico_integration.py
115
128
  tests/test_protocol_resolution_and_graph.py
@@ -117,4 +130,5 @@ tests/test_provides_module_functions.py
117
130
  tests/test_provides_static_methods.py
118
131
  tests/test_proxy_unit.py
119
132
  tests/test_resolution_graph.py
120
- tests/test_scope.py
133
+ tests/test_scope.py
134
+ tests/test_scope_coverage.py
@@ -0,0 +1,72 @@
1
+ import pytest
2
+ import pickle
3
+ import inspect
4
+ import threading
5
+ from pico_ioc.aop import (
6
+ UnifiedComponentProxy,
7
+ intercepted_by,
8
+ MethodInterceptor,
9
+ MethodCtx,
10
+ _gather_interceptors_for_method
11
+ )
12
+ from pico_ioc.exceptions import SerializationError, AsyncResolutionError
13
+
14
+ def test_intercepted_by_validation():
15
+ with pytest.raises(TypeError, match="requires at least one"):
16
+ intercepted_by()
17
+
18
+ with pytest.raises(TypeError, match="expects interceptor classes"):
19
+ intercepted_by("not_a_class")
20
+
21
+ decorator = intercepted_by(MethodInterceptor)
22
+ with pytest.raises(TypeError, match="only decorate callables"):
23
+ decorator("not_callable")
24
+
25
+ def test_proxy_init_validation():
26
+ with pytest.raises(ValueError, match="non-null container"):
27
+ UnifiedComponentProxy(container=None)
28
+
29
+ with pytest.raises(ValueError, match="requires either a target"):
30
+ UnifiedComponentProxy(container="stub")
31
+
32
+ def test_proxy_creator_errors():
33
+ proxy = UnifiedComponentProxy(container="stub", object_creator="not_callable")
34
+ with pytest.raises(TypeError, match="must be callable"):
35
+ getattr(proxy, "any_attr")
36
+
37
+ proxy = UnifiedComponentProxy(container="stub", object_creator=lambda: None)
38
+ with pytest.raises(RuntimeError, match="returned None"):
39
+ getattr(proxy, "any_attr")
40
+
41
+ def test_proxy_async_error_on_sync_access():
42
+ async def async_configure(obj):
43
+ pass
44
+
45
+ class ContainerMock:
46
+ def _run_configure_methods(self, obj):
47
+ return async_configure(obj)
48
+
49
+ proxy = UnifiedComponentProxy(
50
+ container=ContainerMock(),
51
+ object_creator=lambda: "target"
52
+ )
53
+
54
+ with pytest.raises(AsyncResolutionError):
55
+ str(proxy)
56
+
57
+ def test_proxy_serialization_error():
58
+ class Unpicklable:
59
+ def __getstate__(self):
60
+ raise ValueError("No pickle")
61
+
62
+ proxy = UnifiedComponentProxy(container="stub", target=Unpicklable())
63
+
64
+ with pytest.raises(SerializationError):
65
+ pickle.dumps(proxy)
66
+
67
+ with pytest.raises(SerializationError):
68
+ proxy.__setstate__({"data": b"bad_data"})
69
+
70
+ def test_gather_interceptors_edge_cases():
71
+ assert _gather_interceptors_for_method(str, "non_existent") == ()
72
+ assert _gather_interceptors_for_method(list, "append") == ()