pycraftcore 0.2.0__tar.gz → 0.2.2__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {pycraftcore-0.2.0/src/pycraftcore.egg-info → pycraftcore-0.2.2}/PKG-INFO +72 -40
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/Readme.md +71 -39
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/pyproject.toml +1 -1
- pycraftcore-0.2.2/src/pycraftcore/application_configuration/__init__.py +17 -0
- pycraftcore-0.2.2/src/pycraftcore/application_configuration/adapter/schema.py +65 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/__init__.py +2 -0
- pycraftcore-0.2.2/src/pycraftcore/application_configuration/enum/operation_type.py +6 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/model/configuration.py +4 -5
- pycraftcore-0.2.2/src/pycraftcore/application_configuration/model/connector.py +104 -0
- pycraftcore-0.2.2/src/pycraftcore/application_configuration/model/operation.py +81 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2/src/pycraftcore.egg-info}/PKG-INFO +72 -40
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore.egg-info/SOURCES.txt +1 -0
- pycraftcore-0.2.0/src/pycraftcore/application_configuration/__init__.py +0 -9
- pycraftcore-0.2.0/src/pycraftcore/application_configuration/adapter/schema.py +0 -29
- pycraftcore-0.2.0/src/pycraftcore/application_configuration/model/connector.py +0 -42
- pycraftcore-0.2.0/src/pycraftcore/application_configuration/model/operation.py +0 -30
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/LICENSE +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/setup.cfg +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/adapter/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/adapter/load_application_configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/adapter/omega_configuration_reader.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/connector_type.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/file_operation_action.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/run_type_application.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/run_type_environment.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/model/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/port/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/port/configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/port/configuration_reader.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/auth_type.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/basic_auth.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/no_auth.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/token_auth.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/compute_engine.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/numpy_arithmetic_operation.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/scipy_calculus_operation.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/const_typing.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/arithmetic_operation.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/calculus_operation.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/engine.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/extension/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/extension/yml_reader.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/extension/yml_writer.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/factory.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/handler.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/provider.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/strategy.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/port/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/port/reader.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/port/writer.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/aiohttp_client.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/circuit_breaker_policy.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/httpx_client.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/resilient_client.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/retry_policy.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/circuite_breaker_configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/client_configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/http_client_configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/limits_configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/retry_configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/security_configuration.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/context/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/context/request_context.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/enum/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/enum/circuit_breaker_status.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/enum/http_method.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/exception/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/exception/circuit_breaker_open_exception.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/middleware/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/middleware/request_id_middleware.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/middleware/request_middleware.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/async_circuit_breaker.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/async_http_client.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/async_resilient_http_client.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/retry.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/adapter/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/adapter/loguru_logger.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/port/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/port/logger.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/profiler/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/profiler/profiled.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/constants/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/constants/allowed_root_statements.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/constants/forbidden_statements.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/adapter.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/factory.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/sql_handler.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/repository.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/adapter.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/factory.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/mapper.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/schema.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/code.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/adapter.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/factory.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/schema.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/binary_serializer.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/dictionary_serializer.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/json_serializer.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/port/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/port/serializer.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/adapter/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/adapter/open_telemetry.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/port/__init__.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/port/telemetry.py +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore.egg-info/dependency_links.txt +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore.egg-info/requires.txt +0 -0
- {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore.egg-info/top_level.txt +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: pycraftcore
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Provides reusable technical infrastructure and adapter implementations for Python applications
|
|
5
5
|
Author-email: Toufik Saouchi <toufik.saouchi@gmail.com>
|
|
6
6
|
License-Expression: MIT
|
|
@@ -37,43 +37,39 @@ Dynamic: license-file
|
|
|
37
37
|
|
|
38
38
|
# pycraftcore
|
|
39
39
|
|
|
40
|
-
`pycraftcore` is a reusable technical infrastructure library for Python applications: configuration
|
|
41
|
-
clients
|
|
42
|
-
|
|
43
|
-
than a framework.
|
|
40
|
+
`pycraftcore` is a reusable technical infrastructure library for Python applications: configuration, resilient HTTP
|
|
41
|
+
clients, a pooled async SQLite repository, a sandboxed Python code runner, safe SQL validation, serialization, and a
|
|
42
|
+
handful of other adapters — small, swappable building blocks rather than a framework.
|
|
44
43
|
|
|
45
44
|
**Design principles:** Modularity · Reusability · Maintainability
|
|
46
45
|
|
|
47
|
-
Every module follows the same shape: a `port/` package with `Protocol` interfaces and an `adapter/` package with one
|
|
48
|
-
or more concrete implementations, so consumers depend on the port, not the adapter.
|
|
49
|
-
|
|
50
46
|
---
|
|
51
47
|
|
|
52
48
|
## Table of Contents
|
|
53
49
|
|
|
54
|
-
- [
|
|
50
|
+
- [Functionality](#functionality)
|
|
55
51
|
- [Installation](#installation)
|
|
56
52
|
- [Configuration](#configuration)
|
|
57
53
|
- [Development](#development)
|
|
58
54
|
|
|
59
55
|
---
|
|
60
56
|
|
|
61
|
-
##
|
|
57
|
+
## Functionality
|
|
62
58
|
|
|
63
|
-
| Module | Package |
|
|
59
|
+
| Module | Package | What it gives you |
|
|
64
60
|
|---|---|---|
|
|
65
|
-
| Configuration Management | `application_configuration` | Environment-driven YAML
|
|
66
|
-
| Authentication | `application_configuration` |
|
|
67
|
-
| Logging | `logger` |
|
|
68
|
-
| Telemetry & Observability | `telemetry` |
|
|
69
|
-
| HTTP Client Utilities | `http` |
|
|
70
|
-
| Repository / SQLite | `repository` | Async SQLite repository
|
|
71
|
-
| Query Language / SQL Safety | `query_language` |
|
|
72
|
-
| Runtime / Sandboxed Execution | `runtime` | Runs
|
|
73
|
-
| Serialization | `serialization` | JSON, dict, and binary (msgpack) serialization for dataclasses
|
|
74
|
-
| Scientific computation engine | `computation` |
|
|
75
|
-
| File handler | `file_handler` |
|
|
76
|
-
| Profiler | `profiler` |
|
|
61
|
+
| Configuration Management | `application_configuration` | Environment-driven YAML configuration for connectors and operations, validated and typed |
|
|
62
|
+
| Authentication | `application_configuration` | Shared auth models (none / basic / token) for any connector |
|
|
63
|
+
| Logging | `logger` | Structured application logging |
|
|
64
|
+
| Telemetry & Observability | `telemetry` | Distributed tracing, exportable to console or an OTLP collector |
|
|
65
|
+
| HTTP Client Utilities | `http` | Resilient HTTP client with retries, circuit breaking, and tracing |
|
|
66
|
+
| Repository / SQLite | `repository` | Async SQLite repository with real connection pooling for concurrent access |
|
|
67
|
+
| Query Language / SQL Safety | `query_language` | Validates that a SQL statement is read-only before it reaches a database |
|
|
68
|
+
| Runtime / Sandboxed Execution | `runtime` | Runs untrusted Python code in an isolated process with resource limits |
|
|
69
|
+
| Serialization | `serialization` | JSON, dict, and binary (msgpack) serialization for dataclasses |
|
|
70
|
+
| Scientific computation engine | `computation` | Financial arithmetic and calculus (integration, interpolation) |
|
|
71
|
+
| File handler | `file_handler` | Read/write files by extension, pluggable for new formats |
|
|
72
|
+
| Profiler | `profiler` | Async function profiling decorator |
|
|
77
73
|
|
|
78
74
|
---
|
|
79
75
|
|
|
@@ -134,32 +130,68 @@ connector:
|
|
|
134
130
|
key_value: ${oc.env:connector_api_key}
|
|
135
131
|
```
|
|
136
132
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
133
|
+
**Operation example** (`operation/<tag>.yml`) — references a connector by tag:
|
|
134
|
+
|
|
135
|
+
```yaml
|
|
136
|
+
operation:
|
|
137
|
+
<operation_tag>:
|
|
138
|
+
name: <operation name>
|
|
139
|
+
type: api
|
|
140
|
+
connector: ${connector.<connector_tag>}
|
|
141
|
+
endpoint: /<path>
|
|
142
|
+
method: GET
|
|
143
|
+
parameters:
|
|
144
|
+
<param>: <value>
|
|
145
|
+
```
|
|
146
|
+
|
|
147
|
+
**Root example** (`root.yml`) — wires everything together for a given environment:
|
|
140
148
|
|
|
141
|
-
|
|
149
|
+
```yaml
|
|
150
|
+
application_configuration:
|
|
151
|
+
env: ${oc.env:APP_ENV,debug}
|
|
152
|
+
run: async
|
|
153
|
+
|
|
154
|
+
connector:
|
|
155
|
+
api:
|
|
156
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
157
|
+
file:
|
|
158
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
159
|
+
database:
|
|
160
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
161
|
+
telemetry:
|
|
162
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
163
|
+
|
|
164
|
+
operation:
|
|
165
|
+
<operation_tag>: ${operation.<operation_tag>}
|
|
166
|
+
# ...
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Connectors and operations both declare a `type`, which determines which fields they take and which concrete object
|
|
170
|
+
they load as.
|
|
171
|
+
|
|
172
|
+
**Using it in code:**
|
|
142
173
|
|
|
143
174
|
```python
|
|
144
175
|
from pathlib import Path
|
|
145
176
|
|
|
146
|
-
from pycraftcore.application_configuration.adapter.load_application_configuration import
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
from pycraftcore.application_configuration.adapter.omega_configuration_reader import (
|
|
150
|
-
OmegaConfigurationReader,
|
|
151
|
-
)
|
|
152
|
-
from pycraftcore.application_configuration.enum.run_type_environment import (
|
|
153
|
-
RunTypeEnvironment,
|
|
154
|
-
)
|
|
177
|
+
from pycraftcore.application_configuration.adapter.load_application_configuration import LoadApplicationConfiguration
|
|
178
|
+
from pycraftcore.application_configuration.adapter.omega_configuration_reader import OmegaConfigurationReader
|
|
179
|
+
from pycraftcore.application_configuration.enum.run_type_environment import RunTypeEnvironment
|
|
155
180
|
from pycraftcore.logger.adapter.loguru_logger import LoguruLogger
|
|
156
181
|
|
|
157
182
|
reader = OmegaConfigurationReader(RunTypeEnvironment.deploy, Path("config"))
|
|
158
183
|
loader = LoadApplicationConfiguration(reader, LoguruLogger())
|
|
159
184
|
|
|
160
|
-
config = loader.load()
|
|
185
|
+
config = loader.load()
|
|
186
|
+
|
|
187
|
+
connector = config.connector.api("<connector_tag>") # ApiConnector
|
|
188
|
+
operation = config.operation.api("<operation_tag>") # ApiOperation
|
|
161
189
|
```
|
|
162
190
|
|
|
191
|
+
`config.connector`/`config.operation` return the concrete connector/operation type for a given tag (`.api()`,
|
|
192
|
+
`.database()`, `.file()`, `.telemetry()` where applicable) instead of a raw dict — see
|
|
193
|
+
`pycraftcore.application_configuration` for the full API.
|
|
194
|
+
|
|
163
195
|
---
|
|
164
196
|
|
|
165
197
|
## Development
|
|
@@ -186,9 +218,9 @@ uv run pre-commit run --all-files --hook-stage pre-commit
|
|
|
186
218
|
```bash
|
|
187
219
|
make install_dev # uv sync --group dev
|
|
188
220
|
make test # uv run pytest
|
|
189
|
-
make lint # uv run ruff check
|
|
190
|
-
make format # uv run ruff format
|
|
191
|
-
make typecheck
|
|
221
|
+
make lint # uv run ruff check
|
|
222
|
+
make format # uv run ruff format
|
|
223
|
+
make typecheck # uv run ty check (strict mode)
|
|
192
224
|
```
|
|
193
225
|
|
|
194
226
|
Run `pytest --cov=pycraftcore --cov-report=term-missing` for a coverage breakdown by module.
|
|
@@ -1,42 +1,38 @@
|
|
|
1
1
|
# pycraftcore
|
|
2
2
|
|
|
3
|
-
`pycraftcore` is a reusable technical infrastructure library for Python applications: configuration
|
|
4
|
-
clients
|
|
5
|
-
|
|
6
|
-
than a framework.
|
|
3
|
+
`pycraftcore` is a reusable technical infrastructure library for Python applications: configuration, resilient HTTP
|
|
4
|
+
clients, a pooled async SQLite repository, a sandboxed Python code runner, safe SQL validation, serialization, and a
|
|
5
|
+
handful of other adapters — small, swappable building blocks rather than a framework.
|
|
7
6
|
|
|
8
7
|
**Design principles:** Modularity · Reusability · Maintainability
|
|
9
8
|
|
|
10
|
-
Every module follows the same shape: a `port/` package with `Protocol` interfaces and an `adapter/` package with one
|
|
11
|
-
or more concrete implementations, so consumers depend on the port, not the adapter.
|
|
12
|
-
|
|
13
9
|
---
|
|
14
10
|
|
|
15
11
|
## Table of Contents
|
|
16
12
|
|
|
17
|
-
- [
|
|
13
|
+
- [Functionality](#functionality)
|
|
18
14
|
- [Installation](#installation)
|
|
19
15
|
- [Configuration](#configuration)
|
|
20
16
|
- [Development](#development)
|
|
21
17
|
|
|
22
18
|
---
|
|
23
19
|
|
|
24
|
-
##
|
|
20
|
+
## Functionality
|
|
25
21
|
|
|
26
|
-
| Module | Package |
|
|
22
|
+
| Module | Package | What it gives you |
|
|
27
23
|
|---|---|---|
|
|
28
|
-
| Configuration Management | `application_configuration` | Environment-driven YAML
|
|
29
|
-
| Authentication | `application_configuration` |
|
|
30
|
-
| Logging | `logger` |
|
|
31
|
-
| Telemetry & Observability | `telemetry` |
|
|
32
|
-
| HTTP Client Utilities | `http` |
|
|
33
|
-
| Repository / SQLite | `repository` | Async SQLite repository
|
|
34
|
-
| Query Language / SQL Safety | `query_language` |
|
|
35
|
-
| Runtime / Sandboxed Execution | `runtime` | Runs
|
|
36
|
-
| Serialization | `serialization` | JSON, dict, and binary (msgpack) serialization for dataclasses
|
|
37
|
-
| Scientific computation engine | `computation` |
|
|
38
|
-
| File handler | `file_handler` |
|
|
39
|
-
| Profiler | `profiler` |
|
|
24
|
+
| Configuration Management | `application_configuration` | Environment-driven YAML configuration for connectors and operations, validated and typed |
|
|
25
|
+
| Authentication | `application_configuration` | Shared auth models (none / basic / token) for any connector |
|
|
26
|
+
| Logging | `logger` | Structured application logging |
|
|
27
|
+
| Telemetry & Observability | `telemetry` | Distributed tracing, exportable to console or an OTLP collector |
|
|
28
|
+
| HTTP Client Utilities | `http` | Resilient HTTP client with retries, circuit breaking, and tracing |
|
|
29
|
+
| Repository / SQLite | `repository` | Async SQLite repository with real connection pooling for concurrent access |
|
|
30
|
+
| Query Language / SQL Safety | `query_language` | Validates that a SQL statement is read-only before it reaches a database |
|
|
31
|
+
| Runtime / Sandboxed Execution | `runtime` | Runs untrusted Python code in an isolated process with resource limits |
|
|
32
|
+
| Serialization | `serialization` | JSON, dict, and binary (msgpack) serialization for dataclasses |
|
|
33
|
+
| Scientific computation engine | `computation` | Financial arithmetic and calculus (integration, interpolation) |
|
|
34
|
+
| File handler | `file_handler` | Read/write files by extension, pluggable for new formats |
|
|
35
|
+
| Profiler | `profiler` | Async function profiling decorator |
|
|
40
36
|
|
|
41
37
|
---
|
|
42
38
|
|
|
@@ -97,32 +93,68 @@ connector:
|
|
|
97
93
|
key_value: ${oc.env:connector_api_key}
|
|
98
94
|
```
|
|
99
95
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
96
|
+
**Operation example** (`operation/<tag>.yml`) — references a connector by tag:
|
|
97
|
+
|
|
98
|
+
```yaml
|
|
99
|
+
operation:
|
|
100
|
+
<operation_tag>:
|
|
101
|
+
name: <operation name>
|
|
102
|
+
type: api
|
|
103
|
+
connector: ${connector.<connector_tag>}
|
|
104
|
+
endpoint: /<path>
|
|
105
|
+
method: GET
|
|
106
|
+
parameters:
|
|
107
|
+
<param>: <value>
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Root example** (`root.yml`) — wires everything together for a given environment:
|
|
103
111
|
|
|
104
|
-
|
|
112
|
+
```yaml
|
|
113
|
+
application_configuration:
|
|
114
|
+
env: ${oc.env:APP_ENV,debug}
|
|
115
|
+
run: async
|
|
116
|
+
|
|
117
|
+
connector:
|
|
118
|
+
api:
|
|
119
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
120
|
+
file:
|
|
121
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
122
|
+
database:
|
|
123
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
124
|
+
telemetry:
|
|
125
|
+
<connector_tag>: ${connector.<connector_tag>}
|
|
126
|
+
|
|
127
|
+
operation:
|
|
128
|
+
<operation_tag>: ${operation.<operation_tag>}
|
|
129
|
+
# ...
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
Connectors and operations both declare a `type`, which determines which fields they take and which concrete object
|
|
133
|
+
they load as.
|
|
134
|
+
|
|
135
|
+
**Using it in code:**
|
|
105
136
|
|
|
106
137
|
```python
|
|
107
138
|
from pathlib import Path
|
|
108
139
|
|
|
109
|
-
from pycraftcore.application_configuration.adapter.load_application_configuration import
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
from pycraftcore.application_configuration.adapter.omega_configuration_reader import (
|
|
113
|
-
OmegaConfigurationReader,
|
|
114
|
-
)
|
|
115
|
-
from pycraftcore.application_configuration.enum.run_type_environment import (
|
|
116
|
-
RunTypeEnvironment,
|
|
117
|
-
)
|
|
140
|
+
from pycraftcore.application_configuration.adapter.load_application_configuration import LoadApplicationConfiguration
|
|
141
|
+
from pycraftcore.application_configuration.adapter.omega_configuration_reader import OmegaConfigurationReader
|
|
142
|
+
from pycraftcore.application_configuration.enum.run_type_environment import RunTypeEnvironment
|
|
118
143
|
from pycraftcore.logger.adapter.loguru_logger import LoguruLogger
|
|
119
144
|
|
|
120
145
|
reader = OmegaConfigurationReader(RunTypeEnvironment.deploy, Path("config"))
|
|
121
146
|
loader = LoadApplicationConfiguration(reader, LoguruLogger())
|
|
122
147
|
|
|
123
|
-
config = loader.load()
|
|
148
|
+
config = loader.load()
|
|
149
|
+
|
|
150
|
+
connector = config.connector.api("<connector_tag>") # ApiConnector
|
|
151
|
+
operation = config.operation.api("<operation_tag>") # ApiOperation
|
|
124
152
|
```
|
|
125
153
|
|
|
154
|
+
`config.connector`/`config.operation` return the concrete connector/operation type for a given tag (`.api()`,
|
|
155
|
+
`.database()`, `.file()`, `.telemetry()` where applicable) instead of a raw dict — see
|
|
156
|
+
`pycraftcore.application_configuration` for the full API.
|
|
157
|
+
|
|
126
158
|
---
|
|
127
159
|
|
|
128
160
|
## Development
|
|
@@ -149,9 +181,9 @@ uv run pre-commit run --all-files --hook-stage pre-commit
|
|
|
149
181
|
```bash
|
|
150
182
|
make install_dev # uv sync --group dev
|
|
151
183
|
make test # uv run pytest
|
|
152
|
-
make lint # uv run ruff check
|
|
153
|
-
make format # uv run ruff format
|
|
154
|
-
make typecheck
|
|
184
|
+
make lint # uv run ruff check
|
|
185
|
+
make format # uv run ruff format
|
|
186
|
+
make typecheck # uv run ty check (strict mode)
|
|
155
187
|
```
|
|
156
188
|
|
|
157
189
|
Run `pytest --cov=pycraftcore --cov-report=term-missing` for a coverage breakdown by module.
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
from pycraftcore.application_configuration.model.configuration import (
|
|
2
|
+
ApplicationConfiguration,
|
|
3
|
+
)
|
|
4
|
+
from pycraftcore.application_configuration.model.connector import ConnectorRegistry
|
|
5
|
+
from pycraftcore.application_configuration.model.operation import OperationRegistry
|
|
6
|
+
from pycraftcore.application_configuration.port.configuration import Configuration
|
|
7
|
+
from pycraftcore.application_configuration.port.configuration_reader import (
|
|
8
|
+
ConfigurationReader,
|
|
9
|
+
)
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"ApplicationConfiguration",
|
|
13
|
+
"Configuration",
|
|
14
|
+
"ConfigurationReader",
|
|
15
|
+
"ConnectorRegistry",
|
|
16
|
+
"OperationRegistry",
|
|
17
|
+
]
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
from typing import Annotated, Any
|
|
2
|
+
|
|
3
|
+
from pydantic import BaseModel, Discriminator, Tag
|
|
4
|
+
|
|
5
|
+
from pycraftcore.application_configuration.enum.connector_type import ConnectorType
|
|
6
|
+
from pycraftcore.application_configuration.enum.run_type_application import (
|
|
7
|
+
RunTypeApplication,
|
|
8
|
+
)
|
|
9
|
+
from pycraftcore.application_configuration.enum.run_type_environment import (
|
|
10
|
+
RunTypeEnvironment,
|
|
11
|
+
)
|
|
12
|
+
from pycraftcore.application_configuration.model.configuration import (
|
|
13
|
+
ApplicationConfiguration,
|
|
14
|
+
)
|
|
15
|
+
from pycraftcore.application_configuration.model.connector import (
|
|
16
|
+
ApiConnector,
|
|
17
|
+
ConnectorRegistry,
|
|
18
|
+
DatabaseConnector,
|
|
19
|
+
FileConnector,
|
|
20
|
+
TelemetryConnector,
|
|
21
|
+
)
|
|
22
|
+
from pycraftcore.application_configuration.model.operation import (
|
|
23
|
+
ApiOperation,
|
|
24
|
+
FileOperation,
|
|
25
|
+
OperationRegistry,
|
|
26
|
+
)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def _kind_tag(value: Any) -> str:
|
|
30
|
+
"""Discriminator: read `type` off a raw dict (pre-coercion) or an already-built dataclass."""
|
|
31
|
+
return value["type"] if isinstance(value, dict) else value.type.value
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
_ConnectorSchemaTyping = Annotated[
|
|
35
|
+
Annotated[ApiConnector, Tag("api")]
|
|
36
|
+
| Annotated[DatabaseConnector, Tag("database")]
|
|
37
|
+
| Annotated[FileConnector, Tag("file")]
|
|
38
|
+
| Annotated[TelemetryConnector, Tag("telemetry")],
|
|
39
|
+
Discriminator(_kind_tag),
|
|
40
|
+
]
|
|
41
|
+
|
|
42
|
+
_OperationSchemaTyping = Annotated[
|
|
43
|
+
Annotated[ApiOperation, Tag("api")] | Annotated[FileOperation, Tag("file")],
|
|
44
|
+
Discriminator(_kind_tag),
|
|
45
|
+
]
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
class ApplicationConfigurationSchema(BaseModel):
|
|
49
|
+
env: RunTypeEnvironment
|
|
50
|
+
run: RunTypeApplication
|
|
51
|
+
connector: dict[ConnectorType, dict[str, _ConnectorSchemaTyping]]
|
|
52
|
+
operation: dict[str, _OperationSchemaTyping]
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
class MapperDomainSchema:
|
|
56
|
+
@staticmethod
|
|
57
|
+
def map(
|
|
58
|
+
app_configuration_schema: ApplicationConfigurationSchema,
|
|
59
|
+
) -> ApplicationConfiguration:
|
|
60
|
+
return ApplicationConfiguration(
|
|
61
|
+
env=app_configuration_schema.env,
|
|
62
|
+
run=app_configuration_schema.run,
|
|
63
|
+
connector=ConnectorRegistry(app_configuration_schema.connector),
|
|
64
|
+
operation=OperationRegistry(app_configuration_schema.operation),
|
|
65
|
+
)
|
{pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/__init__.py
RENAMED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
from .connector_type import ConnectorType
|
|
2
2
|
from .file_operation_action import FileOperationAction
|
|
3
|
+
from .operation_type import OperationType
|
|
3
4
|
from .run_type_application import RunTypeApplication
|
|
4
5
|
from .run_type_environment import RunTypeEnvironment
|
|
5
6
|
|
|
6
7
|
__all__ = [
|
|
7
8
|
"ConnectorType",
|
|
8
9
|
"FileOperationAction",
|
|
10
|
+
"OperationType",
|
|
9
11
|
"RunTypeApplication",
|
|
10
12
|
"RunTypeEnvironment",
|
|
11
13
|
]
|
|
@@ -1,19 +1,18 @@
|
|
|
1
1
|
from dataclasses import dataclass
|
|
2
2
|
|
|
3
|
-
from pycraftcore.application_configuration.enum.connector_type import ConnectorType
|
|
4
3
|
from pycraftcore.application_configuration.enum.run_type_application import (
|
|
5
4
|
RunTypeApplication,
|
|
6
5
|
)
|
|
7
6
|
from pycraftcore.application_configuration.enum.run_type_environment import (
|
|
8
7
|
RunTypeEnvironment,
|
|
9
8
|
)
|
|
10
|
-
from pycraftcore.application_configuration.model.connector import
|
|
11
|
-
from pycraftcore.application_configuration.model.operation import
|
|
9
|
+
from pycraftcore.application_configuration.model.connector import ConnectorRegistry
|
|
10
|
+
from pycraftcore.application_configuration.model.operation import OperationRegistry
|
|
12
11
|
|
|
13
12
|
|
|
14
13
|
@dataclass(frozen=True, slots=True)
|
|
15
14
|
class ApplicationConfiguration:
|
|
16
15
|
env: RunTypeEnvironment
|
|
17
16
|
run: RunTypeApplication
|
|
18
|
-
connector:
|
|
19
|
-
operation:
|
|
17
|
+
connector: ConnectorRegistry
|
|
18
|
+
operation: OperationRegistry
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
from collections.abc import Mapping
|
|
2
|
+
from dataclasses import dataclass, field
|
|
3
|
+
from typing import Literal, overload
|
|
4
|
+
|
|
5
|
+
from pycraftcore.application_configuration.enum.connector_type import ConnectorType
|
|
6
|
+
from pycraftcore.authentication import AuthTyping
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
@dataclass(slots=True)
|
|
10
|
+
class BaseConnector:
|
|
11
|
+
name: str
|
|
12
|
+
type: ConnectorType
|
|
13
|
+
auth: AuthTyping
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
@dataclass(slots=True)
|
|
17
|
+
class ApiConnector(BaseConnector):
|
|
18
|
+
base_url: str
|
|
19
|
+
timeout: int
|
|
20
|
+
retry: int
|
|
21
|
+
certificate: str | None = field(default=None)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@dataclass(slots=True)
|
|
25
|
+
class DatabaseConnector(BaseConnector):
|
|
26
|
+
engine: str
|
|
27
|
+
host: str
|
|
28
|
+
port: int
|
|
29
|
+
default_name: str
|
|
30
|
+
pool: dict[str, int]
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
@dataclass(slots=True)
|
|
34
|
+
class FileConnector(BaseConnector):
|
|
35
|
+
base_path: str
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
@dataclass(slots=True)
|
|
39
|
+
class TelemetryConnector(BaseConnector):
|
|
40
|
+
host: str
|
|
41
|
+
port: int
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
ConnectorTyping = ApiConnector | FileConnector | DatabaseConnector | TelemetryConnector
|
|
45
|
+
|
|
46
|
+
_CONNECTOR_CLASS_BY_TYPE: dict[ConnectorType, type[ConnectorTyping]] = {
|
|
47
|
+
ConnectorType.api: ApiConnector,
|
|
48
|
+
ConnectorType.database: DatabaseConnector,
|
|
49
|
+
ConnectorType.file: FileConnector,
|
|
50
|
+
ConnectorType.telemetry: TelemetryConnector,
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
@dataclass(frozen=True, slots=True)
|
|
55
|
+
class ConnectorRegistry:
|
|
56
|
+
by_type: dict[ConnectorType, dict[str, ConnectorTyping]] = field(default_factory=dict)
|
|
57
|
+
|
|
58
|
+
def __post_init__(self) -> None:
|
|
59
|
+
for kind, bucket in self.by_type.items():
|
|
60
|
+
expected = _CONNECTOR_CLASS_BY_TYPE.get(kind)
|
|
61
|
+
if expected is None:
|
|
62
|
+
continue
|
|
63
|
+
for name, connector in bucket.items():
|
|
64
|
+
if not isinstance(connector, expected):
|
|
65
|
+
raise TypeError(
|
|
66
|
+
f"connector {name!r} declared under {kind.value!r} must be a "
|
|
67
|
+
f"{expected.__name__}, got {type(connector).__name__}"
|
|
68
|
+
)
|
|
69
|
+
|
|
70
|
+
@overload
|
|
71
|
+
def __getitem__(self, kind: Literal[ConnectorType.api]) -> Mapping[str, ApiConnector]: ...
|
|
72
|
+
@overload
|
|
73
|
+
def __getitem__(
|
|
74
|
+
self, kind: Literal[ConnectorType.database]
|
|
75
|
+
) -> Mapping[str, DatabaseConnector]: ...
|
|
76
|
+
@overload
|
|
77
|
+
def __getitem__(self, kind: Literal[ConnectorType.file]) -> Mapping[str, FileConnector]: ...
|
|
78
|
+
@overload
|
|
79
|
+
def __getitem__(
|
|
80
|
+
self, kind: Literal[ConnectorType.telemetry]
|
|
81
|
+
) -> Mapping[str, TelemetryConnector]: ...
|
|
82
|
+
|
|
83
|
+
def __getitem__(self, kind: ConnectorType) -> Mapping[str, ConnectorTyping]:
|
|
84
|
+
return self.by_type.get(kind, {})
|
|
85
|
+
|
|
86
|
+
def api(self, name: str) -> ApiConnector:
|
|
87
|
+
return self._get(ConnectorType.api, name, ApiConnector)
|
|
88
|
+
|
|
89
|
+
def database(self, name: str) -> DatabaseConnector:
|
|
90
|
+
return self._get(ConnectorType.database, name, DatabaseConnector)
|
|
91
|
+
|
|
92
|
+
def file(self, name: str) -> FileConnector:
|
|
93
|
+
return self._get(ConnectorType.file, name, FileConnector)
|
|
94
|
+
|
|
95
|
+
def telemetry(self, name: str) -> TelemetryConnector:
|
|
96
|
+
return self._get(ConnectorType.telemetry, name, TelemetryConnector)
|
|
97
|
+
|
|
98
|
+
def _get[T: ConnectorTyping](self, kind: ConnectorType, name: str, expected: type[T]) -> T:
|
|
99
|
+
try:
|
|
100
|
+
connector = self.by_type[kind][name]
|
|
101
|
+
except KeyError:
|
|
102
|
+
raise KeyError(f"no {kind.value} connector named {name!r}") from None
|
|
103
|
+
assert isinstance(connector, expected)
|
|
104
|
+
return connector
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
from dataclasses import dataclass, field
|
|
2
|
+
from typing import Any
|
|
3
|
+
|
|
4
|
+
from pycraftcore.application_configuration.enum.file_operation_action import (
|
|
5
|
+
FileOperationAction,
|
|
6
|
+
)
|
|
7
|
+
from pycraftcore.application_configuration.enum.operation_type import OperationType
|
|
8
|
+
from pycraftcore.application_configuration.model.connector import (
|
|
9
|
+
ApiConnector,
|
|
10
|
+
FileConnector,
|
|
11
|
+
)
|
|
12
|
+
from pycraftcore.http.enum.http_method import HttpMethod
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
@dataclass(slots=True)
|
|
16
|
+
class BaseOperation:
|
|
17
|
+
name: str
|
|
18
|
+
type: OperationType
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
@dataclass(slots=True)
|
|
22
|
+
class ApiOperation(BaseOperation):
|
|
23
|
+
connector: ApiConnector
|
|
24
|
+
endpoint: str
|
|
25
|
+
method: HttpMethod
|
|
26
|
+
parameters: dict[str, Any]
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
@dataclass(slots=True)
|
|
30
|
+
class FileOperation(BaseOperation):
|
|
31
|
+
connector: FileConnector
|
|
32
|
+
action: FileOperationAction
|
|
33
|
+
parameters: dict[str, Any]
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
OperationTyping = FileOperation | ApiOperation
|
|
37
|
+
|
|
38
|
+
_OPERATION_CLASS_BY_TYPE: dict[OperationType, type[OperationTyping]] = {
|
|
39
|
+
OperationType.api: ApiOperation,
|
|
40
|
+
OperationType.file: FileOperation,
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
@dataclass(frozen=True, slots=True)
|
|
45
|
+
class OperationRegistry:
|
|
46
|
+
"""Operations keyed by name, with per-kind typed lookups.
|
|
47
|
+
|
|
48
|
+
Mirrors `ConnectorRegistry`: each operation declares its own `OperationType`, and a
|
|
49
|
+
mismatch between that declared type and its concrete dataclass is rejected eagerly here
|
|
50
|
+
instead of surfacing later as a wrong-typed lookup.
|
|
51
|
+
"""
|
|
52
|
+
|
|
53
|
+
by_name: dict[str, OperationTyping] = field(default_factory=dict)
|
|
54
|
+
|
|
55
|
+
def __post_init__(self) -> None:
|
|
56
|
+
for name, operation in self.by_name.items():
|
|
57
|
+
expected = _OPERATION_CLASS_BY_TYPE.get(operation.type)
|
|
58
|
+
if expected is None:
|
|
59
|
+
continue
|
|
60
|
+
if not isinstance(operation, expected):
|
|
61
|
+
raise TypeError(
|
|
62
|
+
f"operation {name!r} declared type {operation.type.value!r} but is a "
|
|
63
|
+
f"{type(operation).__name__}, expected {expected.__name__}"
|
|
64
|
+
)
|
|
65
|
+
|
|
66
|
+
def api(self, name: str) -> ApiOperation:
|
|
67
|
+
return self._get(name, ApiOperation)
|
|
68
|
+
|
|
69
|
+
def file(self, name: str) -> FileOperation:
|
|
70
|
+
return self._get(name, FileOperation)
|
|
71
|
+
|
|
72
|
+
def _get[T: OperationTyping](self, name: str, expected: type[T]) -> T:
|
|
73
|
+
try:
|
|
74
|
+
operation = self.by_name[name]
|
|
75
|
+
except KeyError:
|
|
76
|
+
raise KeyError(f"no operation named {name!r}") from None
|
|
77
|
+
if not isinstance(operation, expected):
|
|
78
|
+
raise TypeError(
|
|
79
|
+
f"operation {name!r} is a {type(operation).__name__}, not {expected.__name__}"
|
|
80
|
+
)
|
|
81
|
+
return operation
|