python-corekit 0.2.0__tar.gz → 0.4.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_corekit-0.2.0/python_corekit.egg-info → python_corekit-0.4.0}/PKG-INFO +38 -9
- {python_corekit-0.2.0 → python_corekit-0.4.0}/README.md +35 -7
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/api/application.py +47 -9
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/api/lifespan.py +26 -3
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/concurrency/__init__.py +2 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/concurrency/decorators.py +32 -5
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/concurrency/thread_local.py +2 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/concurrency/worker.py +9 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/config/loader.py +42 -5
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/config/settings.py +11 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/__init__.py +7 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/connectable.py +45 -4
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/redis/connection.py +53 -10
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/__init__.py +2 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/connection.py +39 -5
- python_corekit-0.4.0/corekit/connections/sql/fields/__init__.py +7 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/fields/jsonb.py +13 -6
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/migration/__init__.py +4 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/migration/operations.py +69 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/operations/base.py +11 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/operations/statements.py +25 -5
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/table.py +7 -29
- python_corekit-0.4.0/corekit/crypto/__init__.py +3 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/crypto/constants.py +2 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/crypto/hasher.py +9 -4
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/dataset.py +8 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/expressions/__init__.py +3 -3
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/expressions/comparison.py +19 -80
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/expressions/expression.py +0 -32
- python_corekit-0.4.0/corekit/data/expressions/operator.py +39 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/stats.py +3 -0
- python_corekit-0.4.0/corekit/decorators/exception_handling.py +72 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/docker/watchdog.py +50 -31
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/__init__.py +2 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/connection.py +14 -12
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/extract/extractor.py +6 -13
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/orchestrator.py +19 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/schemas.py +2 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/transform/transformer.py +4 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/publisher.py +1 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/reader.py +26 -21
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/sse.py +4 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/websocket.py +24 -11
- python_corekit-0.4.0/corekit/exceptions/__init__.py +33 -0
- python_corekit-0.4.0/corekit/exceptions/base.py +174 -0
- python_corekit-0.4.0/corekit/exceptions/enum.py +17 -0
- python_corekit-0.4.0/corekit/exceptions/types.py +17 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/files/__init__.py +2 -4
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/files/base.py +15 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/files/enum.py +0 -5
- python_corekit-0.4.0/corekit/files/json.py +26 -0
- python_corekit-0.4.0/corekit/http/__init__.py +56 -0
- python_corekit-0.4.0/corekit/http/api.py +24 -0
- python_corekit-0.4.0/corekit/http/client.py +234 -0
- python_corekit-0.4.0/corekit/http/exceptions.py +140 -0
- python_corekit-0.4.0/corekit/http/response.py +61 -0
- python_corekit-0.4.0/corekit/http/status.py +89 -0
- python_corekit-0.4.0/corekit/http/stream.py +110 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/jobs/runner.py +12 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/jobs/task.py +23 -2
- python_corekit-0.4.0/corekit/llm/__init__.py +134 -0
- python_corekit-0.4.0/corekit/llm/client.py +179 -0
- python_corekit-0.4.0/corekit/llm/enum.py +123 -0
- python_corekit-0.4.0/corekit/llm/events.py +96 -0
- python_corekit-0.4.0/corekit/llm/messages.py +173 -0
- python_corekit-0.4.0/corekit/llm/prompts/__init__.py +19 -0
- python_corekit-0.4.0/corekit/llm/prompts/enum.py +54 -0
- python_corekit-0.4.0/corekit/llm/prompts/exceptions.py +22 -0
- python_corekit-0.4.0/corekit/llm/prompts/loader.py +139 -0
- python_corekit-0.4.0/corekit/llm/prompts/template.py +53 -0
- python_corekit-0.4.0/corekit/llm/protocols.py +65 -0
- python_corekit-0.4.0/corekit/llm/streaming.py +149 -0
- python_corekit-0.4.0/corekit/llm/tools/__init__.py +19 -0
- python_corekit-0.4.0/corekit/llm/tools/base.py +118 -0
- python_corekit-0.4.0/corekit/llm/tools/detection.py +99 -0
- python_corekit-0.4.0/corekit/llm/tools/loop.py +255 -0
- python_corekit-0.4.0/corekit/llm/tools/registry.py +103 -0
- python_corekit-0.4.0/corekit/llm/wire.py +199 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/log_monitor/models.py +8 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/log_monitor/service.py +77 -38
- python_corekit-0.4.0/corekit/notifications/base.py +59 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/observability/__init__.py +12 -5
- python_corekit-0.4.0/corekit/observability/benchmarkable.py +44 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/observability/loggable.py +21 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/observability/request_context.py +55 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/observability/timing/split.py +14 -0
- python_corekit-0.4.0/corekit/observability/timing/timer.py +54 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/registry/__init__.py +2 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/registry/registry.py +55 -14
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/__init__.py +2 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/enum.py +22 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/types.py +6 -1
- python_corekit-0.4.0/corekit/schemas/version.py +58 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/serialization/__init__.py +2 -0
- python_corekit-0.4.0/corekit/serialization/pickle_file.py +61 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/serialization/serializable.py +22 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/serialization/serializer.py +9 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/__init__.py +2 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/collections.py +38 -14
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/payload.py +12 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/pyproject.toml +6 -3
- {python_corekit-0.2.0 → python_corekit-0.4.0/python_corekit.egg-info}/PKG-INFO +38 -9
- {python_corekit-0.2.0 → python_corekit-0.4.0}/python_corekit.egg-info/SOURCES.txt +30 -3
- {python_corekit-0.2.0 → python_corekit-0.4.0}/python_corekit.egg-info/requires.txt +1 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_application.py +46 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_architecture.py +6 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_config.py +6 -5
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_connections.py +26 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_data.py +10 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_docker.py +13 -2
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_etl.py +24 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_events.py +67 -6
- python_corekit-0.4.0/tests/test_exceptions.py +90 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_expressions.py +10 -88
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_expressions_sqlalchemy.py +26 -3
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_files.py +23 -3
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_jobs.py +32 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_lifespan.py +14 -0
- python_corekit-0.4.0/tests/test_llm.py +492 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_log_monitor.py +115 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_migration.py +42 -0
- python_corekit-0.4.0/tests/test_notifications.py +40 -0
- python_corekit-0.4.0/tests/test_observability.py +75 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_operations.py +36 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_redis.py +47 -4
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_registry.py +34 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_request_context.py +33 -1
- python_corekit-0.4.0/tests/test_requests.py +389 -0
- python_corekit-0.4.0/tests/test_schemas_enum.py +47 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_serialization.py +25 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_sql.py +45 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_utils.py +39 -1
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_utils_collections.py +55 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_utils_payload.py +16 -0
- python_corekit-0.2.0/corekit/connections/sql/fields/__init__.py +0 -7
- python_corekit-0.2.0/corekit/constants.py +0 -45
- python_corekit-0.2.0/corekit/crypto/__init__.py +0 -1
- python_corekit-0.2.0/corekit/data/expressions/operator.py +0 -54
- python_corekit-0.2.0/corekit/decorators/exception_handling.py +0 -44
- python_corekit-0.2.0/corekit/exceptions/__init__.py +0 -18
- python_corekit-0.2.0/corekit/exceptions/base.py +0 -45
- python_corekit-0.2.0/corekit/exceptions/http/exceptions.py +0 -37
- python_corekit-0.2.0/corekit/exceptions/types.py +0 -17
- python_corekit-0.2.0/corekit/files/json.py +0 -12
- python_corekit-0.2.0/corekit/files/pickle.py +0 -12
- python_corekit-0.2.0/corekit/http/__init__.py +0 -13
- python_corekit-0.2.0/corekit/http/client.py +0 -176
- python_corekit-0.2.0/corekit/http/response.py +0 -12
- python_corekit-0.2.0/corekit/notifications/base.py +0 -51
- python_corekit-0.2.0/corekit/observability/benchmarkable.py +0 -12
- python_corekit-0.2.0/corekit/observability/timing/timer.py +0 -30
- python_corekit-0.2.0/tests/test_requests.py +0 -204
- {python_corekit-0.2.0 → python_corekit-0.4.0}/LICENSE +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/api/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/api/handler.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/api/middleware.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/api/responses.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/api/routers.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/config/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/config/sources.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/decorators.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/redis/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/registry.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/migration/base.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/migration/registry.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/migration/table.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/operations/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/connections/sql/query.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/crypto/enum.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/expressions/target.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/data/record.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/decorators/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/decorators/warnings.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/docker/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/extract/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/extract/schemas.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/load/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/load/loader.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/load/schemas.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/transform/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/etl/transform/schemas.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/enum.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/frames.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/events/models.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/exceptions/custom/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/exceptions/http/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/files/toml.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/http/exponential_backoff.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/jobs/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/jobs/registry.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/log_monitor/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/log_monitor/constants.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/notifications/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/notifications/models.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/observability/timing/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/observability/timing/constants.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/py.typed +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/registry/ordered.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/dataclasses/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/models/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/models/arbitrary.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/models/date_models.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/pydantic/__init__.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/schemas/pydantic/fields.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/serialization/enum.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/coercion.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/ids.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/raise_exc.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/text.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/time.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/validators.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/corekit/utils/void.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/python_corekit.egg-info/dependency_links.txt +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/python_corekit.egg-info/top_level.txt +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/setup.cfg +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/setup.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_api.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_concurrency.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_imports.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_imports_are_top_level.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_middleware_stack.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_ordered_registry.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_utils_coercion.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_utils_ids.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_utils_text.py +0 -0
- {python_corekit-0.2.0 → python_corekit-0.4.0}/tests/test_utils_time.py +0 -0
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: python-corekit
|
|
3
|
-
Version: 0.
|
|
4
|
-
Summary: Shared foundations for Python projects: logging, benchmarking, registries, FastAPI application and routers, SQL statements and migrations, background tasks, and
|
|
3
|
+
Version: 0.4.0
|
|
4
|
+
Summary: Shared foundations for Python projects: logging, benchmarking, registries, FastAPI application and routers, SQL statements and migrations, background tasks, ETL, and LLM chat/tool primitives
|
|
5
5
|
Author: Steven Jacobsen
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
Project-URL: Homepage, https://github.com/stevejaker/corekit
|
|
@@ -18,6 +18,7 @@ License-File: LICENSE
|
|
|
18
18
|
Requires-Dist: pydantic<3,>=2.10
|
|
19
19
|
Requires-Dist: pydantic-settings<3,>=2.0
|
|
20
20
|
Requires-Dist: fastapi<1,>=0.115
|
|
21
|
+
Requires-Dist: starlette<0.47,>=0.40
|
|
21
22
|
Requires-Dist: sqlmodel<0.1,>=0.0.16
|
|
22
23
|
Requires-Dist: SQLAlchemy<3,>=2.0
|
|
23
24
|
Requires-Dist: redis<7,>=5.0
|
|
@@ -37,7 +38,8 @@ Dynamic: license-file
|
|
|
37
38
|
|
|
38
39
|
Shared foundations for Python projects: structured logging, benchmarking,
|
|
39
40
|
registries, a FastAPI application with routers and handlers, SQL statements and
|
|
40
|
-
migrations, background tasks, an in-memory record store,
|
|
41
|
+
migrations, background tasks, an in-memory record store, ETL scaffolding, and
|
|
42
|
+
OpenAI-shaped chat / tool-loop primitives.
|
|
41
43
|
|
|
42
44
|
Requires Python 3.11+.
|
|
43
45
|
|
|
@@ -55,7 +57,7 @@ extras to remember, and no import that fails because something was left out.
|
|
|
55
57
|
Pin a compatible release rather than tracking whatever is newest:
|
|
56
58
|
|
|
57
59
|
```
|
|
58
|
-
python-corekit~=0.
|
|
60
|
+
python-corekit~=0.4.0
|
|
59
61
|
```
|
|
60
62
|
|
|
61
63
|
Before 1.0, the minor version carries breaking changes.
|
|
@@ -281,10 +283,36 @@ ceiling, not 9,999 threads.
|
|
|
281
283
|
Failures propagate by default. Pass `raise_on_error=False` to log and skip them
|
|
282
284
|
instead, which loses results silently and so is opt-in.
|
|
283
285
|
|
|
286
|
+
## LLM chat and tools
|
|
287
|
+
|
|
288
|
+
OpenAI-shaped messages, a tool registry, an agentic loop, and a thin
|
|
289
|
+
``ChatCompletionsClient`` (a ``BaseApiClient``) for any OpenAI-compatible
|
|
290
|
+
server — llama.cpp, vLLM, OpenAI, and so on. Not a multi-provider generation
|
|
291
|
+
facade.
|
|
292
|
+
|
|
293
|
+
```python
|
|
294
|
+
from corekit.llm import ChatCompletionsClient, PromptLoader, ToolLoop, ToolRegistry
|
|
295
|
+
|
|
296
|
+
client = ChatCompletionsClient("http://localhost:8080/v1", model="local")
|
|
297
|
+
prompts = PromptLoader("/path/to/prompts") # mount any compatible tree
|
|
298
|
+
tmpl = prompts.load("summarization", "general", system="1.0.0", user="1.0.0")
|
|
299
|
+
|
|
300
|
+
registry = ToolRegistry()
|
|
301
|
+
registry.register(MyTool())
|
|
302
|
+
loop = ToolLoop(client, registry)
|
|
303
|
+
async for event in loop.stream(tmpl.messages(text="…", style="brief", length="short")):
|
|
304
|
+
...
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
``PromptLoader`` reads versioned ``.txt`` files from a directory you point at
|
|
308
|
+
(``{kind}/{name}/{role}-v{version}.txt``, plus ``common/`` fragments). Prompt
|
|
309
|
+
*text* stays with the consumer — corekit only ships the loader. See
|
|
310
|
+
`docs/LLM_EXTRACTION.md`.
|
|
311
|
+
|
|
284
312
|
## HTTP clients
|
|
285
313
|
|
|
286
314
|
```python
|
|
287
|
-
from corekit.http
|
|
315
|
+
from corekit.http import BaseApiClient
|
|
288
316
|
|
|
289
317
|
class GithubClient(BaseApiClient):
|
|
290
318
|
"""
|
|
@@ -301,7 +329,8 @@ response.data["login"]
|
|
|
301
329
|
|
|
302
330
|
Retries 429 and 5xx with exponential backoff. Every response is a
|
|
303
331
|
`BaseApiResponse`, so a non-JSON error page leaves `data` empty rather than
|
|
304
|
-
raising.
|
|
332
|
+
raising. The same methods are awaitable inside a running event loop:
|
|
333
|
+
`await client.get(...)`.
|
|
305
334
|
|
|
306
335
|
## Serialization
|
|
307
336
|
|
|
@@ -351,7 +380,7 @@ Settings are grouped by concern, so `get_settings().concurrency.max_threads`
|
|
|
351
380
|
says where a value belongs. Environment variables use a double underscore for
|
|
352
381
|
the section: `COREKIT_CONCURRENCY__MAX_THREADS=16`.
|
|
353
382
|
|
|
354
|
-
Environment variables use a `COREKIT_` prefix (`
|
|
383
|
+
Environment variables use a `COREKIT_` prefix (`COREKIT_CRYPTO__SALT`). Empty
|
|
355
384
|
values are treated as unset, because container runtimes routinely pass `FOO=`
|
|
356
385
|
for a variable that was never set.
|
|
357
386
|
|
|
@@ -375,7 +404,6 @@ Imports go downward only.
|
|
|
375
404
|
```
|
|
376
405
|
corekit/
|
|
377
406
|
config/ settings, sources, loader
|
|
378
|
-
constants.py
|
|
379
407
|
|
|
380
408
|
exceptions/ error types
|
|
381
409
|
|
|
@@ -390,10 +418,11 @@ corekit/
|
|
|
390
418
|
connections/ the Connectable lifecycle and @connect
|
|
391
419
|
sql/ SQLConnection, statements, migrations
|
|
392
420
|
redis/ RedisConnection
|
|
393
|
-
http/ BaseApiClient, retries, responses
|
|
421
|
+
http/ BaseHttpClient, BaseApiClient, retries, responses
|
|
394
422
|
|
|
395
423
|
api/ Application, lifespan, middleware, routers
|
|
396
424
|
docker/ notifications/ etl/
|
|
425
|
+
llm/ messages, tools, agentic loop (no GenerationClient)
|
|
397
426
|
|
|
398
427
|
events/ log_monitor/ built on the capabilities above
|
|
399
428
|
```
|
|
@@ -2,7 +2,8 @@
|
|
|
2
2
|
|
|
3
3
|
Shared foundations for Python projects: structured logging, benchmarking,
|
|
4
4
|
registries, a FastAPI application with routers and handlers, SQL statements and
|
|
5
|
-
migrations, background tasks, an in-memory record store,
|
|
5
|
+
migrations, background tasks, an in-memory record store, ETL scaffolding, and
|
|
6
|
+
OpenAI-shaped chat / tool-loop primitives.
|
|
6
7
|
|
|
7
8
|
Requires Python 3.11+.
|
|
8
9
|
|
|
@@ -20,7 +21,7 @@ extras to remember, and no import that fails because something was left out.
|
|
|
20
21
|
Pin a compatible release rather than tracking whatever is newest:
|
|
21
22
|
|
|
22
23
|
```
|
|
23
|
-
python-corekit~=0.
|
|
24
|
+
python-corekit~=0.4.0
|
|
24
25
|
```
|
|
25
26
|
|
|
26
27
|
Before 1.0, the minor version carries breaking changes.
|
|
@@ -246,10 +247,36 @@ ceiling, not 9,999 threads.
|
|
|
246
247
|
Failures propagate by default. Pass `raise_on_error=False` to log and skip them
|
|
247
248
|
instead, which loses results silently and so is opt-in.
|
|
248
249
|
|
|
250
|
+
## LLM chat and tools
|
|
251
|
+
|
|
252
|
+
OpenAI-shaped messages, a tool registry, an agentic loop, and a thin
|
|
253
|
+
``ChatCompletionsClient`` (a ``BaseApiClient``) for any OpenAI-compatible
|
|
254
|
+
server — llama.cpp, vLLM, OpenAI, and so on. Not a multi-provider generation
|
|
255
|
+
facade.
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
from corekit.llm import ChatCompletionsClient, PromptLoader, ToolLoop, ToolRegistry
|
|
259
|
+
|
|
260
|
+
client = ChatCompletionsClient("http://localhost:8080/v1", model="local")
|
|
261
|
+
prompts = PromptLoader("/path/to/prompts") # mount any compatible tree
|
|
262
|
+
tmpl = prompts.load("summarization", "general", system="1.0.0", user="1.0.0")
|
|
263
|
+
|
|
264
|
+
registry = ToolRegistry()
|
|
265
|
+
registry.register(MyTool())
|
|
266
|
+
loop = ToolLoop(client, registry)
|
|
267
|
+
async for event in loop.stream(tmpl.messages(text="…", style="brief", length="short")):
|
|
268
|
+
...
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
``PromptLoader`` reads versioned ``.txt`` files from a directory you point at
|
|
272
|
+
(``{kind}/{name}/{role}-v{version}.txt``, plus ``common/`` fragments). Prompt
|
|
273
|
+
*text* stays with the consumer — corekit only ships the loader. See
|
|
274
|
+
`docs/LLM_EXTRACTION.md`.
|
|
275
|
+
|
|
249
276
|
## HTTP clients
|
|
250
277
|
|
|
251
278
|
```python
|
|
252
|
-
from corekit.http
|
|
279
|
+
from corekit.http import BaseApiClient
|
|
253
280
|
|
|
254
281
|
class GithubClient(BaseApiClient):
|
|
255
282
|
"""
|
|
@@ -266,7 +293,8 @@ response.data["login"]
|
|
|
266
293
|
|
|
267
294
|
Retries 429 and 5xx with exponential backoff. Every response is a
|
|
268
295
|
`BaseApiResponse`, so a non-JSON error page leaves `data` empty rather than
|
|
269
|
-
raising.
|
|
296
|
+
raising. The same methods are awaitable inside a running event loop:
|
|
297
|
+
`await client.get(...)`.
|
|
270
298
|
|
|
271
299
|
## Serialization
|
|
272
300
|
|
|
@@ -316,7 +344,7 @@ Settings are grouped by concern, so `get_settings().concurrency.max_threads`
|
|
|
316
344
|
says where a value belongs. Environment variables use a double underscore for
|
|
317
345
|
the section: `COREKIT_CONCURRENCY__MAX_THREADS=16`.
|
|
318
346
|
|
|
319
|
-
Environment variables use a `COREKIT_` prefix (`
|
|
347
|
+
Environment variables use a `COREKIT_` prefix (`COREKIT_CRYPTO__SALT`). Empty
|
|
320
348
|
values are treated as unset, because container runtimes routinely pass `FOO=`
|
|
321
349
|
for a variable that was never set.
|
|
322
350
|
|
|
@@ -340,7 +368,6 @@ Imports go downward only.
|
|
|
340
368
|
```
|
|
341
369
|
corekit/
|
|
342
370
|
config/ settings, sources, loader
|
|
343
|
-
constants.py
|
|
344
371
|
|
|
345
372
|
exceptions/ error types
|
|
346
373
|
|
|
@@ -355,10 +382,11 @@ corekit/
|
|
|
355
382
|
connections/ the Connectable lifecycle and @connect
|
|
356
383
|
sql/ SQLConnection, statements, migrations
|
|
357
384
|
redis/ RedisConnection
|
|
358
|
-
http/ BaseApiClient, retries, responses
|
|
385
|
+
http/ BaseHttpClient, BaseApiClient, retries, responses
|
|
359
386
|
|
|
360
387
|
api/ Application, lifespan, middleware, routers
|
|
361
388
|
docker/ notifications/ etl/
|
|
389
|
+
llm/ messages, tools, agentic loop (no GenerationClient)
|
|
362
390
|
|
|
363
391
|
events/ log_monitor/ built on the capabilities above
|
|
364
392
|
```
|
|
@@ -26,16 +26,34 @@ from typing import Any, Sequence
|
|
|
26
26
|
|
|
27
27
|
from fastapi import FastAPI
|
|
28
28
|
from fastapi.middleware.cors import CORSMiddleware
|
|
29
|
+
from fastapi.responses import JSONResponse
|
|
29
30
|
from fastapi.staticfiles import StaticFiles
|
|
30
31
|
from starlette.middleware.trustedhost import TrustedHostMiddleware
|
|
31
32
|
|
|
32
33
|
from corekit.api.middleware import MiddlewareStack
|
|
33
34
|
from corekit.api.routers import CatchAllRouter, SimpleRouter, router_registry
|
|
35
|
+
from corekit.exceptions import CoreHTTPException
|
|
34
36
|
from corekit.observability.loggable import Loggable
|
|
35
37
|
|
|
36
38
|
__all__ = ["Application"]
|
|
37
39
|
|
|
38
40
|
|
|
41
|
+
def _origin_in_package(router: SimpleRouter, package_name: str) -> bool:
|
|
42
|
+
"""
|
|
43
|
+
Whether this router was constructed by ``package_name`` or a submodule.
|
|
44
|
+
"""
|
|
45
|
+
origin = router._origin_module
|
|
46
|
+
return origin == package_name or origin.startswith(f"{package_name}.")
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
async def _core_http_exception_handler(request: Any, exc: CoreHTTPException) -> JSONResponse:
|
|
50
|
+
"""
|
|
51
|
+
The same body FastAPI returns for ``HTTPException``.
|
|
52
|
+
"""
|
|
53
|
+
headers = getattr(exc, "headers", None)
|
|
54
|
+
return JSONResponse(status_code=exc.status_code, content={"detail": exc.detail}, headers=headers)
|
|
55
|
+
|
|
56
|
+
|
|
39
57
|
class Application(FastAPI, Loggable):
|
|
40
58
|
"""
|
|
41
59
|
``FastAPI`` plus router discovery, middleware helpers and static mounting.
|
|
@@ -47,6 +65,7 @@ class Application(FastAPI, Loggable):
|
|
|
47
65
|
"""
|
|
48
66
|
FastAPI.__init__(self, *args, **kwargs)
|
|
49
67
|
Loggable.__init__(self)
|
|
68
|
+
self._register_http_exception_handler()
|
|
50
69
|
|
|
51
70
|
# ─── Routers ──────────────────────────────────────────────────────────
|
|
52
71
|
|
|
@@ -55,9 +74,12 @@ class Application(FastAPI, Loggable):
|
|
|
55
74
|
Import every module under ``package`` and mount the routers it defines.
|
|
56
75
|
|
|
57
76
|
Importing a module is what constructs its routers, so discovery is a
|
|
58
|
-
walk over ``package`` that lets each one register itself.
|
|
59
|
-
|
|
60
|
-
|
|
77
|
+
walk over ``package`` that lets each one register itself. Only routers
|
|
78
|
+
constructed inside that package are mounted: the registry is
|
|
79
|
+
process-wide, and an unclaimed router from some other import must not
|
|
80
|
+
ride along. A router that was included into another router is skipped
|
|
81
|
+
-- its parent already owns it -- and any ``CatchAllRouter`` is mounted
|
|
82
|
+
last.
|
|
61
83
|
|
|
62
84
|
Args:
|
|
63
85
|
package: The imported package to search, e.g. ``app.backend.routers``.
|
|
@@ -78,11 +100,12 @@ class Application(FastAPI, Loggable):
|
|
|
78
100
|
|
|
79
101
|
self._warn_on_unimportable_dirs(package)
|
|
80
102
|
|
|
81
|
-
before = len(router_registry)
|
|
82
103
|
scanned = self._import_submodules(package)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
104
|
+
# unclaimed is every top-level router in the process. Mounting that
|
|
105
|
+
# list would include routers built by some other package that happens
|
|
106
|
+
# to be imported. Origin is where the instance was constructed, which
|
|
107
|
+
# is the scanned package for a router that belongs here.
|
|
108
|
+
mountable = [router for router in router_registry.unclaimed if _origin_in_package(router, name)]
|
|
86
109
|
if not mountable:
|
|
87
110
|
raise ValueError(
|
|
88
111
|
f"No routers found in '{name}' (scanned {scanned} modules). "
|
|
@@ -90,8 +113,9 @@ class Application(FastAPI, Loggable):
|
|
|
90
113
|
f"a SimpleRouter or SmartRouter at import time."
|
|
91
114
|
)
|
|
92
115
|
|
|
93
|
-
|
|
94
|
-
|
|
116
|
+
in_package = [router for router in router_registry if _origin_in_package(router, name)]
|
|
117
|
+
claimed = len(in_package) - len(mountable)
|
|
118
|
+
self.info(f" scanned {scanned} modules, found {len(in_package)} routers")
|
|
95
119
|
if claimed > 0:
|
|
96
120
|
plural = "s" if claimed != 1 else ""
|
|
97
121
|
self.info(f" skipped {claimed} sub-router{plural} (already included by a parent)")
|
|
@@ -235,3 +259,17 @@ class Application(FastAPI, Loggable):
|
|
|
235
259
|
self.mount(url_path, StaticFiles(directory=str(path), **kwargs), name=name or url_path.strip("/"))
|
|
236
260
|
self.info(f" + static {url_path:<24} -> {path}")
|
|
237
261
|
return True
|
|
262
|
+
|
|
263
|
+
def _register_http_exception_handler(self) -> None:
|
|
264
|
+
"""
|
|
265
|
+
Handle the library's HTTP exceptions the same way FastAPI handles its own.
|
|
266
|
+
|
|
267
|
+
``CoreHTTPException`` is already an ``HTTPException``, so Starlette
|
|
268
|
+
would answer it. Registering the type explicitly keeps that response
|
|
269
|
+
-- ``{"detail": ...}`` only, never ``message`` or ``error`` -- even
|
|
270
|
+
if a later change in lookup order would otherwise miss the subclass.
|
|
271
|
+
A handler the caller already installed is left in place.
|
|
272
|
+
"""
|
|
273
|
+
if CoreHTTPException in self.exception_handlers:
|
|
274
|
+
return
|
|
275
|
+
self.add_exception_handler(CoreHTTPException, _core_http_exception_handler)
|
|
@@ -108,7 +108,9 @@ class Lifespan(Loggable):
|
|
|
108
108
|
startup: Called on the way up, in the order steps were added.
|
|
109
109
|
shutdown: Called on the way down, in reverse order. Called only if
|
|
110
110
|
``startup`` completed, so it can assume what ``startup`` builds.
|
|
111
|
-
tags:
|
|
111
|
+
tags: Labels used by ``for_tags``. Untagged steps always run; a
|
|
112
|
+
tagged step runs only when one of its tags is selected. With
|
|
113
|
+
no filter, every step runs.
|
|
112
114
|
always_shutdown: Call ``shutdown`` even when ``startup`` raised.
|
|
113
115
|
For a startup that acquires something before it can fail -- a
|
|
114
116
|
connection opened, then a handshake that throws. The hook then
|
|
@@ -136,6 +138,25 @@ class Lifespan(Loggable):
|
|
|
136
138
|
"""The registered steps, in the order they will start."""
|
|
137
139
|
return list(self._steps)
|
|
138
140
|
|
|
141
|
+
def for_tags(self, *tags: str) -> "Lifespan":
|
|
142
|
+
"""
|
|
143
|
+
A lifespan that runs the steps matching ``tags``, plus untagged ones.
|
|
144
|
+
|
|
145
|
+
An untagged step is ordinary startup and always runs. A tagged step
|
|
146
|
+
runs only when the caller asked for one of its tags, so a label is a
|
|
147
|
+
filter rather than decoration. No tags means every step, which is
|
|
148
|
+
what ``__call__`` does.
|
|
149
|
+
"""
|
|
150
|
+
if not tags:
|
|
151
|
+
return self
|
|
152
|
+
|
|
153
|
+
wanted = set(tags)
|
|
154
|
+
selected = Lifespan()
|
|
155
|
+
for step in self._steps:
|
|
156
|
+
if not step.tags or wanted.intersection(step.tags):
|
|
157
|
+
selected._steps.append(step)
|
|
158
|
+
return selected
|
|
159
|
+
|
|
139
160
|
@asynccontextmanager
|
|
140
161
|
async def __call__(self, app: Any = None) -> AsyncIterator[None]:
|
|
141
162
|
"""
|
|
@@ -166,7 +187,8 @@ class Lifespan(Loggable):
|
|
|
166
187
|
stack.push_async_callback(self._run_shutdown, step)
|
|
167
188
|
|
|
168
189
|
if step.startup is not None:
|
|
169
|
-
|
|
190
|
+
label = step.name if not step.tags else f"{step.name} [{', '.join(step.tags)}]"
|
|
191
|
+
self.info(f" ^ {label}")
|
|
170
192
|
try:
|
|
171
193
|
await self._maybe_await(step.startup)
|
|
172
194
|
except Exception:
|
|
@@ -192,7 +214,8 @@ class Lifespan(Loggable):
|
|
|
192
214
|
self.warning(f" - {step.name} (skipped: startup did not complete)")
|
|
193
215
|
return
|
|
194
216
|
|
|
195
|
-
|
|
217
|
+
label = step.name if not step.tags else f"{step.name} [{', '.join(step.tags)}]"
|
|
218
|
+
self.info(f" v {label}")
|
|
196
219
|
try:
|
|
197
220
|
await self._maybe_await(step.shutdown)
|
|
198
221
|
except Exception:
|
|
@@ -2,8 +2,8 @@
|
|
|
2
2
|
Concurrency primitives: per-thread storage, workers, and parallel mapping.
|
|
3
3
|
"""
|
|
4
4
|
|
|
5
|
-
from corekit.concurrency.decorators import parallelize
|
|
5
|
+
from corekit.concurrency.decorators import allow_sync, parallelize
|
|
6
6
|
from corekit.concurrency.thread_local import ThreadLocalRegistry
|
|
7
7
|
from corekit.concurrency.worker import ThreadWorker
|
|
8
8
|
|
|
9
|
-
__all__ = ["ThreadLocalRegistry", "ThreadWorker", "parallelize"]
|
|
9
|
+
__all__ = ["ThreadLocalRegistry", "ThreadWorker", "allow_sync", "parallelize"]
|
|
@@ -18,19 +18,26 @@ machine rather than an error message.
|
|
|
18
18
|
|
|
19
19
|
Results arrive out of order, because they arrive as they complete. Pass
|
|
20
20
|
``ordered=True`` to get them in the order the inputs were given.
|
|
21
|
+
|
|
22
|
+
``allow_sync`` wraps an async function so a script can call it directly, while
|
|
23
|
+
an already-running event loop still gets a coroutine to await.
|
|
21
24
|
"""
|
|
22
25
|
|
|
26
|
+
import asyncio
|
|
23
27
|
import functools
|
|
28
|
+
import logging
|
|
29
|
+
from collections.abc import Awaitable, Callable
|
|
24
30
|
from concurrent.futures import Future, ThreadPoolExecutor, as_completed
|
|
25
|
-
from typing import Any,
|
|
31
|
+
from typing import Any, Iterable, Iterator, ParamSpec, TypeVar
|
|
26
32
|
|
|
27
33
|
from corekit.config import get_settings
|
|
28
|
-
from corekit.observability import Loggable
|
|
29
34
|
|
|
30
|
-
__all__ = ["parallelize"]
|
|
35
|
+
__all__ = ["parallelize", "allow_sync"]
|
|
36
|
+
|
|
37
|
+
_logger = logging.getLogger("parallelize")
|
|
31
38
|
|
|
32
|
-
|
|
33
|
-
|
|
39
|
+
P = ParamSpec("P")
|
|
40
|
+
R = TypeVar("R")
|
|
34
41
|
|
|
35
42
|
|
|
36
43
|
def parallelize(
|
|
@@ -70,3 +77,23 @@ def parallelize(
|
|
|
70
77
|
return wrapper
|
|
71
78
|
|
|
72
79
|
return decorator
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
def allow_sync(async_func: Callable[P, Awaitable[R]]) -> Callable[P, R | Awaitable[R]]:
|
|
83
|
+
"""
|
|
84
|
+
Call an async function from sync code, or return its coroutine if a loop is running.
|
|
85
|
+
|
|
86
|
+
A script gets the result back immediately. Inside an async app the wrapper
|
|
87
|
+
returns the coroutine, so the caller must await it — calling the wrapper
|
|
88
|
+
and ignoring the result will not run the function.
|
|
89
|
+
"""
|
|
90
|
+
|
|
91
|
+
@functools.wraps(async_func)
|
|
92
|
+
def wrapper(*args: P.args, **kwargs: P.kwargs) -> R | Awaitable[R]:
|
|
93
|
+
try:
|
|
94
|
+
asyncio.get_running_loop()
|
|
95
|
+
except RuntimeError:
|
|
96
|
+
return asyncio.run(async_func(*args, **kwargs))
|
|
97
|
+
return async_func(*args, **kwargs)
|
|
98
|
+
|
|
99
|
+
return wrapper
|
|
@@ -19,7 +19,7 @@ import threading
|
|
|
19
19
|
from typing import Any
|
|
20
20
|
|
|
21
21
|
from corekit.observability.loggable import Loggable
|
|
22
|
-
from corekit.registry import
|
|
22
|
+
from corekit.registry import normalize_key
|
|
23
23
|
|
|
24
24
|
__all__ = ["ThreadLocalRegistry"]
|
|
25
25
|
|
|
@@ -59,7 +59,7 @@ class ThreadLocalRegistry(threading.local, Loggable):
|
|
|
59
59
|
turn SQLConnection into "sqlconnection" rather than "sql_connection".
|
|
60
60
|
"""
|
|
61
61
|
name = key.__name__ if isinstance(key, type) else str(key)
|
|
62
|
-
return
|
|
62
|
+
return normalize_key(name).replace("-", "_")
|
|
63
63
|
|
|
64
64
|
def get(self, key: Any, fallback: Any = None) -> Any:
|
|
65
65
|
"""
|
|
@@ -5,6 +5,15 @@ import time
|
|
|
5
5
|
|
|
6
6
|
|
|
7
7
|
class ThreadWorker(threading.Thread):
|
|
8
|
+
"""
|
|
9
|
+
A daemon thread that waits for ``can_start``, then calls ``process`` once.
|
|
10
|
+
|
|
11
|
+
``timeout`` and ``loop`` are stored for a subclass that wants them.
|
|
12
|
+
``run`` does not enforce ``timeout`` and does not drive ``loop``.
|
|
13
|
+
``stop`` sets ``running`` so a subclass of ``process`` can notice it; it
|
|
14
|
+
does not interrupt a thread already inside ``process``.
|
|
15
|
+
"""
|
|
16
|
+
|
|
8
17
|
def __init__(
|
|
9
18
|
self,
|
|
10
19
|
can_start: threading.Event,
|
|
@@ -9,6 +9,8 @@ settings know only their own shape.
|
|
|
9
9
|
from pathlib import Path
|
|
10
10
|
from typing import Any
|
|
11
11
|
|
|
12
|
+
from pydantic import ValidationError
|
|
13
|
+
|
|
12
14
|
from corekit.config.settings import ENV_PREFIX, CorekitSettings
|
|
13
15
|
from corekit.config.sources import ConfigFileSource, ConfigSource, EnvironmentSource, PyprojectSource
|
|
14
16
|
from corekit.observability import Loggable
|
|
@@ -72,11 +74,12 @@ class SettingsLoader(Loggable):
|
|
|
72
74
|
"""
|
|
73
75
|
Read every source and construct the settings.
|
|
74
76
|
|
|
75
|
-
Invalid configuration
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
77
|
+
Invalid configuration drops the bad section and keeps the rest.
|
|
78
|
+
A bad thread count must not discard a valid salt or serialization
|
|
79
|
+
key that arrived from another section or the environment. If what
|
|
80
|
+
remains still will not validate, the last resort is defaults --
|
|
81
|
+
corekit has to remain importable. The log line is the difference
|
|
82
|
+
between this and silently ignoring it.
|
|
80
83
|
"""
|
|
81
84
|
values: dict[str, Any] = {}
|
|
82
85
|
for source in self.sources():
|
|
@@ -84,10 +87,44 @@ class SettingsLoader(Loggable):
|
|
|
84
87
|
|
|
85
88
|
try:
|
|
86
89
|
return CorekitSettings(**values)
|
|
90
|
+
except ValidationError as exc:
|
|
91
|
+
cleaned, dropped = self._without_invalid_sections(values, exc)
|
|
92
|
+
if dropped:
|
|
93
|
+
self.error(
|
|
94
|
+
f"Invalid corekit configuration in {', '.join(dropped)}; "
|
|
95
|
+
f"those sections fall back to defaults: {exc}"
|
|
96
|
+
)
|
|
97
|
+
try:
|
|
98
|
+
return CorekitSettings(**cleaned)
|
|
99
|
+
except ValidationError as retry_exc:
|
|
100
|
+
self.error(f"Invalid corekit configuration, falling back to defaults: {retry_exc}")
|
|
101
|
+
return CorekitSettings()
|
|
102
|
+
self.error(f"Invalid corekit configuration, falling back to defaults: {exc}")
|
|
103
|
+
return CorekitSettings()
|
|
87
104
|
except Exception as exc:
|
|
88
105
|
self.error(f"Invalid corekit configuration, falling back to defaults: {exc}")
|
|
89
106
|
return CorekitSettings()
|
|
90
107
|
|
|
108
|
+
@staticmethod
|
|
109
|
+
def _without_invalid_sections(values: dict[str, Any], exc: ValidationError) -> tuple[dict[str, Any], list[str]]:
|
|
110
|
+
"""
|
|
111
|
+
Drop each section named in a validation error, leaving the others.
|
|
112
|
+
|
|
113
|
+
A section is the first element of the error location. A failure that
|
|
114
|
+
names no section cannot be isolated, so nothing is dropped and the
|
|
115
|
+
caller falls back to defaults.
|
|
116
|
+
"""
|
|
117
|
+
dropped: list[str] = []
|
|
118
|
+
for error in exc.errors():
|
|
119
|
+
loc = error.get("loc") or ()
|
|
120
|
+
if not loc:
|
|
121
|
+
continue
|
|
122
|
+
section = str(loc[0])
|
|
123
|
+
if section not in dropped:
|
|
124
|
+
dropped.append(section)
|
|
125
|
+
cleaned = {key: value for key, value in values.items() if key not in dropped}
|
|
126
|
+
return cleaned, dropped
|
|
127
|
+
|
|
91
128
|
@staticmethod
|
|
92
129
|
def _merge(base: dict[str, Any], incoming: dict[str, Any]) -> dict[str, Any]:
|
|
93
130
|
"""
|
|
@@ -35,6 +35,10 @@ class StandardsSettings(BaseModel):
|
|
|
35
35
|
|
|
36
36
|
Off by default. These are conventions, not library invariants, and crashing
|
|
37
37
|
a consumer's application over a missing docstring would be hostile.
|
|
38
|
+
|
|
39
|
+
``strict_mode`` makes a ``SmartRegistry`` refuse a second write to a
|
|
40
|
+
normalized key instead of warning and replacing. It does not imply
|
|
41
|
+
``require_handler_docstrings``; that check stays its own flag.
|
|
38
42
|
"""
|
|
39
43
|
|
|
40
44
|
require_handler_docstrings: bool = False
|
|
@@ -80,7 +84,13 @@ class ConcurrencySettings(BaseModel):
|
|
|
80
84
|
@staticmethod
|
|
81
85
|
def cpu_default() -> int:
|
|
82
86
|
"""
|
|
83
|
-
A
|
|
87
|
+
A machine-sized thread count, for a caller that asks for one.
|
|
88
|
+
|
|
89
|
+
Not what ``resolve`` or ``parallelize`` use when the caller is silent.
|
|
90
|
+
Those use ``default_threads``. Pass this in when the work should
|
|
91
|
+
scale with the machine::
|
|
92
|
+
|
|
93
|
+
parallelize(num_threads=ConcurrencySettings.cpu_default())
|
|
84
94
|
"""
|
|
85
95
|
return min(32, (os.cpu_count() or 1) * 5)
|
|
86
96
|
|
|
@@ -17,7 +17,12 @@ implementations of the abstraction directly above them, and there is no useful
|
|
|
17
17
|
way to think about one without the other.
|
|
18
18
|
"""
|
|
19
19
|
|
|
20
|
-
from corekit.connections.connectable import
|
|
20
|
+
from corekit.connections.connectable import (
|
|
21
|
+
Connectable,
|
|
22
|
+
ConnectableType,
|
|
23
|
+
ConnectionPreference,
|
|
24
|
+
ReadOnlyConnectionError,
|
|
25
|
+
)
|
|
21
26
|
from corekit.connections.decorators import connect
|
|
22
27
|
from corekit.connections.registry import ConnectionRegistry, registry
|
|
23
28
|
|
|
@@ -26,6 +31,7 @@ __all__ = [
|
|
|
26
31
|
"ConnectableType",
|
|
27
32
|
"ConnectionPreference",
|
|
28
33
|
"ConnectionRegistry",
|
|
34
|
+
"ReadOnlyConnectionError",
|
|
29
35
|
"connect",
|
|
30
36
|
"registry",
|
|
31
37
|
]
|