pycraftcore 0.2.0__tar.gz → 0.2.3__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 (133) hide show
  1. {pycraftcore-0.2.0/src/pycraftcore.egg-info → pycraftcore-0.2.3}/PKG-INFO +70 -34
  2. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/Readme.md +69 -33
  3. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/pyproject.toml +3 -3
  4. pycraftcore-0.2.3/src/pycraftcore/application_configuration/__init__.py +17 -0
  5. pycraftcore-0.2.3/src/pycraftcore/application_configuration/adapter/schema.py +64 -0
  6. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/enum/__init__.py +2 -0
  7. pycraftcore-0.2.3/src/pycraftcore/application_configuration/enum/operation_type.py +6 -0
  8. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/model/configuration.py +4 -5
  9. pycraftcore-0.2.3/src/pycraftcore/application_configuration/model/connector.py +104 -0
  10. pycraftcore-0.2.3/src/pycraftcore/application_configuration/model/operation.py +74 -0
  11. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/port/configuration.py +2 -3
  12. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/port/configuration_reader.py +2 -1
  13. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/adapter/compute_engine.py +5 -3
  14. pycraftcore-0.2.3/src/pycraftcore/computation_engine/port/arithmetic_operation.py +15 -0
  15. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/port/calculus_operation.py +4 -3
  16. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/port/engine.py +5 -4
  17. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/adapter/aiohttp_client.py +2 -2
  18. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/adapter/resilient_client.py +6 -2
  19. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/port/async_circuit_breaker.py +2 -1
  20. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/port/async_http_client.py +4 -4
  21. pycraftcore-0.2.3/src/pycraftcore/http/port/async_resilient_http_client.py +8 -0
  22. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/port/retry.py +2 -1
  23. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/logger/port/logger.py +2 -1
  24. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/sql/sql_handler.py +3 -1
  25. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/repository/repository.py +3 -1
  26. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/runtime/code.py +3 -1
  27. pycraftcore-0.2.3/src/pycraftcore/serializer/port/serializer.py +12 -0
  28. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/telemetry/port/telemetry.py +3 -1
  29. {pycraftcore-0.2.0 → pycraftcore-0.2.3/src/pycraftcore.egg-info}/PKG-INFO +70 -34
  30. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore.egg-info/SOURCES.txt +1 -0
  31. pycraftcore-0.2.0/src/pycraftcore/application_configuration/__init__.py +0 -9
  32. pycraftcore-0.2.0/src/pycraftcore/application_configuration/adapter/schema.py +0 -29
  33. pycraftcore-0.2.0/src/pycraftcore/application_configuration/model/connector.py +0 -42
  34. pycraftcore-0.2.0/src/pycraftcore/application_configuration/model/operation.py +0 -30
  35. pycraftcore-0.2.0/src/pycraftcore/computation_engine/port/arithmetic_operation.py +0 -16
  36. pycraftcore-0.2.0/src/pycraftcore/http/port/async_resilient_http_client.py +0 -6
  37. pycraftcore-0.2.0/src/pycraftcore/serializer/port/serializer.py +0 -11
  38. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/LICENSE +0 -0
  39. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/setup.cfg +0 -0
  40. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/__init__.py +0 -0
  41. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/adapter/__init__.py +0 -0
  42. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/adapter/load_application_configuration.py +0 -0
  43. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/adapter/omega_configuration_reader.py +0 -0
  44. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/enum/connector_type.py +0 -0
  45. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/enum/file_operation_action.py +0 -0
  46. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/enum/run_type_application.py +0 -0
  47. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/enum/run_type_environment.py +0 -0
  48. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/model/__init__.py +0 -0
  49. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/application_configuration/port/__init__.py +0 -0
  50. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/authentication/__init__.py +0 -0
  51. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/authentication/model/__init__.py +0 -0
  52. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/authentication/model/auth_type.py +0 -0
  53. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/authentication/model/basic_auth.py +0 -0
  54. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/authentication/model/no_auth.py +0 -0
  55. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/authentication/model/token_auth.py +0 -0
  56. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/__init__.py +0 -0
  57. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/adapter/__init__.py +0 -0
  58. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/adapter/numpy_arithmetic_operation.py +0 -0
  59. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/adapter/scipy_calculus_operation.py +0 -0
  60. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/const_typing.py +0 -0
  61. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/computation_engine/port/__init__.py +0 -0
  62. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/__init__.py +0 -0
  63. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/__init__.py +0 -0
  64. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/extension/__init__.py +0 -0
  65. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/extension/yml_reader.py +0 -0
  66. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/extension/yml_writer.py +0 -0
  67. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/factory.py +0 -0
  68. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/handler.py +0 -0
  69. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/provider.py +0 -0
  70. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/adapter/strategy.py +0 -0
  71. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/port/__init__.py +0 -0
  72. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/port/reader.py +0 -0
  73. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/file_handler/port/writer.py +0 -0
  74. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/__init__.py +0 -0
  75. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/adapter/__init__.py +0 -0
  76. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/adapter/circuit_breaker_policy.py +0 -0
  77. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/adapter/httpx_client.py +0 -0
  78. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/adapter/retry_policy.py +0 -0
  79. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/configuration/__init__.py +0 -0
  80. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/configuration/circuite_breaker_configuration.py +0 -0
  81. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/configuration/client_configuration.py +0 -0
  82. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/configuration/http_client_configuration.py +0 -0
  83. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/configuration/limits_configuration.py +0 -0
  84. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/configuration/retry_configuration.py +0 -0
  85. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/configuration/security_configuration.py +0 -0
  86. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/context/__init__.py +0 -0
  87. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/context/request_context.py +0 -0
  88. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/enum/__init__.py +0 -0
  89. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/enum/circuit_breaker_status.py +0 -0
  90. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/enum/http_method.py +0 -0
  91. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/exception/__init__.py +0 -0
  92. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/exception/circuit_breaker_open_exception.py +0 -0
  93. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/middleware/__init__.py +0 -0
  94. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/middleware/request_id_middleware.py +0 -0
  95. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/middleware/request_middleware.py +0 -0
  96. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/http/port/__init__.py +0 -0
  97. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/logger/__init__.py +0 -0
  98. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/logger/adapter/__init__.py +0 -0
  99. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/logger/adapter/loguru_logger.py +0 -0
  100. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/logger/port/__init__.py +0 -0
  101. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/profiler/__init__.py +0 -0
  102. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/profiler/profiled.py +0 -0
  103. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/__init__.py +0 -0
  104. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/constants/__init__.py +0 -0
  105. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/constants/allowed_root_statements.py +0 -0
  106. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/constants/forbidden_statements.py +0 -0
  107. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/sql/__init__.py +0 -0
  108. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/sql/adapter.py +0 -0
  109. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/query_language/sql/factory.py +0 -0
  110. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/repository/__init__.py +0 -0
  111. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/repository/sqlite/__init__.py +0 -0
  112. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/repository/sqlite/adapter.py +0 -0
  113. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/repository/sqlite/factory.py +0 -0
  114. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/repository/sqlite/mapper.py +0 -0
  115. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/repository/sqlite/schema.py +0 -0
  116. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/runtime/__init__.py +0 -0
  117. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/runtime/python/__init__.py +0 -0
  118. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/runtime/python/adapter.py +0 -0
  119. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/runtime/python/factory.py +0 -0
  120. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/runtime/python/schema.py +0 -0
  121. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/serializer/__init__.py +0 -0
  122. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/serializer/adapter/__init__.py +0 -0
  123. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/serializer/adapter/binary_serializer.py +0 -0
  124. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/serializer/adapter/dictionary_serializer.py +0 -0
  125. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/serializer/adapter/json_serializer.py +0 -0
  126. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/serializer/port/__init__.py +0 -0
  127. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/telemetry/__init__.py +0 -0
  128. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/telemetry/adapter/__init__.py +0 -0
  129. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/telemetry/adapter/open_telemetry.py +0 -0
  130. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore/telemetry/port/__init__.py +0 -0
  131. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore.egg-info/dependency_links.txt +0 -0
  132. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/src/pycraftcore.egg-info/requires.txt +0 -0
  133. {pycraftcore-0.2.0 → pycraftcore-0.2.3}/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.3
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,11 +130,46 @@ 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:
140
134
 
141
- **Loading it:**
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:
148
+
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
@@ -149,17 +180,22 @@ from pycraftcore.application_configuration.adapter.load_application_configuratio
149
180
  from pycraftcore.application_configuration.adapter.omega_configuration_reader import (
150
181
  OmegaConfigurationReader,
151
182
  )
152
- from pycraftcore.application_configuration.enum.run_type_environment import (
153
- RunTypeEnvironment,
154
- )
183
+ from pycraftcore.application_configuration.enum.run_type_environment import RunTypeEnvironment
155
184
  from pycraftcore.logger.adapter.loguru_logger import LoguruLogger
156
185
 
157
186
  reader = OmegaConfigurationReader(RunTypeEnvironment.deploy, Path("config"))
158
187
  loader = LoadApplicationConfiguration(reader, LoguruLogger())
159
188
 
160
- config = loader.load() # re-reads and validates every call; raises on failure (after logging)
189
+ config = loader.load()
190
+
191
+ connector = config.connector.api("<connector_tag>") # ApiConnector
192
+ operation = config.operation.api("<operation_tag>") # ApiOperation
161
193
  ```
162
194
 
195
+ `config.connector`/`config.operation` return the concrete connector/operation type for a given tag (`.api()`,
196
+ `.database()`, `.file()`, `.telemetry()` where applicable) instead of a raw dict — see
197
+ `pycraftcore.application_configuration` for the full API.
198
+
163
199
  ---
164
200
 
165
201
  ## Development
@@ -186,9 +222,9 @@ uv run pre-commit run --all-files --hook-stage pre-commit
186
222
  ```bash
187
223
  make install_dev # uv sync --group dev
188
224
  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)
225
+ make lint # uv run ruff check
226
+ make format # uv run ruff format
227
+ make typecheck # uv run ty check (strict mode)
192
228
  ```
193
229
 
194
230
  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,11 +93,46 @@ 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:
103
97
 
104
- **Loading it:**
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:
111
+
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
@@ -112,17 +143,22 @@ from pycraftcore.application_configuration.adapter.load_application_configuratio
112
143
  from pycraftcore.application_configuration.adapter.omega_configuration_reader import (
113
144
  OmegaConfigurationReader,
114
145
  )
115
- from pycraftcore.application_configuration.enum.run_type_environment import (
116
- RunTypeEnvironment,
117
- )
146
+ from pycraftcore.application_configuration.enum.run_type_environment import RunTypeEnvironment
118
147
  from pycraftcore.logger.adapter.loguru_logger import LoguruLogger
119
148
 
120
149
  reader = OmegaConfigurationReader(RunTypeEnvironment.deploy, Path("config"))
121
150
  loader = LoadApplicationConfiguration(reader, LoguruLogger())
122
151
 
123
- config = loader.load() # re-reads and validates every call; raises on failure (after logging)
152
+ config = loader.load()
153
+
154
+ connector = config.connector.api("<connector_tag>") # ApiConnector
155
+ operation = config.operation.api("<operation_tag>") # ApiOperation
124
156
  ```
125
157
 
158
+ `config.connector`/`config.operation` return the concrete connector/operation type for a given tag (`.api()`,
159
+ `.database()`, `.file()`, `.telemetry()` where applicable) instead of a raw dict — see
160
+ `pycraftcore.application_configuration` for the full API.
161
+
126
162
  ---
127
163
 
128
164
  ## Development
@@ -149,9 +185,9 @@ uv run pre-commit run --all-files --hook-stage pre-commit
149
185
  ```bash
150
186
  make install_dev # uv sync --group dev
151
187
  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)
188
+ make lint # uv run ruff check
189
+ make format # uv run ruff format
190
+ make typecheck # uv run ty check (strict mode)
155
191
  ```
156
192
 
157
193
  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.3"
8
8
  authors = [
9
9
  { name = "Toufik Saouchi", email = "toufik.saouchi@gmail.com" },
10
10
  ]
@@ -71,8 +71,8 @@ ignore = [
71
71
  ]
72
72
 
73
73
  [tool.ty.src]
74
- include = ["src"]
75
- exclude = ["tests", ".venv"]
74
+ include = ["src", "tests/ducktype"]
75
+ exclude = [".venv"]
76
76
 
77
77
  [tool.setuptools.packages.find]
78
78
  where = ["src"]
@@ -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,64 @@
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
+ return value["type"] if isinstance(value, dict) else value.type.value
31
+
32
+
33
+ _ConnectorSchemaTyping = Annotated[
34
+ Annotated[ApiConnector, Tag("api")]
35
+ | Annotated[DatabaseConnector, Tag("database")]
36
+ | Annotated[FileConnector, Tag("file")]
37
+ | Annotated[TelemetryConnector, Tag("telemetry")],
38
+ Discriminator(_kind_tag),
39
+ ]
40
+
41
+ _OperationSchemaTyping = Annotated[
42
+ Annotated[ApiOperation, Tag("api")] | Annotated[FileOperation, Tag("file")],
43
+ Discriminator(_kind_tag),
44
+ ]
45
+
46
+
47
+ class ApplicationConfigurationSchema(BaseModel):
48
+ env: RunTypeEnvironment
49
+ run: RunTypeApplication
50
+ connector: dict[ConnectorType, dict[str, _ConnectorSchemaTyping]]
51
+ operation: dict[str, _OperationSchemaTyping]
52
+
53
+
54
+ class MapperDomainSchema:
55
+ @staticmethod
56
+ def map(
57
+ app_configuration_schema: ApplicationConfigurationSchema,
58
+ ) -> ApplicationConfiguration:
59
+ return ApplicationConfiguration(
60
+ env=app_configuration_schema.env,
61
+ run=app_configuration_schema.run,
62
+ connector=ConnectorRegistry(app_configuration_schema.connector),
63
+ operation=OperationRegistry(app_configuration_schema.operation),
64
+ )
@@ -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,74 @@
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
+ by_name: dict[str, OperationTyping] = field(default_factory=dict)
47
+
48
+ def __post_init__(self) -> None:
49
+ for name, operation in self.by_name.items():
50
+ expected = _OPERATION_CLASS_BY_TYPE.get(operation.type)
51
+ if expected is None:
52
+ continue
53
+ if not isinstance(operation, expected):
54
+ raise TypeError(
55
+ f"operation {name!r} declared type {operation.type.value!r} but is a "
56
+ f"{type(operation).__name__}, expected {expected.__name__}"
57
+ )
58
+
59
+ def api(self, name: str) -> ApiOperation:
60
+ return self._get(name, ApiOperation)
61
+
62
+ def file(self, name: str) -> FileOperation:
63
+ return self._get(name, FileOperation)
64
+
65
+ def _get[T: OperationTyping](self, name: str, expected: type[T]) -> T:
66
+ try:
67
+ operation = self.by_name[name]
68
+ except KeyError:
69
+ raise KeyError(f"no operation named {name!r}") from None
70
+ if not isinstance(operation, expected):
71
+ raise TypeError(
72
+ f"operation {name!r} is a {type(operation).__name__}, not {expected.__name__}"
73
+ )
74
+ return operation
@@ -1,11 +1,10 @@
1
- from typing import Protocol
1
+ from typing import Protocol, runtime_checkable
2
2
 
3
3
  from pycraftcore.application_configuration.model.configuration import (
4
4
  ApplicationConfiguration,
5
5
  )
6
6
 
7
7
 
8
+ @runtime_checkable
8
9
  class Configuration(Protocol):
9
10
  def load(self) -> ApplicationConfiguration | None: ...
10
-
11
- def reload(self) -> ApplicationConfiguration | None: ...
@@ -1,9 +1,10 @@
1
- from typing import Protocol
1
+ from typing import Protocol, runtime_checkable
2
2
 
3
3
  from pycraftcore.application_configuration.model.configuration import (
4
4
  ApplicationConfiguration,
5
5
  )
6
6
 
7
7
 
8
+ @runtime_checkable
8
9
  class ConfigurationReader(Protocol):
9
10
  def read(self) -> ApplicationConfiguration: ...