python-neva 4.1.0__tar.gz → 5.0.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-4.1.0 → python_neva-5.0.0}/.gitlab-ci.yml +9 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/CHANGELOG.md +20 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/PKG-INFO +47 -6
- {python_neva-4.1.0 → python_neva-5.0.0}/README.md +44 -3
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/database/manager.py +0 -2
- python_neva-5.0.0/neva/guidelines/fragments/observability.md +116 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/fragments/service-providers.md +34 -2
- python_neva-5.0.0/neva/obs/__init__.py +19 -0
- python_neva-5.0.0/neva/obs/config.py +37 -0
- python_neva-5.0.0/neva/obs/logging/__init__.py +22 -0
- python_neva-5.0.0/neva/obs/logging/channels.py +310 -0
- python_neva-5.0.0/neva/obs/logging/contracts.py +40 -0
- python_neva-5.0.0/neva/obs/logging/manager.py +196 -0
- python_neva-5.0.0/neva/obs/logging/provider.py +36 -0
- python_neva-5.0.0/neva/obs/logging/resolver.py +138 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/log.pyi +40 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/pyproject.toml +2 -3
- python_neva-5.0.0/tests/arch/test_register_resolution.py +108 -0
- python_neva-5.0.0/tests/obs/conftest.py +133 -0
- python_neva-5.0.0/tests/obs/test_channels.py +213 -0
- python_neva-5.0.0/tests/obs/test_facade.py +90 -0
- python_neva-5.0.0/tests/obs/test_manager.py +160 -0
- python_neva-5.0.0/tests/obs/test_provider_hook.py +107 -0
- python_neva-5.0.0/tests/obs/test_resolver.py +127 -0
- python_neva-5.0.0/tests/support/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/uv.lock +20 -62
- python_neva-4.1.0/neva/obs/__init__.py +0 -9
- python_neva-4.1.0/neva/obs/instrumentation/__init__.py +0 -1
- python_neva-4.1.0/neva/obs/instrumentation/sqlalchemy.py +0 -15
- python_neva-4.1.0/neva/obs/logging/__init__.py +0 -10
- python_neva-4.1.0/neva/obs/logging/manager.py +0 -90
- python_neva-4.1.0/neva/obs/logging/provider.py +0 -26
- {python_neva-4.1.0 → python_neva-5.0.0}/.claude/settings.local.json +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/.envrc +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/.gitignore +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/.pre-commit-config.yaml +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/.python-version +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/CLAUDE.md +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/application.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/config.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/facade.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/integrations/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/integrations/faststream.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/markers.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/scopes.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/arch/service_provider.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/config/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/config/base_providers.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/config/loader.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/config/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/config/repository.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/database/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/database/config.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/database/connection.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/database/provider.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/database/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/database/transaction.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/contracts/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/contracts/dispatcher.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/contracts/event.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/contracts/handler.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/contracts/listener.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/dispatcher.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/event.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/event_registry.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/listener.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/policy.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/provider.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/events/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/fragments/configuration.md +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/fragments/database-transactions.md +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/fragments/events.md +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/fragments/result-option.md +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/fragments/testing.md +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/manifest.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/guidelines/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/obs/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/polyfactory/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/polyfactory/factories.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/polyfactory/persistence.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/polyfactory/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/encryption/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/encryption/encrypter.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/encryption/protocol.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/hashing/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/hashing/config.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/hashing/hash_manager.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/hashing/hashers/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/hashing/hashers/argon2.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/hashing/hashers/bcrypt.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/hashing/hashers/protocol.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/provider.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/tokens/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/tokens/generate_token.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/tokens/hash_token.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/security/tokens/verify_token.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/accessors.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/app.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/app.pyi +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/config.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/config.pyi +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/crypt.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/crypt.pyi +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/db.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/db.pyi +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/event.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/event.pyi +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/hash.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/hash.pyi +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/facade/log.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/results.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/strategy.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/strconv.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/support/time.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/testing/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/testing/fakes.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/testing/fixtures.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/testing/py.typed +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/neva/testing/test_case.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/ruff.toml +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/scripts/retag-with-changelog.sh +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_cache.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_config_shapes.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_context.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_extends.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_facade_root_nesting.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_lifetimes.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_registration.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/arch/test_scope.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/config/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/config/test_config_path_resolution.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/config/test_loader.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/config/test_repository.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/conftest.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_connection_manager.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_database_manager.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_detached_lifespan.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_edge_cases.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_multi_connection.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_sqlalchemy_integration.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_transaction.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_transaction_callbacks.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_transaction_context.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/database/test_transaction_registry.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/conftest.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_before_dispatch.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_binding.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_deferred.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_dispatch.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_event.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_function_listener.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_immediate.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_listen_on_parent_class.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/events/test_listener_wiring.py +0 -0
- {python_neva-4.1.0/tests/support → python_neva-5.0.0/tests/obs}/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/security/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/security/test_encrypter.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/security/test_hash_manager.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/security/test_tokens.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/support/test_results.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/__init__.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/test_application_reuse.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/test_create_config_migration.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/test_event_fake.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/test_facade_restore.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/test_fixtures.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/test_refresh_database.py +0 -0
- {python_neva-4.1.0 → python_neva-5.0.0}/tests/testing/test_test_case.py +0 -0
|
@@ -48,6 +48,15 @@ pyrefly:
|
|
|
48
48
|
script:
|
|
49
49
|
- uv run --frozen pyrefly check
|
|
50
50
|
|
|
51
|
+
# The fragments shipped in the wheel cite the tests that prove their claims.
|
|
52
|
+
# Every cited path must still resolve, so a behaviour change that leaves the
|
|
53
|
+
# guidance behind fails here rather than reaching an agent as a wrong answer.
|
|
54
|
+
guidelines:
|
|
55
|
+
extends: [.uv_base, .default_rules]
|
|
56
|
+
stage: code_qa
|
|
57
|
+
script:
|
|
58
|
+
- uv run --frozen neva-boost check neva/guidelines/fragments
|
|
59
|
+
|
|
51
60
|
##### Test #####
|
|
52
61
|
pytest:
|
|
53
62
|
extends: [.uv_base, .default_rules]
|
|
@@ -1,3 +1,23 @@
|
|
|
1
|
+
## 5.0.0 (2026-09-01)
|
|
2
|
+
|
|
3
|
+
### 💥 Boom
|
|
4
|
+
|
|
5
|
+
- **obs**: drop OpenTelemetry and pyinstrument from the core
|
|
6
|
+
|
|
7
|
+
### ✨ Features
|
|
8
|
+
|
|
9
|
+
- **obs**: resolve logging through named channels
|
|
10
|
+
- **deps**: offer neva-boost through the boost extra
|
|
11
|
+
|
|
12
|
+
### 💚👷 CI & Build
|
|
13
|
+
|
|
14
|
+
- **guidelines**: fail when a fragment cites a test that is gone
|
|
15
|
+
|
|
16
|
+
### 📝💡 Documentation
|
|
17
|
+
|
|
18
|
+
- **guidelines**: rule out resolving from the container in register()
|
|
19
|
+
- **guidelines**: cover logging channels, and where OTel now lives
|
|
20
|
+
|
|
1
21
|
## 4.1.0 (2026-08-31)
|
|
2
22
|
|
|
3
23
|
### ✅🤡🧪 Tests
|
|
@@ -1,21 +1,21 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: python-neva
|
|
3
|
-
Version:
|
|
3
|
+
Version: 5.0.0
|
|
4
4
|
Summary: Add your description here
|
|
5
5
|
Requires-Python: >=3.12
|
|
6
6
|
Requires-Dist: aiosqlite>=0.20.0
|
|
7
7
|
Requires-Dist: asyncpg>=0.30.0
|
|
8
8
|
Requires-Dist: cryptography>=46.0.3
|
|
9
9
|
Requires-Dist: dishka>=1.10.0
|
|
10
|
-
Requires-Dist: opentelemetry-instrumentation-sqlalchemy>=0.62b0
|
|
11
10
|
Requires-Dist: pwdlib[argon2,bcrypt]>=0.3.0
|
|
12
11
|
Requires-Dist: pydantic>=2.7
|
|
13
|
-
Requires-Dist: pyinstrument>=5.1.1
|
|
14
12
|
Requires-Dist: sqlalchemy[asyncio]>=2.0.0
|
|
15
13
|
Requires-Dist: structlog>=25.5.0
|
|
16
14
|
Requires-Dist: typing-extensions>=4.13
|
|
17
15
|
Provides-Extra: asgi
|
|
18
16
|
Requires-Dist: neva-asgi>=0.1.1; extra == 'asgi'
|
|
17
|
+
Provides-Extra: boost
|
|
18
|
+
Requires-Dist: neva-boost>=0.2.1; extra == 'boost'
|
|
19
19
|
Provides-Extra: fastapi
|
|
20
20
|
Requires-Dist: neva-fastapi>=1.1.1; extra == 'fastapi'
|
|
21
21
|
Provides-Extra: faststream
|
|
@@ -47,17 +47,59 @@ independent repo, independently versioned and published.
|
|
|
47
47
|
|
|
48
48
|
| Package | Repo / directory | Role | Status |
|
|
49
49
|
| ----------------- | ------------------ | ------------------------------------------------------------------------------------- | ------------------------------- |
|
|
50
|
-
| `python-neva` | `neva/` | **Core, framework-agnostic.** DI, service providers, facades, Result/Option, events, security,
|
|
50
|
+
| `python-neva` | `neva/` | **Core, framework-agnostic.** DI, service providers, facades, Result/Option, events, security, structured logging, SQLAlchemy database layer. | published ([PyPI](https://pypi.org/project/python-neva/)) |
|
|
51
51
|
| `neva-fastapi` | `neva-fastapi/` | FastAPI integration — `App` extends `FastAPI` and wires dishka into routes. Pulled in via the `python-neva[fastapi]` extra. | published ([PyPI](https://pypi.org/project/neva-fastapi/)) |
|
|
52
52
|
| `neva-asgi` | `neva-asgi/` | **ASGI middleware** — correlation IDs and per-request profiling. Pure ASGI, so both the HTTP and messaging integrations can consume it. Pulled in via the `python-neva[asgi]` extra. | published ([PyPI](https://pypi.org/project/neva-asgi/)) |
|
|
53
53
|
| `neva-faststream` | `neva-faststream/` | FastStream (messaging) integration. | scaffolded, early placeholder |
|
|
54
54
|
| `neva-auth` | `neva-auth/` | Authentication & identity toolkit — token-issuance engine, OAuth2 grants, guards, pluggable formats/backends. | core complete, OAuth-server slice in progress |
|
|
55
|
+
| `neva-otel` | `neva-otel/` | **OpenTelemetry** — SDK wiring, tracer and meter providers, exporters, samplers and instrumentors. Kept out of the core so nothing is committed to an observability backend. | planned |
|
|
56
|
+
| `neva-boost` | `neva-boost/` | **Agent guidelines** — composes each installed package's versioned guideline fragments into a project's agent configuration (Claude Code skills or `AGENTS.md`). Dev-time tooling; pulled in via the `python-neva[boost]` extra. | published ([PyPI](https://pypi.org/project/neva-boost/)) |
|
|
55
57
|
| `neva-example` | `neva-example/` | Reference application consuming `python-neva[fastapi]`. Not published; demonstration + integration test bed. | unpublished |
|
|
56
58
|
|
|
57
59
|
> When working across packages locally, each repo can be cloned as a sibling
|
|
58
60
|
> directory; integration packages depend on `python-neva` from PyPI (or an
|
|
59
61
|
> editable local path during development).
|
|
60
62
|
|
|
63
|
+
## Logging
|
|
64
|
+
|
|
65
|
+
Logging is a set of named channels, each with its own driver, level and output —
|
|
66
|
+
Laravel's `Log`, configured from one `config/obs.py` namespace.
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
config = {
|
|
70
|
+
"logging": {
|
|
71
|
+
"default": "stdout",
|
|
72
|
+
"channels": {
|
|
73
|
+
"stdout": {"driver": "console", "level": "DEBUG"},
|
|
74
|
+
"json": {"driver": "json", "stream": "stderr", "level": "INFO"},
|
|
75
|
+
"audit": {"driver": "file", "path": "var/audit.log"},
|
|
76
|
+
"both": {"driver": "stack", "channels": ["json", "audit"]},
|
|
77
|
+
},
|
|
78
|
+
},
|
|
79
|
+
}
|
|
80
|
+
```
|
|
81
|
+
|
|
82
|
+
```python
|
|
83
|
+
from neva.support.facade import Log
|
|
84
|
+
|
|
85
|
+
Log.info("order placed", order_id=42) # default channel
|
|
86
|
+
Log.channel("audit").warning("role granted", actor="root")
|
|
87
|
+
Log.bind(tenant="acme") # every channel, this context
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
Drivers are `console`, `json`, `file`, `stack` and `null`. With no `config/obs.py`
|
|
91
|
+
at all, logging still works: a console channel on stdout at `DEBUG`.
|
|
92
|
+
|
|
93
|
+
Each channel carries its own structlog processor chain rather than going through
|
|
94
|
+
the process-global `structlog.configure()`, so one application's renderer cannot
|
|
95
|
+
leak into another in the same interpreter. `LogManager.processor(fn)` lets a
|
|
96
|
+
plugin add a processor to every chain — the seam `neva-otel` uses to put the
|
|
97
|
+
current span's ids on every record.
|
|
98
|
+
|
|
99
|
+
**Tracing and metrics are deliberately not here.** They live in `neva-otel`, so
|
|
100
|
+
the core carries no OpenTelemetry dependency and commits no application to an
|
|
101
|
+
observability backend.
|
|
102
|
+
|
|
61
103
|
## Develop
|
|
62
104
|
|
|
63
105
|
```bash
|
|
@@ -103,10 +145,9 @@ rewrite the new tag with the rendered changelog as its annotation.
|
|
|
103
145
|
## Feature ideas
|
|
104
146
|
|
|
105
147
|
- Feature-parity FastStream integration (now scaffolded as `neva-faststream`)
|
|
106
|
-
- Finish cleaning up the core package (moving any remaining ASGI-dependent code into the appropriate integration packages)
|
|
107
148
|
- Improved router registration (auto-discovery OR provider-based? both?)
|
|
108
149
|
- Improved security tooling (performance improvements, better defaults, etc.)
|
|
109
|
-
-
|
|
150
|
+
- OpenTelemetry as a plugin (`neva-otel`) — the core keeps logging and takes no OTel dependency
|
|
110
151
|
- Improved factory module (based on Polyfactory)
|
|
111
152
|
- Queue/Jobs system
|
|
112
153
|
- CLI integration
|
|
@@ -18,17 +18,59 @@ independent repo, independently versioned and published.
|
|
|
18
18
|
|
|
19
19
|
| Package | Repo / directory | Role | Status |
|
|
20
20
|
| ----------------- | ------------------ | ------------------------------------------------------------------------------------- | ------------------------------- |
|
|
21
|
-
| `python-neva` | `neva/` | **Core, framework-agnostic.** DI, service providers, facades, Result/Option, events, security,
|
|
21
|
+
| `python-neva` | `neva/` | **Core, framework-agnostic.** DI, service providers, facades, Result/Option, events, security, structured logging, SQLAlchemy database layer. | published ([PyPI](https://pypi.org/project/python-neva/)) |
|
|
22
22
|
| `neva-fastapi` | `neva-fastapi/` | FastAPI integration — `App` extends `FastAPI` and wires dishka into routes. Pulled in via the `python-neva[fastapi]` extra. | published ([PyPI](https://pypi.org/project/neva-fastapi/)) |
|
|
23
23
|
| `neva-asgi` | `neva-asgi/` | **ASGI middleware** — correlation IDs and per-request profiling. Pure ASGI, so both the HTTP and messaging integrations can consume it. Pulled in via the `python-neva[asgi]` extra. | published ([PyPI](https://pypi.org/project/neva-asgi/)) |
|
|
24
24
|
| `neva-faststream` | `neva-faststream/` | FastStream (messaging) integration. | scaffolded, early placeholder |
|
|
25
25
|
| `neva-auth` | `neva-auth/` | Authentication & identity toolkit — token-issuance engine, OAuth2 grants, guards, pluggable formats/backends. | core complete, OAuth-server slice in progress |
|
|
26
|
+
| `neva-otel` | `neva-otel/` | **OpenTelemetry** — SDK wiring, tracer and meter providers, exporters, samplers and instrumentors. Kept out of the core so nothing is committed to an observability backend. | planned |
|
|
27
|
+
| `neva-boost` | `neva-boost/` | **Agent guidelines** — composes each installed package's versioned guideline fragments into a project's agent configuration (Claude Code skills or `AGENTS.md`). Dev-time tooling; pulled in via the `python-neva[boost]` extra. | published ([PyPI](https://pypi.org/project/neva-boost/)) |
|
|
26
28
|
| `neva-example` | `neva-example/` | Reference application consuming `python-neva[fastapi]`. Not published; demonstration + integration test bed. | unpublished |
|
|
27
29
|
|
|
28
30
|
> When working across packages locally, each repo can be cloned as a sibling
|
|
29
31
|
> directory; integration packages depend on `python-neva` from PyPI (or an
|
|
30
32
|
> editable local path during development).
|
|
31
33
|
|
|
34
|
+
## Logging
|
|
35
|
+
|
|
36
|
+
Logging is a set of named channels, each with its own driver, level and output —
|
|
37
|
+
Laravel's `Log`, configured from one `config/obs.py` namespace.
|
|
38
|
+
|
|
39
|
+
```python
|
|
40
|
+
config = {
|
|
41
|
+
"logging": {
|
|
42
|
+
"default": "stdout",
|
|
43
|
+
"channels": {
|
|
44
|
+
"stdout": {"driver": "console", "level": "DEBUG"},
|
|
45
|
+
"json": {"driver": "json", "stream": "stderr", "level": "INFO"},
|
|
46
|
+
"audit": {"driver": "file", "path": "var/audit.log"},
|
|
47
|
+
"both": {"driver": "stack", "channels": ["json", "audit"]},
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
}
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from neva.support.facade import Log
|
|
55
|
+
|
|
56
|
+
Log.info("order placed", order_id=42) # default channel
|
|
57
|
+
Log.channel("audit").warning("role granted", actor="root")
|
|
58
|
+
Log.bind(tenant="acme") # every channel, this context
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Drivers are `console`, `json`, `file`, `stack` and `null`. With no `config/obs.py`
|
|
62
|
+
at all, logging still works: a console channel on stdout at `DEBUG`.
|
|
63
|
+
|
|
64
|
+
Each channel carries its own structlog processor chain rather than going through
|
|
65
|
+
the process-global `structlog.configure()`, so one application's renderer cannot
|
|
66
|
+
leak into another in the same interpreter. `LogManager.processor(fn)` lets a
|
|
67
|
+
plugin add a processor to every chain — the seam `neva-otel` uses to put the
|
|
68
|
+
current span's ids on every record.
|
|
69
|
+
|
|
70
|
+
**Tracing and metrics are deliberately not here.** They live in `neva-otel`, so
|
|
71
|
+
the core carries no OpenTelemetry dependency and commits no application to an
|
|
72
|
+
observability backend.
|
|
73
|
+
|
|
32
74
|
## Develop
|
|
33
75
|
|
|
34
76
|
```bash
|
|
@@ -74,10 +116,9 @@ rewrite the new tag with the rendered changelog as its annotation.
|
|
|
74
116
|
## Feature ideas
|
|
75
117
|
|
|
76
118
|
- Feature-parity FastStream integration (now scaffolded as `neva-faststream`)
|
|
77
|
-
- Finish cleaning up the core package (moving any remaining ASGI-dependent code into the appropriate integration packages)
|
|
78
119
|
- Improved router registration (auto-discovery OR provider-based? both?)
|
|
79
120
|
- Improved security tooling (performance improvements, better defaults, etc.)
|
|
80
|
-
-
|
|
121
|
+
- OpenTelemetry as a plugin (`neva-otel`) — the core keeps logging and takes no OTel dependency
|
|
81
122
|
- Improved factory module (based on Polyfactory)
|
|
82
123
|
- Queue/Jobs system
|
|
83
124
|
- CLI integration
|
|
@@ -10,7 +10,6 @@ from neva.database.config import ConnectionConfig
|
|
|
10
10
|
from neva.database.connection import ConnectionManager, TransactionContext
|
|
11
11
|
from neva.database.transaction import BoundTransaction
|
|
12
12
|
from neva.obs import LogManager
|
|
13
|
-
from neva.obs.instrumentation.sqlalchemy import instrument
|
|
14
13
|
from neva.support import Nothing, Option, Some, from_optional
|
|
15
14
|
|
|
16
15
|
|
|
@@ -36,7 +35,6 @@ class DatabaseManager:
|
|
|
36
35
|
name: The connection name.
|
|
37
36
|
engine: The async engine.
|
|
38
37
|
"""
|
|
39
|
-
instrument(engine)
|
|
40
38
|
self._connections[name] = ConnectionManager(
|
|
41
39
|
name,
|
|
42
40
|
self._tx_context,
|
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
---
|
|
2
|
+
id: observability
|
|
3
|
+
title: Observability
|
|
4
|
+
requires: python-neva>=5.0
|
|
5
|
+
triggers: [logging, Log facade, log channel, structured logging, log level, JSON logs, audit log, obs config, tracing, OpenTelemetry, metrics, correlation id]
|
|
6
|
+
priority: 55
|
|
7
|
+
verified_by: [tests/obs/test_channels.py, tests/obs/test_resolver.py, tests/obs/test_manager.py, tests/obs/test_facade.py, tests/obs/test_provider_hook.py]
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
# Observability
|
|
11
|
+
|
|
12
|
+
The core ships **logging only**. Tracing, metrics, exporters and instrumentors live in a
|
|
13
|
+
separate plugin, so installing the core commits an application to no observability backend.
|
|
14
|
+
There is no `Trace` facade, no tracer, and no OpenTelemetry dependency here — do not add one.
|
|
15
|
+
|
|
16
|
+
## Channels
|
|
17
|
+
|
|
18
|
+
Logging is a set of named channels, each with its own driver, level and output. One
|
|
19
|
+
`config/obs.py` namespace configures them.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
config = {
|
|
23
|
+
"logging": {
|
|
24
|
+
"default": "stdout",
|
|
25
|
+
"channels": {
|
|
26
|
+
"stdout": {"driver": "console", "level": "DEBUG"},
|
|
27
|
+
"json": {"driver": "json", "stream": "stderr", "level": "INFO"},
|
|
28
|
+
"audit": {"driver": "file", "path": "var/audit.log"},
|
|
29
|
+
"both": {"driver": "stack", "channels": ["json", "audit"]},
|
|
30
|
+
"quiet": {"driver": "null"},
|
|
31
|
+
},
|
|
32
|
+
},
|
|
33
|
+
}
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
| Driver | Writes | Driver-specific keys |
|
|
37
|
+
| --- | --- | --- |
|
|
38
|
+
| `console` | human-readable, to a stream | `stream` |
|
|
39
|
+
| `json` | one JSON object per line, to a stream | `stream` |
|
|
40
|
+
| `file` | JSON lines appended to a path | `path` (required) |
|
|
41
|
+
| `stack` | fans one record out to other channels | `channels` (required) |
|
|
42
|
+
| `null` | nothing | — |
|
|
43
|
+
|
|
44
|
+
`level` is one of `DEBUG`/`INFO`/`WARNING`/`ERROR`/`CRITICAL`, default `DEBUG`. `stream` is
|
|
45
|
+
`stdout` (default) or `stderr`. A `file` channel creates parent directories and holds its
|
|
46
|
+
handle open for the process's life.
|
|
47
|
+
|
|
48
|
+
**With no `config/obs.py` at all**, logging still works: a `console` channel named `stdout` on
|
|
49
|
+
stdout at `DEBUG`.
|
|
50
|
+
|
|
51
|
+
## Writing
|
|
52
|
+
|
|
53
|
+
```python
|
|
54
|
+
from neva.support.facade import Log
|
|
55
|
+
|
|
56
|
+
Log.info("order placed", order_id=42) # default channel
|
|
57
|
+
Log.channel("audit").warning("role granted", actor="root")
|
|
58
|
+
```
|
|
59
|
+
|
|
60
|
+
Level methods go to the default channel. Everything after the message is structured context,
|
|
61
|
+
not format arguments — never build the message with an f-string when the value belongs in a
|
|
62
|
+
field.
|
|
63
|
+
|
|
64
|
+
`Log.channel(name)` **raises** on a name no channel is configured for, carrying the reason.
|
|
65
|
+
A typo is a configuration mistake, and serving the default instead would hide it behind
|
|
66
|
+
working output. Where the failure has to be a value, `Log.channels` gives the resolver and
|
|
67
|
+
`use(name)` returns `Result`.
|
|
68
|
+
|
|
69
|
+
## Ambient context
|
|
70
|
+
|
|
71
|
+
```python
|
|
72
|
+
Log.bind(tenant="acme") # every subsequent record in this context, every channel
|
|
73
|
+
Log.unbind("tenant")
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
Backed by structlog's contextvars, so a binding follows the async task rather than the
|
|
77
|
+
channel. This is how a request's correlation ID reaches log lines without being passed
|
|
78
|
+
around — `neva-asgi`'s middleware binds it the same way.
|
|
79
|
+
|
|
80
|
+
## Enriching records from a plugin
|
|
81
|
+
|
|
82
|
+
`LogManager.processor(fn)` appends a structlog processor to **every** channel's chain. This is
|
|
83
|
+
the seam a plugin adds derived fields through — the OpenTelemetry plugin injects the current
|
|
84
|
+
span's ids this way.
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
class MyProvider(ServiceProvider):
|
|
88
|
+
@asynccontextmanager
|
|
89
|
+
async def lifespan(self) -> AsyncIterator[None]:
|
|
90
|
+
manager = (await self.app.make_async(LogManager)).unwrap()
|
|
91
|
+
_ = manager.processor(add_my_field)
|
|
92
|
+
yield
|
|
93
|
+
```
|
|
94
|
+
|
|
95
|
+
**Call it from `lifespan()`, never from `register()`.** Resolving anything during
|
|
96
|
+
registration hands back an instance the booted application does not use, so a processor
|
|
97
|
+
registered there silently never runs. See the service-providers fragment.
|
|
98
|
+
|
|
99
|
+
Records emitted by base providers' own startup are not enriched — their lifespans run before
|
|
100
|
+
a plugin's. Everything after is. Already-built channels are discarded on registration, so the
|
|
101
|
+
next resolution picks the processor up.
|
|
102
|
+
|
|
103
|
+
Custom drivers register the same way, through `resolver.driver(name, builder)`.
|
|
104
|
+
|
|
105
|
+
## Rules
|
|
106
|
+
|
|
107
|
+
- **Never call `structlog.configure()`.** It is process-global; channels carry their own
|
|
108
|
+
chains precisely so one application's renderer cannot leak into another in the same
|
|
109
|
+
interpreter. Configuring it globally breaks that isolation and every channel's level.
|
|
110
|
+
- **`merge_contextvars` must lead every chain.** A custom driver that omits it silently drops
|
|
111
|
+
the correlation ID from every line it renders.
|
|
112
|
+
- **A broken default channel fails at boot**, not at the first log call — the framework logs
|
|
113
|
+
during provider startup, so a `file` channel with no `path` surfaces there. The error names
|
|
114
|
+
the channel and the reason.
|
|
115
|
+
- `LogManager()` with no application is legitimate and serves one console channel; it is what
|
|
116
|
+
a test needing somewhere for records to go should use.
|
|
@@ -4,7 +4,7 @@ title: Service providers and bindings
|
|
|
4
4
|
requires: python-neva>=4.0
|
|
5
5
|
triggers: [registering a service, binding a dependency, provider.py, wiring a component, DI container, resolving a type]
|
|
6
6
|
priority: 20
|
|
7
|
-
verified_by: [tests/arch/test_lifetimes.py, tests/arch/test_registration.py, tests/arch/test_cache.py, tests/arch/test_scope.py, tests/arch/test_extends.py]
|
|
7
|
+
verified_by: [tests/arch/test_lifetimes.py, tests/arch/test_registration.py, tests/arch/test_cache.py, tests/arch/test_scope.py, tests/arch/test_extends.py, tests/arch/test_register_resolution.py]
|
|
8
8
|
---
|
|
9
9
|
|
|
10
10
|
# Service providers and bindings
|
|
@@ -28,6 +28,36 @@ class ActorServiceProvider(ServiceProvider):
|
|
|
28
28
|
`register()` returns `Ok(self)`, or `Err("...")` to abort boot with a message. Bind only — no
|
|
29
29
|
I/O, no connections. Startup work goes in `lifespan()`.
|
|
30
30
|
|
|
31
|
+
## Never resolve in register()
|
|
32
|
+
|
|
33
|
+
`register()` **declares**; `lifespan()` **uses**. Calling `make()` or `make_async()` during
|
|
34
|
+
registration cannot work, and fails in one of two ways depending on who bound the type:
|
|
35
|
+
|
|
36
|
+
| Resolving | Outcome |
|
|
37
|
+
| --- | --- |
|
|
38
|
+
| A type **this provider** binds | `Err` — a provider joins the graph only once `register()` has returned, so its own bindings are not there yet. |
|
|
39
|
+
| A type **another provider** bound | Succeeds, then is **orphaned**. Resolving builds the container early; a successful registration discards it, so the booted application serves a *different* instance. |
|
|
40
|
+
|
|
41
|
+
The second is the dangerous one: nothing raises, nothing logs, and the object looks right. A
|
|
42
|
+
provider that configures a manager this way — adding a log processor, registering a driver —
|
|
43
|
+
silently configures a throwaway.
|
|
44
|
+
|
|
45
|
+
```python
|
|
46
|
+
def register(self) -> Result[Self, str]:
|
|
47
|
+
manager = self.app.make(SomeManager).unwrap() # WRONG: discarded instance
|
|
48
|
+
manager.configure(...) # silently affects nothing
|
|
49
|
+
return Ok(self)
|
|
50
|
+
|
|
51
|
+
@asynccontextmanager
|
|
52
|
+
async def lifespan(self) -> AsyncIterator[None]:
|
|
53
|
+
manager = (await self.app.make_async(SomeManager)).unwrap() # the real one
|
|
54
|
+
manager.configure(...)
|
|
55
|
+
yield
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
Needing another service in order to *bind* is a sign the binding wants a factory function
|
|
59
|
+
taking it as a parameter — let the container inject it, rather than reaching for it.
|
|
60
|
+
|
|
31
61
|
## A lifetime is mandatory
|
|
32
62
|
|
|
33
63
|
Bare `bind()` with neither `scope` nor `cache` **raises `TypeError`** since 4.0. Use the named
|
|
@@ -111,4 +141,6 @@ catches typos before boot. Each package declares the shape for the keys it reads
|
|
|
111
141
|
- A provider absent from the `providers` config never boots, and nothing says so.
|
|
112
142
|
- Resolution is by **type**. There are no string keys; for two implementations of one interface
|
|
113
143
|
use distinct types or markers.
|
|
114
|
-
- `register()` must be pure binding. Connections
|
|
144
|
+
- `register()` must be pure binding. Connections **and any `make()` call** belong in
|
|
145
|
+
`lifespan()` — see above; resolving during registration silently yields a discarded
|
|
146
|
+
instance.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
"""Observability tooling.
|
|
2
|
+
|
|
3
|
+
Logging only: tracing, metrics and their exporters live in the `neva-otel`
|
|
4
|
+
plugin, so the core commits no application to an observability backend.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from neva.obs.config import ChannelConfig, LoggingConfig, ObsConfig
|
|
8
|
+
from neva.obs.logging import Channel, ChannelResolver, LogManager, LogServiceProvider
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"Channel",
|
|
13
|
+
"ChannelConfig",
|
|
14
|
+
"ChannelResolver",
|
|
15
|
+
"LogManager",
|
|
16
|
+
"LogServiceProvider",
|
|
17
|
+
"LoggingConfig",
|
|
18
|
+
"ObsConfig",
|
|
19
|
+
]
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
"""Observability configs."""
|
|
2
|
+
|
|
3
|
+
from typing import Literal, NotRequired, TypedDict
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
LogLevel = Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"]
|
|
7
|
+
ChannelDriver = Literal["console", "json", "file", "stack", "null"]
|
|
8
|
+
Stream = Literal["stdout", "stderr"]
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
class ChannelConfig(TypedDict):
|
|
12
|
+
"""Configuration for a single log channel.
|
|
13
|
+
|
|
14
|
+
Three keys are driver-specific and carry no meaning elsewhere: `stream`
|
|
15
|
+
belongs to `console` and `json`, `path` to `file`, and `channels` to
|
|
16
|
+
`stack`. A `file` channel without `path`, or a `stack` without `channels`,
|
|
17
|
+
fails to resolve rather than logging nowhere.
|
|
18
|
+
"""
|
|
19
|
+
|
|
20
|
+
driver: ChannelDriver
|
|
21
|
+
level: NotRequired[LogLevel]
|
|
22
|
+
stream: NotRequired[Stream]
|
|
23
|
+
path: NotRequired[str]
|
|
24
|
+
channels: NotRequired[list[str]]
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class LoggingConfig(TypedDict):
|
|
28
|
+
"""Logging config."""
|
|
29
|
+
|
|
30
|
+
default: NotRequired[str]
|
|
31
|
+
channels: NotRequired[dict[str, ChannelConfig]]
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
class ObsConfig(TypedDict):
|
|
35
|
+
"""Observability config."""
|
|
36
|
+
|
|
37
|
+
logging: NotRequired[LoggingConfig]
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
"""Logging module.
|
|
2
|
+
|
|
3
|
+
Structured logging over structlog, shaped as named channels resolved from
|
|
4
|
+
configuration, reached through the `Log` facade.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from neva.obs.logging.channels import NullChannel, StackChannel
|
|
8
|
+
from neva.obs.logging.contracts import Channel, Processor
|
|
9
|
+
from neva.obs.logging.manager import LogManager
|
|
10
|
+
from neva.obs.logging.provider import LogServiceProvider
|
|
11
|
+
from neva.obs.logging.resolver import ChannelResolver
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
__all__ = [
|
|
15
|
+
"Channel",
|
|
16
|
+
"ChannelResolver",
|
|
17
|
+
"LogManager",
|
|
18
|
+
"LogServiceProvider",
|
|
19
|
+
"NullChannel",
|
|
20
|
+
"Processor",
|
|
21
|
+
"StackChannel",
|
|
22
|
+
]
|