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.
Files changed (130) hide show
  1. {pycraftcore-0.2.0/src/pycraftcore.egg-info → pycraftcore-0.2.2}/PKG-INFO +72 -40
  2. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/Readme.md +71 -39
  3. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/pyproject.toml +1 -1
  4. pycraftcore-0.2.2/src/pycraftcore/application_configuration/__init__.py +17 -0
  5. pycraftcore-0.2.2/src/pycraftcore/application_configuration/adapter/schema.py +65 -0
  6. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/__init__.py +2 -0
  7. pycraftcore-0.2.2/src/pycraftcore/application_configuration/enum/operation_type.py +6 -0
  8. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/model/configuration.py +4 -5
  9. pycraftcore-0.2.2/src/pycraftcore/application_configuration/model/connector.py +104 -0
  10. pycraftcore-0.2.2/src/pycraftcore/application_configuration/model/operation.py +81 -0
  11. {pycraftcore-0.2.0 → pycraftcore-0.2.2/src/pycraftcore.egg-info}/PKG-INFO +72 -40
  12. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore.egg-info/SOURCES.txt +1 -0
  13. pycraftcore-0.2.0/src/pycraftcore/application_configuration/__init__.py +0 -9
  14. pycraftcore-0.2.0/src/pycraftcore/application_configuration/adapter/schema.py +0 -29
  15. pycraftcore-0.2.0/src/pycraftcore/application_configuration/model/connector.py +0 -42
  16. pycraftcore-0.2.0/src/pycraftcore/application_configuration/model/operation.py +0 -30
  17. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/LICENSE +0 -0
  18. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/setup.cfg +0 -0
  19. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/__init__.py +0 -0
  20. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/adapter/__init__.py +0 -0
  21. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/adapter/load_application_configuration.py +0 -0
  22. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/adapter/omega_configuration_reader.py +0 -0
  23. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/connector_type.py +0 -0
  24. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/file_operation_action.py +0 -0
  25. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/run_type_application.py +0 -0
  26. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/enum/run_type_environment.py +0 -0
  27. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/model/__init__.py +0 -0
  28. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/port/__init__.py +0 -0
  29. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/port/configuration.py +0 -0
  30. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/application_configuration/port/configuration_reader.py +0 -0
  31. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/__init__.py +0 -0
  32. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/__init__.py +0 -0
  33. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/auth_type.py +0 -0
  34. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/basic_auth.py +0 -0
  35. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/no_auth.py +0 -0
  36. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/authentication/model/token_auth.py +0 -0
  37. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/__init__.py +0 -0
  38. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/__init__.py +0 -0
  39. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/compute_engine.py +0 -0
  40. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/numpy_arithmetic_operation.py +0 -0
  41. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/adapter/scipy_calculus_operation.py +0 -0
  42. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/const_typing.py +0 -0
  43. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/__init__.py +0 -0
  44. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/arithmetic_operation.py +0 -0
  45. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/calculus_operation.py +0 -0
  46. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/computation_engine/port/engine.py +0 -0
  47. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/__init__.py +0 -0
  48. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/__init__.py +0 -0
  49. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/extension/__init__.py +0 -0
  50. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/extension/yml_reader.py +0 -0
  51. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/extension/yml_writer.py +0 -0
  52. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/factory.py +0 -0
  53. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/handler.py +0 -0
  54. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/provider.py +0 -0
  55. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/adapter/strategy.py +0 -0
  56. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/port/__init__.py +0 -0
  57. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/port/reader.py +0 -0
  58. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/file_handler/port/writer.py +0 -0
  59. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/__init__.py +0 -0
  60. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/__init__.py +0 -0
  61. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/aiohttp_client.py +0 -0
  62. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/circuit_breaker_policy.py +0 -0
  63. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/httpx_client.py +0 -0
  64. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/resilient_client.py +0 -0
  65. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/adapter/retry_policy.py +0 -0
  66. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/__init__.py +0 -0
  67. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/circuite_breaker_configuration.py +0 -0
  68. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/client_configuration.py +0 -0
  69. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/http_client_configuration.py +0 -0
  70. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/limits_configuration.py +0 -0
  71. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/retry_configuration.py +0 -0
  72. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/configuration/security_configuration.py +0 -0
  73. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/context/__init__.py +0 -0
  74. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/context/request_context.py +0 -0
  75. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/enum/__init__.py +0 -0
  76. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/enum/circuit_breaker_status.py +0 -0
  77. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/enum/http_method.py +0 -0
  78. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/exception/__init__.py +0 -0
  79. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/exception/circuit_breaker_open_exception.py +0 -0
  80. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/middleware/__init__.py +0 -0
  81. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/middleware/request_id_middleware.py +0 -0
  82. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/middleware/request_middleware.py +0 -0
  83. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/__init__.py +0 -0
  84. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/async_circuit_breaker.py +0 -0
  85. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/async_http_client.py +0 -0
  86. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/async_resilient_http_client.py +0 -0
  87. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/http/port/retry.py +0 -0
  88. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/__init__.py +0 -0
  89. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/adapter/__init__.py +0 -0
  90. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/adapter/loguru_logger.py +0 -0
  91. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/port/__init__.py +0 -0
  92. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/logger/port/logger.py +0 -0
  93. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/profiler/__init__.py +0 -0
  94. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/profiler/profiled.py +0 -0
  95. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/__init__.py +0 -0
  96. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/constants/__init__.py +0 -0
  97. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/constants/allowed_root_statements.py +0 -0
  98. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/constants/forbidden_statements.py +0 -0
  99. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/__init__.py +0 -0
  100. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/adapter.py +0 -0
  101. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/factory.py +0 -0
  102. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/query_language/sql/sql_handler.py +0 -0
  103. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/__init__.py +0 -0
  104. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/repository.py +0 -0
  105. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/__init__.py +0 -0
  106. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/adapter.py +0 -0
  107. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/factory.py +0 -0
  108. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/mapper.py +0 -0
  109. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/repository/sqlite/schema.py +0 -0
  110. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/__init__.py +0 -0
  111. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/code.py +0 -0
  112. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/__init__.py +0 -0
  113. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/adapter.py +0 -0
  114. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/factory.py +0 -0
  115. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/runtime/python/schema.py +0 -0
  116. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/__init__.py +0 -0
  117. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/__init__.py +0 -0
  118. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/binary_serializer.py +0 -0
  119. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/dictionary_serializer.py +0 -0
  120. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/adapter/json_serializer.py +0 -0
  121. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/port/__init__.py +0 -0
  122. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/serializer/port/serializer.py +0 -0
  123. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/__init__.py +0 -0
  124. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/adapter/__init__.py +0 -0
  125. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/adapter/open_telemetry.py +0 -0
  126. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/port/__init__.py +0 -0
  127. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore/telemetry/port/telemetry.py +0 -0
  128. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore.egg-info/dependency_links.txt +0 -0
  129. {pycraftcore-0.2.0 → pycraftcore-0.2.2}/src/pycraftcore.egg-info/requires.txt +0 -0
  130. {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.0
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 loading, HTTP
41
- clients with retries/circuit-breaking/tracing, a pooled async SQLite repository, a sandboxed Python code runner, safe
42
- SQL validation, serialization, and a handful of other adapters — all built as small, swappable ports/adapters rather
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
- - [Components](#components)
50
+ - [Functionality](#functionality)
55
51
  - [Installation](#installation)
56
52
  - [Configuration](#configuration)
57
53
  - [Development](#development)
58
54
 
59
55
  ---
60
56
 
61
- ## Components
57
+ ## Functionality
62
58
 
63
- | Module | Package | Description |
59
+ | Module | Package | What it gives you |
64
60
  |---|---|---|
65
- | Configuration Management | `application_configuration` | Environment-driven YAML config (connector/operation/cronjob), `${oc.env:...}` injection, typed/validated models |
66
- | Authentication | `application_configuration` | Typed auth models (none / basic / token) shared across connector configs |
67
- | Logging | `logger` | Singleton structured logger over loguru, behind a `Logger` port |
68
- | Telemetry & Observability | `telemetry` | OpenTelemetry-backed tracing: span decorator, request-id enrichment, console or OTLP export |
69
- | HTTP Client Utilities | `http` | `aiohttp`/`httpx` clients behind a common port, retry policy, circuit breaker, and a `ResilientClient` composing all three with tracing |
70
- | Repository / SQLite | `repository` | Async SQLite repository backed by a real connection pool (WAL mode, `busy_timeout`) — not just an async-wrapped single connection |
71
- | Query Language / SQL Safety | `query_language` | `sqlglot`-based parser that rejects any non-read-only statement anywhere in the parsed tree, including nested |
72
- | Runtime / Sandboxed Execution | `runtime` | Runs arbitrary Python in a separate OS process with an import allowlist, memory/CPU limits, and a timeout |
73
- | Serialization | `serialization` | JSON, dict, and binary (msgpack) serialization for dataclasses, backed by pydantic validation |
74
- | Scientific computation engine | `computation` | NumPy/SciPy-backed arithmetic (log returns, rolling average/stddev) and calculus (integration, interpolation) |
75
- | File handler | `file_handler` | Extension-based read/write strategy (currently YAML), pluggable for more formats |
76
- | Profiler | `profiler` | `pyinstrument`-based async function profiling decorator |
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
- A database connector additionally takes `engine`, `host`, `port`, `default_name`, and a `pool` block controlling how
138
- many pooled connections the repository opens. An `operation/<tag>.yml` references a connector by tag and adds
139
- `endpoint`, `method`, and `parameters` — see `pycraftcore.application_configuration.model` for the full schema.
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
- **Loading it:**
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
- LoadApplicationConfiguration,
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() # re-reads and validates every call; raises on failure (after logging)
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 # uv run ty (strict mode)
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 loading, HTTP
4
- clients with retries/circuit-breaking/tracing, a pooled async SQLite repository, a sandboxed Python code runner, safe
5
- SQL validation, serialization, and a handful of other adapters — all built as small, swappable ports/adapters rather
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
- - [Components](#components)
13
+ - [Functionality](#functionality)
18
14
  - [Installation](#installation)
19
15
  - [Configuration](#configuration)
20
16
  - [Development](#development)
21
17
 
22
18
  ---
23
19
 
24
- ## Components
20
+ ## Functionality
25
21
 
26
- | Module | Package | Description |
22
+ | Module | Package | What it gives you |
27
23
  |---|---|---|
28
- | Configuration Management | `application_configuration` | Environment-driven YAML config (connector/operation/cronjob), `${oc.env:...}` injection, typed/validated models |
29
- | Authentication | `application_configuration` | Typed auth models (none / basic / token) shared across connector configs |
30
- | Logging | `logger` | Singleton structured logger over loguru, behind a `Logger` port |
31
- | Telemetry & Observability | `telemetry` | OpenTelemetry-backed tracing: span decorator, request-id enrichment, console or OTLP export |
32
- | HTTP Client Utilities | `http` | `aiohttp`/`httpx` clients behind a common port, retry policy, circuit breaker, and a `ResilientClient` composing all three with tracing |
33
- | Repository / SQLite | `repository` | Async SQLite repository backed by a real connection pool (WAL mode, `busy_timeout`) — not just an async-wrapped single connection |
34
- | Query Language / SQL Safety | `query_language` | `sqlglot`-based parser that rejects any non-read-only statement anywhere in the parsed tree, including nested |
35
- | Runtime / Sandboxed Execution | `runtime` | Runs arbitrary Python in a separate OS process with an import allowlist, memory/CPU limits, and a timeout |
36
- | Serialization | `serialization` | JSON, dict, and binary (msgpack) serialization for dataclasses, backed by pydantic validation |
37
- | Scientific computation engine | `computation` | NumPy/SciPy-backed arithmetic (log returns, rolling average/stddev) and calculus (integration, interpolation) |
38
- | File handler | `file_handler` | Extension-based read/write strategy (currently YAML), pluggable for more formats |
39
- | Profiler | `profiler` | `pyinstrument`-based async function profiling decorator |
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
- A database connector additionally takes `engine`, `host`, `port`, `default_name`, and a `pool` block controlling how
101
- many pooled connections the repository opens. An `operation/<tag>.yml` references a connector by tag and adds
102
- `endpoint`, `method`, and `parameters` — see `pycraftcore.application_configuration.model` for the full schema.
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
- **Loading it:**
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
- LoadApplicationConfiguration,
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() # re-reads and validates every call; raises on failure (after logging)
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 # uv run ty (strict mode)
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.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pycraftcore"
7
- version = "0.2.0"
7
+ version = "0.2.2"
8
8
  authors = [
9
9
  { name = "Toufik Saouchi", email = "toufik.saouchi@gmail.com" },
10
10
  ]
@@ -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
+ )
@@ -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
  ]
@@ -0,0 +1,6 @@
1
+ from enum import Enum
2
+
3
+
4
+ class OperationType(Enum):
5
+ api = "api"
6
+ file = "file"
@@ -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 ConnectorTyping
11
- from pycraftcore.application_configuration.model.operation import OperationTyping
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: dict[ConnectorType, dict[str, ConnectorTyping]]
19
- operation: dict[str, OperationTyping]
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