python-neva 3.1.0__tar.gz → 3.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.
- python_neva-3.2.0/CHANGELOG.md +80 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/PKG-INFO +2 -2
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/application.py +11 -6
- python_neva-3.2.0/neva/arch/markers.py +4 -0
- python_neva-3.2.0/neva/arch/scopes.py +5 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/service_provider.py +83 -2
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/events/dispatcher.py +19 -5
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/events/provider.py +2 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/pyproject.toml +8 -16
- {python_neva-3.1.0 → python_neva-3.2.0}/scripts/retag-with-changelog.sh +1 -1
- python_neva-3.2.0/tests/arch/test_cache.py +52 -0
- python_neva-3.2.0/tests/arch/test_extends.py +98 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/conftest.py +2 -1
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/test_dispatch.py +3 -2
- python_neva-3.1.0/.envrc +0 -2
- python_neva-3.1.0/CHANGELOG.md +0 -42
- python_neva-3.1.0/uv.lock +0 -2440
- {python_neva-3.1.0 → python_neva-3.2.0}/.gitignore +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/.pre-commit-config.yaml +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/.python-version +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/README.md +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/config.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/facade.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/integrations/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/integrations/faststream.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/arch/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/config/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/config/base_providers.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/config/loader.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/config/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/config/repository.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/database/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/database/config.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/database/connection.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/database/manager.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/database/provider.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/database/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/database/transaction.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/events/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/events/event.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/events/event_registry.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/events/listener.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/events/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/instrumentation/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/instrumentation/sqlalchemy.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/logging/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/logging/manager.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/logging/provider.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/middleware/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/middleware/correlation.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/middleware/profiler.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/obs/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/polyfactory/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/polyfactory/factories.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/polyfactory/persistence.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/polyfactory/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/encryption/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/encryption/encrypter.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/encryption/protocol.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/hashing/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/hashing/config.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/hashing/hash_manager.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/hashing/hashers/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/hashing/hashers/argon2.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/hashing/hashers/bcrypt.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/hashing/hashers/protocol.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/provider.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/tokens/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/tokens/generate_token.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/tokens/hash_token.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/security/tokens/verify_token.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/accessors.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/app.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/app.pyi +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/config.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/config.pyi +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/crypt.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/crypt.pyi +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/db.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/db.pyi +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/event.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/event.pyi +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/hash.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/hash.pyi +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/log.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/facade/log.pyi +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/results.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/strategy.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/strconv.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/support/time.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/testing/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/testing/fakes.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/testing/fixtures.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/testing/py.typed +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/neva/testing/test_case.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/ruff.toml +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/arch/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/arch/test_scope.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/config/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/config/test_loader.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/config/test_repository.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/conftest.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_connection_manager.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_database_manager.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_edge_cases.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_multi_connection.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_sqlalchemy_integration.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_transaction.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_transaction_context.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/database/test_transaction_registry.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/test_deferred.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/test_event.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/test_event_registry.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/test_function_listener.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/events/test_immediate.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/obs/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/obs/test_correlation.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/obs/test_profiler.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/security/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/security/test_encrypter.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/security/test_hash_manager.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/testing/__init__.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/testing/test_event_fake.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/testing/test_facade_restore.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/testing/test_fixtures.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/testing/test_refresh_database.py +0 -0
- {python_neva-3.1.0 → python_neva-3.2.0}/tests/testing/test_test_case.py +0 -0
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
## 3.1.1 (2026-05-11)
|
|
2
|
+
|
|
3
|
+
### 📌➕⬇️➖⬆️ Dependencies
|
|
4
|
+
|
|
5
|
+
- bump opt dep on neva-fastapi
|
|
6
|
+
|
|
7
|
+
## 3.1.0 (2026-05-11)
|
|
8
|
+
|
|
9
|
+
### ✨ Features
|
|
10
|
+
|
|
11
|
+
- fixing db facade
|
|
12
|
+
- Add eq method to Some / Ok
|
|
13
|
+
- Deprecate register_engine, add register_connection
|
|
14
|
+
- RefreshDatabase now properly use the testcase inner app
|
|
15
|
+
- Add neva-fastapi as optional dependency
|
|
16
|
+
- renaming of make / make_async for retrocompatibility
|
|
17
|
+
|
|
18
|
+
### 🐛🚑️ Fixes
|
|
19
|
+
|
|
20
|
+
- Fix provider registration ordering
|
|
21
|
+
|
|
22
|
+
### ♻️ Refactorings
|
|
23
|
+
|
|
24
|
+
- refactor of results / db toolkit
|
|
25
|
+
|
|
26
|
+
## 3.1.0a1 (2026-05-05)
|
|
27
|
+
|
|
28
|
+
### build
|
|
29
|
+
|
|
30
|
+
- add commitizen + versioningit
|
|
31
|
+
|
|
32
|
+
### 💚👷 CI & Build
|
|
33
|
+
|
|
34
|
+
- update perms on tag script
|
|
35
|
+
- tweaking cz tags
|
|
36
|
+
- remove auto-annotated tags
|
|
37
|
+
- configure versioningit + cz
|
|
38
|
+
- Fix cz config >>> ⏰ 1
|
|
39
|
+
- add and configure cz_gitmoji >>> ⏰ 5m
|
|
40
|
+
|
|
41
|
+
### 📝💡 Documentation
|
|
42
|
+
|
|
43
|
+
- clear changelog
|
|
44
|
+
|
|
45
|
+
### 🔧🔨📦️ Configuration, Scripts, Packages
|
|
46
|
+
|
|
47
|
+
- improve auto-annotation of tags
|
|
48
|
+
- Fix tagging script
|
|
49
|
+
|
|
50
|
+
## python-neva-v3.2.0 (2026-06-04)
|
|
51
|
+
|
|
52
|
+
### ✨ Features
|
|
53
|
+
|
|
54
|
+
- **core**: add optional activator on all binding functions
|
|
55
|
+
- **core**: remove scoping option from scoped, default to request scope
|
|
56
|
+
- **core**: introduce conditional activation
|
|
57
|
+
- **core**: add specifying interface in extends method
|
|
58
|
+
- **core**: add extends feature
|
|
59
|
+
|
|
60
|
+
### build
|
|
61
|
+
|
|
62
|
+
- **core**: remove redundant .envrc files
|
|
63
|
+
- **core**: fixing version derivation
|
|
64
|
+
|
|
65
|
+
### docs
|
|
66
|
+
|
|
67
|
+
- **core,-fastapi**: add contributing documentation
|
|
68
|
+
|
|
69
|
+
### feat
|
|
70
|
+
|
|
71
|
+
- **core**: allow passing context data to scope method
|
|
72
|
+
- **core**: add some utility functions for binding
|
|
73
|
+
- **core**: add some utility functions for binding
|
|
74
|
+
- **core**: add the possibility to turn off dependency caching
|
|
75
|
+
- **core,-fastapi**: add .envrc file
|
|
76
|
+
|
|
77
|
+
### refactor
|
|
78
|
+
|
|
79
|
+
- **core**: cleaning up type hints
|
|
80
|
+
- **core**: update type hints on main application
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-neva
|
|
3
|
-
Version: 3.
|
|
3
|
+
Version: 3.2.0
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: aiosqlite>=0.20.0
|
|
@@ -16,7 +16,7 @@ Requires-Dist: sqlalchemy[asyncio]>=2.0.0
|
|
|
16
16
|
Requires-Dist: structlog>=25.5.0
|
|
17
17
|
Requires-Dist: typer>=0.21.1
|
|
18
18
|
Provides-Extra: fastapi
|
|
19
|
-
Requires-Dist: neva-fastapi>=
|
|
19
|
+
Requires-Dist: neva-fastapi>=1.0.0; extra == 'fastapi'
|
|
20
20
|
Provides-Extra: testing
|
|
21
21
|
Requires-Dist: pytest-asyncio>=0.25.3; extra == 'testing'
|
|
22
22
|
Requires-Dist: pytest>=9.0.2; extra == 'testing'
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
4
|
from collections import OrderedDict
|
|
5
|
-
from collections.abc import
|
|
5
|
+
from collections.abc import AsyncGenerator, Sequence
|
|
6
6
|
from contextlib import AsyncExitStack, asynccontextmanager
|
|
7
7
|
from contextvars import ContextVar
|
|
8
8
|
from pathlib import Path
|
|
@@ -13,6 +13,7 @@ from dishka.provider import BaseProvider
|
|
|
13
13
|
from typing_extensions import deprecated
|
|
14
14
|
|
|
15
15
|
from neva.arch.facade import Facade
|
|
16
|
+
from neva.arch.scopes import BaseScope, Scope
|
|
16
17
|
from neva.arch.service_provider import Bootable, ServiceProvider
|
|
17
18
|
from neva.config.loader import ConfigLoader
|
|
18
19
|
from neva.support import Err, Ok, Result
|
|
@@ -41,7 +42,7 @@ class Application:
|
|
|
41
42
|
|
|
42
43
|
self.config: ConfigRepository = ConfigRepository()
|
|
43
44
|
self.providers: OrderedDict[type, ServiceProvider] = OrderedDict()
|
|
44
|
-
self.root_provider: dishka.Provider = dishka.Provider(scope=
|
|
45
|
+
self.root_provider: dishka.Provider = dishka.Provider(scope=Scope.APP)
|
|
45
46
|
|
|
46
47
|
configuration_path = (
|
|
47
48
|
config_path
|
|
@@ -112,7 +113,7 @@ class Application:
|
|
|
112
113
|
source: type | Callable[..., Any],
|
|
113
114
|
*,
|
|
114
115
|
interface: type | None = None,
|
|
115
|
-
scope:
|
|
116
|
+
scope: BaseScope | None = None,
|
|
116
117
|
) -> None:
|
|
117
118
|
"""Binds a source to the container."""
|
|
118
119
|
_ = self.root_provider.provide(
|
|
@@ -146,14 +147,18 @@ class Application:
|
|
|
146
147
|
return Err(f"Failed to resolve service '{interface.__name__}': {e}")
|
|
147
148
|
|
|
148
149
|
@asynccontextmanager
|
|
149
|
-
async def scope(
|
|
150
|
+
async def scope(
|
|
151
|
+
self,
|
|
152
|
+
scope: BaseScope | None = None,
|
|
153
|
+
context: dict[type, Any] | None = None,
|
|
154
|
+
) -> AsyncGenerator[Self]:
|
|
150
155
|
"""Enter a new scope.
|
|
151
156
|
|
|
152
157
|
Yields:
|
|
153
158
|
The application instance with the new scope.
|
|
154
159
|
"""
|
|
155
160
|
parent = _current_container.get(self.container)
|
|
156
|
-
async with parent(scope=scope) as container:
|
|
161
|
+
async with parent(scope=scope, context=context) as container:
|
|
157
162
|
token = _current_container.set(container)
|
|
158
163
|
try:
|
|
159
164
|
yield self
|
|
@@ -161,7 +166,7 @@ class Application:
|
|
|
161
166
|
_current_container.reset(token)
|
|
162
167
|
|
|
163
168
|
@asynccontextmanager
|
|
164
|
-
async def lifespan(self) ->
|
|
169
|
+
async def lifespan(self) -> AsyncGenerator[None]:
|
|
165
170
|
"""Wire the facades and providers."""
|
|
166
171
|
Facade.set_facade_application(self)
|
|
167
172
|
|
|
@@ -21,6 +21,8 @@ from typing import (
|
|
|
21
21
|
|
|
22
22
|
import dishka
|
|
23
23
|
|
|
24
|
+
from neva.arch.markers import BaseMarker, Marker
|
|
25
|
+
from neva.arch.scopes import BaseScope, Scope
|
|
24
26
|
from neva.support import Result
|
|
25
27
|
|
|
26
28
|
|
|
@@ -69,6 +71,7 @@ class ServiceProvider(abc.ABC):
|
|
|
69
71
|
|
|
70
72
|
app: "Application"
|
|
71
73
|
listen: ClassVar[dict[type[Event], list[type[EventListener[Any]]]]] = {}
|
|
74
|
+
when: ClassVar[Marker | None] = None
|
|
72
75
|
|
|
73
76
|
def __init__(self, app: "Application") -> None:
|
|
74
77
|
"""Initialize the service provider.
|
|
@@ -77,23 +80,101 @@ class ServiceProvider(abc.ABC):
|
|
|
77
80
|
app: The application instance.
|
|
78
81
|
|
|
79
82
|
"""
|
|
80
|
-
self.provider: dishka.Provider = dishka.Provider(
|
|
83
|
+
self.provider: dishka.Provider = dishka.Provider(
|
|
84
|
+
scope=dishka.Scope.APP,
|
|
85
|
+
when=self.when,
|
|
86
|
+
)
|
|
81
87
|
self.app = app
|
|
82
88
|
|
|
89
|
+
def activator(
|
|
90
|
+
self,
|
|
91
|
+
activation_fn: Callable[..., bool],
|
|
92
|
+
*markers: Marker | type[Marker],
|
|
93
|
+
) -> None:
|
|
94
|
+
"""Registers an activator function for the given markers.
|
|
95
|
+
|
|
96
|
+
Args:
|
|
97
|
+
activation_fn: The activator function.
|
|
98
|
+
markers: Markers triggered by this function.
|
|
99
|
+
"""
|
|
100
|
+
_ = self.provider.activate(activation_fn, *markers)
|
|
101
|
+
|
|
83
102
|
def bind(
|
|
84
103
|
self,
|
|
85
104
|
source: type | Callable[..., Any],
|
|
86
105
|
*,
|
|
87
106
|
interface: type | None = None,
|
|
88
|
-
scope:
|
|
107
|
+
scope: BaseScope | None = None,
|
|
108
|
+
when: BaseMarker | None = None,
|
|
109
|
+
cache: bool = True,
|
|
89
110
|
) -> None:
|
|
90
111
|
"""Binds a source to the container."""
|
|
91
112
|
_ = self.provider.provide(
|
|
92
113
|
source=source,
|
|
93
114
|
scope=scope,
|
|
94
115
|
provides=interface,
|
|
116
|
+
cache=cache,
|
|
117
|
+
when=when,
|
|
95
118
|
)
|
|
96
119
|
|
|
120
|
+
def scoped(
|
|
121
|
+
self,
|
|
122
|
+
source: type | Callable[..., Any],
|
|
123
|
+
*,
|
|
124
|
+
interface: type | None = None,
|
|
125
|
+
when: BaseMarker | None = None,
|
|
126
|
+
) -> Self:
|
|
127
|
+
"""Binds the source to the container.
|
|
128
|
+
|
|
129
|
+
Scope is REQUEST by default but a custom scope be provided.
|
|
130
|
+
Dependency declared with this are cached no matter what.
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
the provider itself for chaining purposes
|
|
134
|
+
"""
|
|
135
|
+
self.bind(source, interface=interface, scope=Scope.REQUEST, when=when)
|
|
136
|
+
return self
|
|
137
|
+
|
|
138
|
+
def transient(
|
|
139
|
+
self,
|
|
140
|
+
source: type | Callable[..., Any],
|
|
141
|
+
*,
|
|
142
|
+
interface: type | None = None,
|
|
143
|
+
when: BaseMarker | None = None,
|
|
144
|
+
) -> Self:
|
|
145
|
+
"""Binds the source to the container as a transient dependency.
|
|
146
|
+
|
|
147
|
+
Dependencies declared this way have global scope and are never
|
|
148
|
+
cached.
|
|
149
|
+
|
|
150
|
+
Returns:
|
|
151
|
+
the provider itself for chaining purposes
|
|
152
|
+
"""
|
|
153
|
+
self.bind(source, interface=interface, when=when, cache=False)
|
|
154
|
+
return self
|
|
155
|
+
|
|
156
|
+
def extend(
|
|
157
|
+
self,
|
|
158
|
+
source: Callable[..., Any],
|
|
159
|
+
*,
|
|
160
|
+
interface: type | None = None,
|
|
161
|
+
scope: dishka.BaseScope | None = None,
|
|
162
|
+
when: BaseMarker | None = None,
|
|
163
|
+
) -> Self:
|
|
164
|
+
"""Extends a dependency declared by another provider.
|
|
165
|
+
|
|
166
|
+
This allows to 'extend' a dependency, maybe even override it entirely.
|
|
167
|
+
Particularly useful if you want to run some code on a dependency declared
|
|
168
|
+
by another provider / package.
|
|
169
|
+
|
|
170
|
+
You may also provide a scope for this.
|
|
171
|
+
|
|
172
|
+
Returns:
|
|
173
|
+
the provider itself for chaining purposes
|
|
174
|
+
"""
|
|
175
|
+
_ = self.provider.decorate(source, provides=interface, scope=scope, when=when)
|
|
176
|
+
return self
|
|
177
|
+
|
|
97
178
|
@abc.abstractmethod
|
|
98
179
|
def register(self) -> Result[Self, str]:
|
|
99
180
|
"""Register services into the application container.
|
|
@@ -1,7 +1,5 @@
|
|
|
1
1
|
"""Base implementation of the event dispatcher."""
|
|
2
2
|
|
|
3
|
-
from typing import final
|
|
4
|
-
|
|
5
3
|
from neva.arch.application import Application
|
|
6
4
|
from neva.database.manager import DatabaseManager
|
|
7
5
|
from neva.database.transaction import TransactionCallback
|
|
@@ -11,15 +9,30 @@ from neva.events.listener import EventListener
|
|
|
11
9
|
from neva.support import Err, Nothing, Result, Some
|
|
12
10
|
|
|
13
11
|
|
|
14
|
-
@final
|
|
15
12
|
class EventDispatcher:
|
|
16
13
|
"""Event dispatcher implementation."""
|
|
17
14
|
|
|
18
|
-
def __init__(
|
|
19
|
-
self
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
app: Application,
|
|
18
|
+
db: DatabaseManager,
|
|
19
|
+
registry: EventRegistry,
|
|
20
|
+
) -> None:
|
|
21
|
+
self._registry = registry
|
|
20
22
|
self._app = app
|
|
21
23
|
self._db = db
|
|
22
24
|
|
|
25
|
+
async def _before_dispatch(self, event: Event) -> None:
|
|
26
|
+
"""Extension hook called before listeners are invoked.
|
|
27
|
+
|
|
28
|
+
Override in a subclass to add cross-cutting behaviour such as
|
|
29
|
+
persisting the event to an event store. The default implementation
|
|
30
|
+
is a no-op.
|
|
31
|
+
|
|
32
|
+
Args:
|
|
33
|
+
event: The event about to be dispatched.
|
|
34
|
+
"""
|
|
35
|
+
|
|
23
36
|
async def dispatch(self, event: Event) -> list[Result[None, str]]:
|
|
24
37
|
"""Dispatch an event to all registered listeners.
|
|
25
38
|
|
|
@@ -32,6 +45,7 @@ class EventDispatcher:
|
|
|
32
45
|
Returns:
|
|
33
46
|
A list of results, one per listener invocation.
|
|
34
47
|
"""
|
|
48
|
+
await self._before_dispatch(event)
|
|
35
49
|
results: list[Result[None, str]] = []
|
|
36
50
|
listeners = self._registry.get_listeners(event)
|
|
37
51
|
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
from typing import Self, override
|
|
4
4
|
|
|
5
5
|
from neva.arch.service_provider import ServiceProvider
|
|
6
|
+
from neva.events.event_registry import EventRegistry
|
|
6
7
|
from neva.support import Ok, Result
|
|
7
8
|
|
|
8
9
|
|
|
@@ -23,5 +24,6 @@ class EventServiceProvider(ServiceProvider):
|
|
|
23
24
|
"""
|
|
24
25
|
from neva.events.dispatcher import EventDispatcher
|
|
25
26
|
|
|
27
|
+
self.bind(EventRegistry)
|
|
26
28
|
self.bind(EventDispatcher)
|
|
27
29
|
return Ok(self)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "python-neva"
|
|
3
|
-
|
|
3
|
+
version = "3.2.0"
|
|
4
4
|
description = "Add your description here"
|
|
5
5
|
readme = "README.md"
|
|
6
6
|
requires-python = ">=3.12"
|
|
@@ -37,34 +37,26 @@ dev = [
|
|
|
37
37
|
|
|
38
38
|
[project.optional-dependencies]
|
|
39
39
|
testing = ["pytest>=9.0.2", "pytest-asyncio>=0.25.3"]
|
|
40
|
-
fastapi = ["neva-fastapi>=
|
|
41
|
-
|
|
42
|
-
[tool.uv.sources]
|
|
43
|
-
neva-fastapi = { path = "../neva-fastapi", editable = true }
|
|
40
|
+
fastapi = ["neva-fastapi>=1.0.0"]
|
|
44
41
|
|
|
45
42
|
[build-system]
|
|
46
|
-
requires = ["hatchling"
|
|
43
|
+
requires = ["hatchling"]
|
|
47
44
|
build-backend = "hatchling.build"
|
|
48
45
|
|
|
49
46
|
[tool.hatch.build.targets.wheel]
|
|
50
47
|
packages = ["neva"]
|
|
51
48
|
|
|
52
|
-
[tool.hatch.version]
|
|
53
|
-
source = "versioningit"
|
|
54
|
-
|
|
55
|
-
[tool.hatch.version.format]
|
|
56
|
-
distance = "{base_version}.dev{distance}+{vcs}{rev}"
|
|
57
|
-
dirty = "{version}+dirty"
|
|
58
|
-
distance-dirty = "{next_version}.dev{distance}+{vcs}{rev}.dirty"
|
|
59
|
-
|
|
60
49
|
[tool.commitizen]
|
|
61
50
|
name = "cz_gitmoji"
|
|
62
|
-
version_provider = "
|
|
63
|
-
tag_format = "$version"
|
|
51
|
+
version_provider = "uv"
|
|
52
|
+
tag_format = "python-neva-v$version"
|
|
64
53
|
version_scheme = "pep440"
|
|
65
54
|
update_changelog_on_bump = true
|
|
66
55
|
annotated_tag = true
|
|
67
56
|
post_bump_hooks = ["scripts/retag-with-changelog.sh"]
|
|
57
|
+
# Only commits scoped to `core` (alone or in a comma-separated scope list) feed
|
|
58
|
+
# this package's changelog. See CONTRIBUTING.md.
|
|
59
|
+
changelog_pattern = '''^(?::[\w_]+:\s+)?\w+\([^)]*\bcore\b[^)]*\)!?:\s'''
|
|
68
60
|
|
|
69
61
|
[tool.basedpyright]
|
|
70
62
|
enableExperimentalFeatures = true
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import asyncio
|
|
2
|
+
from datetime import datetime
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
from typing import Self, override
|
|
5
|
+
|
|
6
|
+
from neva.arch import ServiceProvider
|
|
7
|
+
from neva.support import Ok, Result
|
|
8
|
+
from neva.support.time import utcnow
|
|
9
|
+
from neva.testing import TestCase
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def _make_config_dir(tmp_path: Path) -> Path:
|
|
13
|
+
config_dir = tmp_path / "config"
|
|
14
|
+
config_dir.mkdir()
|
|
15
|
+
|
|
16
|
+
_ = (config_dir / "app.py").write_text(
|
|
17
|
+
'config = {"name": "TestApp", "debug": True}'
|
|
18
|
+
)
|
|
19
|
+
_ = (config_dir / "providers.py").write_text(
|
|
20
|
+
"""
|
|
21
|
+
from tests.arch.test_cache import CacheServiceProvider
|
|
22
|
+
|
|
23
|
+
config = {"providers": [CacheServiceProvider]}
|
|
24
|
+
"""
|
|
25
|
+
)
|
|
26
|
+
|
|
27
|
+
return config_dir
|
|
28
|
+
|
|
29
|
+
|
|
30
|
+
class CacheProvider:
|
|
31
|
+
def __init__(self) -> None:
|
|
32
|
+
self.ts: datetime = utcnow()
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class CacheServiceProvider(ServiceProvider):
|
|
36
|
+
@override
|
|
37
|
+
def register(self) -> Result[Self, str]:
|
|
38
|
+
self.bind(CacheProvider, cache=False)
|
|
39
|
+
return Ok(self)
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class TestCache(TestCase):
|
|
43
|
+
@override
|
|
44
|
+
def create_config(self, tmp_path: Path) -> Path:
|
|
45
|
+
return _make_config_dir(tmp_path)
|
|
46
|
+
|
|
47
|
+
async def test_transient_dependency(self) -> None:
|
|
48
|
+
provider1 = self.app.make(CacheProvider).unwrap()
|
|
49
|
+
await asyncio.sleep(0.1)
|
|
50
|
+
provider2 = self.app.make(CacheProvider).unwrap()
|
|
51
|
+
assert provider1 is not provider2
|
|
52
|
+
assert provider1.ts != provider2.ts
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from dataclasses import dataclass
|
|
2
|
+
import uuid
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import Protocol, Self, override
|
|
6
|
+
|
|
7
|
+
from neva.arch import ServiceProvider
|
|
8
|
+
from neva.support import Ok, Result
|
|
9
|
+
from neva.support.time import utcnow
|
|
10
|
+
from neva.testing import TestCase
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def _base_config_dir(tmp_path: Path) -> Path:
|
|
14
|
+
config_dir = tmp_path / "config"
|
|
15
|
+
config_dir.mkdir()
|
|
16
|
+
|
|
17
|
+
_ = (config_dir / "app.py").write_text(
|
|
18
|
+
'config = {"name": "TestApp", "debug": True}'
|
|
19
|
+
)
|
|
20
|
+
return config_dir
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
def _make_config_dir(tmp_path: Path) -> Path:
|
|
24
|
+
config_dir = _base_config_dir(tmp_path)
|
|
25
|
+
|
|
26
|
+
_ = (config_dir / "providers.py").write_text(
|
|
27
|
+
"""
|
|
28
|
+
from tests.arch.test_extends import CacheServiceProvider, ExtendsServiceProvider
|
|
29
|
+
|
|
30
|
+
config = {"providers": [CacheServiceProvider, ExtendsServiceProvider]}
|
|
31
|
+
"""
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
return config_dir
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class CacheProtocol(Protocol):
|
|
38
|
+
def add(self, key: str, value: str) -> Self: ...
|
|
39
|
+
def get(self, key: str) -> str: ...
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
class Cache:
|
|
43
|
+
def __init__(self) -> None:
|
|
44
|
+
self.ts: datetime = utcnow()
|
|
45
|
+
self._inner: dict[str, str] = {}
|
|
46
|
+
|
|
47
|
+
def add(self, key: str, value: str) -> Self:
|
|
48
|
+
self._inner[key] = value
|
|
49
|
+
return self
|
|
50
|
+
|
|
51
|
+
def get(self, key: str) -> str:
|
|
52
|
+
return self._inner[key]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class TransientCache(Cache):
|
|
56
|
+
pass
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
class CacheServiceProvider(ServiceProvider):
|
|
60
|
+
@override
|
|
61
|
+
def register(self) -> Result[Self, str]:
|
|
62
|
+
self.bind(Cache)
|
|
63
|
+
self.bind(TransientCache, cache=False)
|
|
64
|
+
return Ok(self)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
class ExtendsServiceProvider(ServiceProvider):
|
|
68
|
+
def override_cache(self, cache: Cache) -> Cache:
|
|
69
|
+
return cache.add("override", uuid.uuid4().hex)
|
|
70
|
+
|
|
71
|
+
def override_transient_cache(self, cache: TransientCache) -> TransientCache:
|
|
72
|
+
return cache.add("override", uuid.uuid4().hex)
|
|
73
|
+
|
|
74
|
+
@override
|
|
75
|
+
def register(self) -> Result[Self, str]:
|
|
76
|
+
_ = self.extend(self.override_cache)
|
|
77
|
+
_ = self.extend(self.override_transient_cache)
|
|
78
|
+
return Ok(self)
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
class TestExtends(TestCase):
|
|
82
|
+
@override
|
|
83
|
+
def create_config(self, tmp_path: Path) -> Path:
|
|
84
|
+
return _make_config_dir(tmp_path)
|
|
85
|
+
|
|
86
|
+
async def test_extends_works(self) -> None:
|
|
87
|
+
cache = self.app.make(Cache).unwrap()
|
|
88
|
+
assert cache.get("override") is not None
|
|
89
|
+
|
|
90
|
+
async def test_extends_respects_cache(self) -> None:
|
|
91
|
+
cache = self.app.make(Cache).unwrap()
|
|
92
|
+
assert cache.get("override") is not None
|
|
93
|
+
cache2 = self.app.make(Cache).unwrap()
|
|
94
|
+
assert cache.get("override") == cache2.get("override")
|
|
95
|
+
|
|
96
|
+
cache3 = self.app.make(TransientCache).unwrap()
|
|
97
|
+
cache4 = self.app.make(TransientCache).unwrap()
|
|
98
|
+
assert cache3.get("override") != cache4.get("override")
|
|
@@ -8,6 +8,7 @@ import pytest
|
|
|
8
8
|
from neva.arch import Application
|
|
9
9
|
from neva.database.manager import DatabaseManager
|
|
10
10
|
from neva.events import Event, EventDispatcher
|
|
11
|
+
from neva.events.event_registry import EventRegistry
|
|
11
12
|
|
|
12
13
|
|
|
13
14
|
class OrderPlaced(Event[int]):
|
|
@@ -25,4 +26,4 @@ class UserCreated(Event[int]):
|
|
|
25
26
|
|
|
26
27
|
@pytest.fixture
|
|
27
28
|
def dispatcher(application: Application, db: DatabaseManager) -> EventDispatcher:
|
|
28
|
-
return EventDispatcher(app=application, db=db)
|
|
29
|
+
return EventDispatcher(app=application, db=db, registry=EventRegistry())
|
|
@@ -7,6 +7,7 @@ import dishka
|
|
|
7
7
|
from neva.arch import Application
|
|
8
8
|
from neva.database.manager import DatabaseManager
|
|
9
9
|
from neva.events import EventDispatcher, EventListener
|
|
10
|
+
from neva.events.event_registry import EventRegistry
|
|
10
11
|
from neva.support import Err, Ok, Result
|
|
11
12
|
from tests.events.conftest import OrderPlaced
|
|
12
13
|
|
|
@@ -134,7 +135,7 @@ class TestDIResolution:
|
|
|
134
135
|
|
|
135
136
|
di_instance_id = id((await application.make_async(TrackedListener)).unwrap())
|
|
136
137
|
|
|
137
|
-
dispatcher = EventDispatcher(app=application, db=db)
|
|
138
|
+
dispatcher = EventDispatcher(app=application, db=db, registry=EventRegistry())
|
|
138
139
|
dispatcher.listen(OrderPlaced, TrackedListener)
|
|
139
140
|
_ = await dispatcher.dispatch(OrderPlaced(event_id=1, order_id=1))
|
|
140
141
|
|
|
@@ -153,7 +154,7 @@ class TestDIResolution:
|
|
|
153
154
|
async def handle(self, event: OrderPlaced) -> Result[None, str]:
|
|
154
155
|
return Ok(None)
|
|
155
156
|
|
|
156
|
-
dispatcher = EventDispatcher(app=application, db=db)
|
|
157
|
+
dispatcher = EventDispatcher(app=application, db=db, registry=EventRegistry())
|
|
157
158
|
dispatcher.listen(OrderPlaced, UnboundListener)
|
|
158
159
|
_ = await dispatcher.dispatch(OrderPlaced(event_id=1, order_id=1))
|
|
159
160
|
|
python_neva-3.1.0/.envrc
DELETED
python_neva-3.1.0/CHANGELOG.md
DELETED
|
@@ -1,42 +0,0 @@
|
|
|
1
|
-
## 3.1.0 (2026-05-11)
|
|
2
|
-
|
|
3
|
-
### ✨ Features
|
|
4
|
-
|
|
5
|
-
- fixing db facade
|
|
6
|
-
- Add eq method to Some / Ok
|
|
7
|
-
- Deprecate register_engine, add register_connection
|
|
8
|
-
- RefreshDatabase now properly use the testcase inner app
|
|
9
|
-
- Add neva-fastapi as optional dependency
|
|
10
|
-
- renaming of make / make_async for retrocompatibility
|
|
11
|
-
|
|
12
|
-
### 🐛🚑️ Fixes
|
|
13
|
-
|
|
14
|
-
- Fix provider registration ordering
|
|
15
|
-
|
|
16
|
-
### ♻️ Refactorings
|
|
17
|
-
|
|
18
|
-
- refactor of results / db toolkit
|
|
19
|
-
|
|
20
|
-
## 3.1.0a1 (2026-05-05)
|
|
21
|
-
|
|
22
|
-
### build
|
|
23
|
-
|
|
24
|
-
- add commitizen + versioningit
|
|
25
|
-
|
|
26
|
-
### 💚👷 CI & Build
|
|
27
|
-
|
|
28
|
-
- update perms on tag script
|
|
29
|
-
- tweaking cz tags
|
|
30
|
-
- remove auto-annotated tags
|
|
31
|
-
- configure versioningit + cz
|
|
32
|
-
- Fix cz config >>> ⏰ 1
|
|
33
|
-
- add and configure cz_gitmoji >>> ⏰ 5m
|
|
34
|
-
|
|
35
|
-
### 📝💡 Documentation
|
|
36
|
-
|
|
37
|
-
- clear changelog
|
|
38
|
-
|
|
39
|
-
### 🔧🔨📦️ Configuration, Scripts, Packages
|
|
40
|
-
|
|
41
|
-
- improve auto-annotation of tags
|
|
42
|
-
- Fix tagging script
|