pycraftcore 2.0.0__tar.gz → 2.0.1__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 (207) hide show
  1. pycraftcore-2.0.1/PKG-INFO +326 -0
  2. pycraftcore-2.0.1/Readme.md +286 -0
  3. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/pyproject.toml +1 -1
  4. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/__init__.py +4 -0
  5. pycraftcore-2.0.1/src/pycraftcore/file_handler/adapter/extension/svg/svg_reader.py +8 -0
  6. pycraftcore-2.0.1/src/pycraftcore/file_handler/adapter/extension/svg/svg_writer.py +8 -0
  7. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/handler.py +3 -0
  8. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/adapter/python/host_bridge_server.py +4 -1
  9. pycraftcore-2.0.1/src/pycraftcore/serializer/__init__.py +0 -0
  10. pycraftcore-2.0.1/src/pycraftcore.egg-info/PKG-INFO +326 -0
  11. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore.egg-info/SOURCES.txt +3 -0
  12. pycraftcore-2.0.0/PKG-INFO +0 -257
  13. pycraftcore-2.0.0/Readme.md +0 -217
  14. pycraftcore-2.0.0/src/pycraftcore.egg-info/PKG-INFO +0 -257
  15. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/LICENSE +0 -0
  16. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/setup.cfg +0 -0
  17. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/__init__.py +0 -0
  18. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/__init__.py +0 -0
  19. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/adapter/__init__.py +0 -0
  20. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/adapter/load_application_configuration.py +0 -0
  21. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/adapter/omega_configuration_reader.py +0 -0
  22. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/adapter/schema.py +0 -0
  23. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/enum/__init__.py +0 -0
  24. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/enum/connector_type.py +0 -0
  25. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/enum/file_operation_action.py +0 -0
  26. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/enum/operation_type.py +0 -0
  27. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/enum/run_type_application.py +0 -0
  28. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/enum/run_type_environment.py +0 -0
  29. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/model/__init__.py +0 -0
  30. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/model/configuration.py +0 -0
  31. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/model/connector.py +0 -0
  32. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/model/operation.py +0 -0
  33. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/port/__init__.py +0 -0
  34. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/port/configuration.py +0 -0
  35. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/application_configuration/port/configuration_reader.py +0 -0
  36. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/authentication/__init__.py +0 -0
  37. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/authentication/model/__init__.py +0 -0
  38. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/authentication/model/auth_type.py +0 -0
  39. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/authentication/model/basic_auth.py +0 -0
  40. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/authentication/model/no_auth.py +0 -0
  41. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/authentication/model/token_auth.py +0 -0
  42. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/__init__.py +0 -0
  43. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/adapter/__init__.py +0 -0
  44. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/adapter/aiobreaker_circuit_breaker_policy.py +0 -0
  45. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/configuration/__init__.py +0 -0
  46. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/configuration/circuit_breaker_configuration.py +0 -0
  47. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/enum/__init__.py +0 -0
  48. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/enum/circuit_breaker_status.py +0 -0
  49. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/exception/__init__.py +0 -0
  50. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/exception/circuit_breaker_open_exception.py +0 -0
  51. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/port/__init__.py +0 -0
  52. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/circuit_breaker/port/async_circuit_breaker.py +0 -0
  53. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/__init__.py +0 -0
  54. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/adapter/__init__.py +0 -0
  55. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/adapter/compute_engine.py +0 -0
  56. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/adapter/numpy_arithmetic_operation.py +0 -0
  57. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/adapter/scipy_calculus_operation.py +0 -0
  58. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/const_typing.py +0 -0
  59. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/port/__init__.py +0 -0
  60. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/port/arithmetic_operation.py +0 -0
  61. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/port/calculus_operation.py +0 -0
  62. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/computation_engine/port/engine.py +0 -0
  63. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/context/__init__.py +0 -0
  64. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/context/request_id_context.py +0 -0
  65. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/__init__.py +0 -0
  66. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/__init__.py +0 -0
  67. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/csv/__init__.py +0 -0
  68. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/csv/csv_reader.py +0 -0
  69. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/csv/csv_writer.py +0 -0
  70. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/json/__init__.py +0 -0
  71. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/json/json_reader.py +0 -0
  72. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/json/json_writer.py +0 -0
  73. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/markdown/__init__.py +0 -0
  74. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/markdown/markdown_reader.py +0 -0
  75. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/markdown/markdown_writer.py +0 -0
  76. {pycraftcore-2.0.0/src/pycraftcore/file_handler/adapter/extension/txt → pycraftcore-2.0.1/src/pycraftcore/file_handler/adapter/extension/svg}/__init__.py +0 -0
  77. {pycraftcore-2.0.0/src/pycraftcore/file_handler/adapter/extension/yml → pycraftcore-2.0.1/src/pycraftcore/file_handler/adapter/extension/txt}/__init__.py +0 -0
  78. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/txt/txt_reader.py +0 -0
  79. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/txt/txt_writer.py +0 -0
  80. {pycraftcore-2.0.0/src/pycraftcore/file_handler/enum → pycraftcore-2.0.1/src/pycraftcore/file_handler/adapter/extension/yml}/__init__.py +0 -0
  81. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/yml/yml_reader.py +0 -0
  82. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/extension/yml/yml_writer.py +0 -0
  83. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/factory.py +0 -0
  84. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/provider.py +0 -0
  85. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/adapter/strategy.py +0 -0
  86. {pycraftcore-2.0.0/src/pycraftcore/http → pycraftcore-2.0.1/src/pycraftcore/file_handler/enum}/__init__.py +0 -0
  87. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/enum/read_chunk_mode.py +0 -0
  88. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/port/__init__.py +0 -0
  89. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/port/file_handler_factory.py +0 -0
  90. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/port/file_handler_provider.py +0 -0
  91. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/port/reader.py +0 -0
  92. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/port/writer.py +0 -0
  93. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/schema/__init__.py +0 -0
  94. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/file_handler/schema/file_manifest.py +0 -0
  95. {pycraftcore-2.0.0/src/pycraftcore/http/context → pycraftcore-2.0.1/src/pycraftcore/http}/__init__.py +0 -0
  96. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/adapter/__init__.py +0 -0
  97. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/adapter/aiohttp_client.py +0 -0
  98. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/adapter/httpx_client.py +0 -0
  99. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/configuration/__init__.py +0 -0
  100. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/configuration/client_configuration.py +0 -0
  101. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/configuration/http_client_configuration.py +0 -0
  102. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/configuration/limits_configuration.py +0 -0
  103. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/configuration/security_configuration.py +0 -0
  104. {pycraftcore-2.0.0/src/pycraftcore/http/policy → pycraftcore-2.0.1/src/pycraftcore/http/context}/__init__.py +0 -0
  105. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/context/request_context.py +0 -0
  106. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/enum/__init__.py +0 -0
  107. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/enum/http_method.py +0 -0
  108. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/middleware/__init__.py +0 -0
  109. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/middleware/request_id_middleware.py +0 -0
  110. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/middleware/request_middleware.py +0 -0
  111. {pycraftcore-2.0.0/src/pycraftcore/profiler → pycraftcore-2.0.1/src/pycraftcore/http/policy}/__init__.py +0 -0
  112. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/policy/http_error_policy.py +0 -0
  113. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/port/__init__.py +0 -0
  114. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/http/port/async_http_client.py +0 -0
  115. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/logger/__init__.py +0 -0
  116. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/logger/adapter/__init__.py +0 -0
  117. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/logger/adapter/standard_logger.py +0 -0
  118. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/logger/configuration.py +0 -0
  119. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/logger/port/__init__.py +0 -0
  120. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/logger/port/logger.py +0 -0
  121. {pycraftcore-2.0.0/src/pycraftcore/query_language → pycraftcore-2.0.1/src/pycraftcore/profiler}/__init__.py +0 -0
  122. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/profiler/profiled.py +0 -0
  123. {pycraftcore-2.0.0/src/pycraftcore/query_language/constants/sql → pycraftcore-2.0.1/src/pycraftcore/query_language}/__init__.py +0 -0
  124. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/adapter/__init__.py +0 -0
  125. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/adapter/sql/__init__.py +0 -0
  126. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/adapter/sql/adapter.py +0 -0
  127. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/adapter/sql/factory.py +0 -0
  128. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/constants/__init__.py +0 -0
  129. {pycraftcore-2.0.0/src/pycraftcore/repository → pycraftcore-2.0.1/src/pycraftcore/query_language/constants/sql}/__init__.py +0 -0
  130. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/constants/sql/allowed_sql_root_statements.py +0 -0
  131. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/constants/sql/forbidden_sql_statements.py +0 -0
  132. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/port/__init__.py +0 -0
  133. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/query_language/port/query_handler.py +0 -0
  134. {pycraftcore-2.0.0/src/pycraftcore/repository/adapter/no_sql → pycraftcore-2.0.1/src/pycraftcore/repository}/__init__.py +0 -0
  135. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/__init__.py +0 -0
  136. {pycraftcore-2.0.0/src/pycraftcore/repository/adapter/no_sql/mongodb → pycraftcore-2.0.1/src/pycraftcore/repository/adapter/no_sql}/__init__.py +0 -0
  137. {pycraftcore-2.0.0/src/pycraftcore/repository/adapter/sql → pycraftcore-2.0.1/src/pycraftcore/repository/adapter/no_sql/mongodb}/__init__.py +0 -0
  138. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/no_sql/mongodb/adapter.py +0 -0
  139. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/no_sql/mongodb/factory.py +0 -0
  140. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/no_sql/mongodb/mapper.py +0 -0
  141. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/no_sql/mongodb/schema.py +0 -0
  142. {pycraftcore-2.0.0/src/pycraftcore/repository/adapter/sql/postgresql → pycraftcore-2.0.1/src/pycraftcore/repository/adapter/sql}/__init__.py +0 -0
  143. {pycraftcore-2.0.0/src/pycraftcore/resilient_http → pycraftcore-2.0.1/src/pycraftcore/repository/adapter/sql/postgresql}/__init__.py +0 -0
  144. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/postgresql/adapter.py +0 -0
  145. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/postgresql/factory.py +0 -0
  146. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/postgresql/mapper.py +0 -0
  147. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/postgresql/schema.py +0 -0
  148. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/sqlite/__init__.py +0 -0
  149. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/sqlite/adapter.py +0 -0
  150. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/sqlite/factory.py +0 -0
  151. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/sqlite/mapper.py +0 -0
  152. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/adapter/sql/sqlite/schema.py +0 -0
  153. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/port/__init__.py +0 -0
  154. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/repository/port/repository.py +0 -0
  155. {pycraftcore-2.0.0/src/pycraftcore/retry → pycraftcore-2.0.1/src/pycraftcore/resilient_http}/__init__.py +0 -0
  156. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/adapter/__init__.py +0 -0
  157. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/adapter/resilient_client.py +0 -0
  158. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/adapter/resilient_http_client_factory.py +0 -0
  159. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/adapter/resilient_transport.py +0 -0
  160. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/adapter/resilient_transport_factory.py +0 -0
  161. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/configuration/__init__.py +0 -0
  162. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/configuration/resilient_http_configuration.py +0 -0
  163. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/port/__init__.py +0 -0
  164. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/resilient_http/port/async_resilient_http_client.py +0 -0
  165. {pycraftcore-2.0.0/src/pycraftcore/serializer → pycraftcore-2.0.1/src/pycraftcore/retry}/__init__.py +0 -0
  166. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/retry/adapter/__init__.py +0 -0
  167. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/retry/adapter/tenacity_retry_policy.py +0 -0
  168. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/retry/configuration/__init__.py +0 -0
  169. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/retry/configuration/retry_configuration.py +0 -0
  170. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/retry/port/__init__.py +0 -0
  171. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/retry/port/retry.py +0 -0
  172. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/__init__.py +0 -0
  173. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/adapter/__init__.py +0 -0
  174. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/adapter/python/__init__.py +0 -0
  175. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/adapter/python/adapter.py +0 -0
  176. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/adapter/python/factory.py +0 -0
  177. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/adapter/python/python_runner_template.py +0 -0
  178. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/port/__init__.py +0 -0
  179. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/port/code.py +0 -0
  180. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/schema/__init__.py +0 -0
  181. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/schema/code_result.py +0 -0
  182. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/schema/code_stdout.py +0 -0
  183. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/schema/host_bridge.py +0 -0
  184. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/runtime/schema/safe_code_settings.py +0 -0
  185. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/serializer/adapter/__init__.py +0 -0
  186. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/serializer/adapter/binary_serializer.py +0 -0
  187. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/serializer/adapter/dictionary_serializer.py +0 -0
  188. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/serializer/adapter/json_serializer.py +0 -0
  189. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/serializer/port/__init__.py +0 -0
  190. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/serializer/port/serializer.py +0 -0
  191. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/__init__.py +0 -0
  192. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/adapter/__init__.py +0 -0
  193. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/adapter/null_telemetry.py +0 -0
  194. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/adapter/open_telemetry.py +0 -0
  195. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/adapter/open_telemetry_logger_provider.py +0 -0
  196. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/adapter/open_telemetry_meter_provider.py +0 -0
  197. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/adapter/open_telemetry_provider.py +0 -0
  198. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/adapter/open_telemetry_tracer.py +0 -0
  199. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/port/__init__.py +0 -0
  200. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/port/logger_provider.py +0 -0
  201. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/port/meter_provider.py +0 -0
  202. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/port/telemetry.py +0 -0
  203. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/port/trace_provider.py +0 -0
  204. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore/telemetry/port/tracer.py +0 -0
  205. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore.egg-info/dependency_links.txt +0 -0
  206. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore.egg-info/requires.txt +0 -0
  207. {pycraftcore-2.0.0 → pycraftcore-2.0.1}/src/pycraftcore.egg-info/top_level.txt +0 -0
@@ -0,0 +1,326 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycraftcore
3
+ Version: 2.0.1
4
+ Summary: Provides reusable technical infrastructure and adapter implementations for Python applications
5
+ Author-email: Toufik Saouchi <toufik.saouchi@gmail.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/s-toufik/pycraftcore
8
+ Classifier: Programming Language :: Python :: 3
9
+ Classifier: Operating System :: OS Independent
10
+ Requires-Python: >=3.14
11
+ Description-Content-Type: text/markdown
12
+ License-File: LICENSE
13
+ Requires-Dist: numpy
14
+ Requires-Dist: aiohttp
15
+ Requires-Dist: orjson
16
+ Requires-Dist: pyyaml
17
+ Requires-Dist: omegaconf
18
+ Requires-Dist: opentelemetry-api
19
+ Requires-Dist: opentelemetry-sdk
20
+ Requires-Dist: opentelemetry-exporter-otlp
21
+ Requires-Dist: pyinstrument
22
+ Requires-Dist: asyncpg
23
+ Requires-Dist: pydantic>=2
24
+ Requires-Dist: python-dotenv
25
+ Requires-Dist: msgpack>=1.1.2
26
+ Requires-Dist: scipy>=1.17.1
27
+ Requires-Dist: httpx
28
+ Requires-Dist: tenacity
29
+ Requires-Dist: aiobreaker
30
+ Requires-Dist: aiosqlite
31
+ Requires-Dist: sqlglot
32
+ Requires-Dist: starlette
33
+ Requires-Dist: pandas
34
+ Requires-Dist: matplotlib
35
+ Requires-Dist: pymongo<4.18,>=4.9
36
+ Requires-Dist: psycopg[binary]
37
+ Requires-Dist: psycopg-pool
38
+ Requires-Dist: opentelemetry-instrumentation-logging>=0.65b0
39
+ Dynamic: license-file
40
+
41
+ # pycraftcore
42
+
43
+ The shared technical foundation of the homelab's Python services: configuration, logging, telemetry, resilient HTTP, database repositories, safe SQL and a sandbox for running untrusted Python. Small, swappable building blocks rather than a framework — every module is an interface (a port) plus the implementations behind it (adapters).
44
+
45
+ **[Using pycraftcore](#using-pycraftcore)** — install it and use each module in your service.
46
+ **[Working on pycraftcore](#working-on-pycraftcore)** — how it is organised and how to release it.
47
+
48
+ ```text
49
+ your service (agent-orchestrator, agent-toolbox, …)
50
+ │
51
+ ▼
52
+ ┌─ pycraftcore ─────────────────────────────────────────────────────────────┐
53
+ │ │
54
+ │ set up application_configuration · logger · telemetry · context │
55
+ │ talk out http · resilient_http (retry + circuit_breaker) │
56
+ │ store repository: SQLite · PostgreSQL · MongoDB │
57
+ │ run safely query_language (read-only SQL) · runtime (Python sandbox) │
58
+ │ utilities file_handler · serializer · computation_engine · profiler │
59
+ │ … new modules, same port + adapter shape │
60
+ │ │
61
+ └───────────────────────────────────────────────────────────────────────────┘
62
+ ```
63
+
64
+ ---
65
+
66
+ ## Using pycraftcore
67
+
68
+ ### Install
69
+
70
+ ```bash
71
+ uv add pycraftcore # or: pip install pycraftcore
72
+ ```
73
+
74
+ Requires Python 3.14. Development builds are published to TestPyPI (`pip install -i https://test.pypi.org/simple/ pycraftcore`); use them for testing only.
75
+
76
+ ### What's inside
77
+
78
+ Each module stands on its own: use the ones you need. The modules today (new ones follow the same shape, see [Working on pycraftcore](#working-on-pycraftcore)):
79
+
80
+ | Package | What it gives you |
81
+ |---|---|
82
+ | `application_configuration` | YAML configuration (connectors and operations) driven by environment variables, loaded into typed objects |
83
+ | `authentication` | The auth models a connector can use: none, basic, token |
84
+ | `logger` | One log format for the whole process, with the request id on every line |
85
+ | `context` | `request_id_context`, the request id shared by logs, telemetry and outgoing calls |
86
+ | `telemetry` | OpenTelemetry traces, metrics and logs, exported to an OTLP collector (or nothing) |
87
+ | `http` | Async HTTP clients (httpx, aiohttp), request-id and request-context middlewares, retry policies |
88
+ | `resilient_http` | An HTTP transport with retries and a circuit breaker built in |
89
+ | `retry`, `circuit_breaker` | The two policies on their own, for any async call |
90
+ | `repository` | Async repositories built from a database connector — today SQLite (pooled), PostgreSQL and MongoDB |
91
+ | `query_language` | Checks that a SQL statement is read-only, and translates between dialects, before it reaches a database |
92
+ | `runtime` | Runs untrusted Python in a separate process with memory, time and module limits; optionally lets it call your functions |
93
+ | `file_handler` | Read and write files by extension (today csv, json, yml, md, txt, svg); a new format is a new reader/writer pair |
94
+ | `serializer` | JSON, dict and binary (msgpack) serialisation of dataclasses |
95
+ | `computation_engine` | Numerical helpers: financial arithmetic, integration, interpolation |
96
+ | `profiler` | `@profiled`, an async profiling decorator |
97
+
98
+ ### Configuration
99
+
100
+ A service's configuration is a folder of YAML files, with values taken from environment variables (`${oc.env:NAME}`):
101
+
102
+ | Path | What it holds |
103
+ |---|---|
104
+ | `root.yml` | The environment and the list of connectors and operations in use |
105
+ | `<env>/connector/*.yml` | Where things are: APIs, databases, files, MCP servers, telemetry |
106
+ | `<env>/operation/*.yml` | What you do with them; each operation points at a connector |
107
+
108
+ ```yaml
109
+ # <env>/connector/api.yml
110
+ connector:
111
+ weather:
112
+ name: weather
113
+ type: api
114
+ base_url: https://<api-host>
115
+ timeout: 5
116
+ retry: 3
117
+ auth:
118
+ type: token
119
+ key_name: apikey
120
+ key_value: ${oc.env:WEATHER_API_KEY}
121
+ ```
122
+
123
+ ```yaml
124
+ # <env>/operation/weather.yml
125
+ operation:
126
+ forecast:
127
+ name: forecast
128
+ type: api
129
+ connector: ${connector.weather}
130
+ endpoint: /forecast
131
+ method: GET
132
+ parameters:
133
+ days: 3
134
+ ```
135
+
136
+ ```yaml
137
+ # root.yml
138
+ application_configuration:
139
+ env: ${oc.env:APP_ENV,debug}
140
+ run: async
141
+ connector:
142
+ api:
143
+ weather: ${connector.weather}
144
+ operation:
145
+ forecast: ${operation.forecast}
146
+ ```
147
+
148
+ ```python
149
+ from pathlib import Path
150
+
151
+ from pycraftcore.application_configuration.adapter import (
152
+ LoadApplicationConfiguration,
153
+ OmegaConfigurationReader,
154
+ )
155
+ from pycraftcore.application_configuration.enum import RunTypeEnvironment
156
+ from pycraftcore.logger.adapter import StandardLogger
157
+
158
+ reader = OmegaConfigurationReader(RunTypeEnvironment.debug, Path("config"))
159
+ config = LoadApplicationConfiguration(reader, StandardLogger()).load()
160
+
161
+ connector = config.connector.api("weather") # ApiConnector
162
+ operation = config.operation.api("forecast") # ApiOperation
163
+ ```
164
+
165
+ `config.connector` returns typed connectors per kind (`.api()`, `.database()`, `.file()`, `.mcp()`, `.telemetry()`) and `config.operation` typed operations (`.api()`, `.file()`); the `type` field of each entry decides which fields it takes.
166
+
167
+ ### Logging and the request id
168
+
169
+ ```python
170
+ from pycraftcore.logger import configure_logging
171
+ from pycraftcore.logger.adapter import StandardLogger
172
+
173
+ configure_logging("INFO")
174
+ logger = StandardLogger()
175
+ logger.info("booted")
176
+ ```
177
+
178
+ ```text
179
+ 2026-09-27 10:06:17.679 | INFO | 1bddfefb-… | controller:execute:51 - stream request accepted
180
+ ```
181
+
182
+ - The third column is `request_id_context`. In a web service, `RequestIDMiddleware` (`pycraftcore.http.middleware`) sets it from the `X-Request-ID` header; every line logged during that request shows it, `-` otherwise. Don't write it in your messages.
183
+ - Uvicorn logs use the same format; chatty libraries (httpx, mcp, pymongo…) stay at `WARNING`.
184
+ - Calling `configure_logging` again replaces the handler rather than adding a second one.
185
+
186
+ ### Telemetry
187
+
188
+ ```python
189
+ from pycraftcore.application_configuration.enum import RunTypeEnvironment
190
+ from pycraftcore.telemetry.adapter import OpenTelemetryProvider
191
+
192
+ telemetry = OpenTelemetryProvider(
193
+ service_name="my-service",
194
+ environment=RunTypeEnvironment.debug,
195
+ otlp_endpoint="<collector-host>:<port>", # None: nothing is exported
196
+ )
197
+ tracer = telemetry.tracer("my-service")
198
+ ```
199
+
200
+ Spans and log records carry the request id as an attribute, so logs and traces line up in Grafana.
201
+
202
+ ### Resilient HTTP
203
+
204
+ A transport that retries what is worth retrying and stops calling a backend that keeps failing:
205
+
206
+ ```python
207
+ from pycraftcore.circuit_breaker.configuration import CircuitBreakerSettings
208
+ from pycraftcore.http.configuration import HttpClientSettings, LimitsSettings
209
+ from pycraftcore.http.policy.http_error_policy import is_business_error, is_retryable
210
+ from pycraftcore.resilient_http.adapter import ResilientTransportFactory
211
+ from pycraftcore.resilient_http.configuration import ResilientHttpSettings
212
+ from pycraftcore.retry.configuration import RetrySettings
213
+
214
+ settings = ResilientHttpSettings(
215
+ http=HttpClientSettings(limits=LimitsSettings(timeout=10)),
216
+ retry=RetrySettings(
217
+ retry_count=3, retry_delay=1, max_retry_delay=20, should_retry=is_retryable
218
+ ),
219
+ circuit_breaker=CircuitBreakerSettings(
220
+ failure_threshold=3, recovery_timeout=30, is_excluded=is_business_error, name="weather"
221
+ ),
222
+ )
223
+ client = ResilientTransportFactory(
224
+ settings=settings, trace_manager=tracer, logger=logger
225
+ ).create_async_client()
226
+ ```
227
+
228
+ `client` is an `httpx.AsyncClient`: pass it to any library that accepts one.
229
+
230
+ ### Databases
231
+
232
+ Repositories are built from a database connector and share one interface, `AsyncRepository.execute(statement)`:
233
+
234
+ ```python
235
+ from pycraftcore.repository.adapter import SqliteRepositoryFactory, SqliteSettingsMapper
236
+
237
+ factory = SqliteRepositoryFactory(SqliteSettingsMapper(config.connector.database("users"))())
238
+ repository = await factory.connect()
239
+ rows = await repository.execute("SELECT id, email FROM users LIMIT 10")
240
+ await factory.disconnect()
241
+ ```
242
+
243
+ `PostgresRepositoryFactory` / `PostgresSettingsMapper` and `MongoRepositoryFactory` / `MongoSettingsMapper` work the same way, and so will any database added later: a factory, a settings mapper and a repository behind the same `AsyncRepository` port.
244
+
245
+ ### Read-only SQL
246
+
247
+ ```python
248
+ from pycraftcore.query_language.adapter import SqlHandlerFactory
249
+
250
+ statement = SqlHandlerFactory()("SELECT * FROM users", dialect="sqlite").transpile()
251
+ ```
252
+
253
+ `transpile()` raises when the statement writes, deletes or changes the schema, so only reads reach the database.
254
+
255
+ ### Running untrusted Python
256
+
257
+ ```python
258
+ from pycraftcore.runtime.adapter import PythonSafeCodeFactory
259
+ from pycraftcore.runtime.schema import CodeResult, SafeCodeSettings
260
+
261
+ factory = PythonSafeCodeFactory(settings=SafeCodeSettings(code_timeout=30, max_memory_mb=256))
262
+ output = await factory(code="import math\nresult = math.sqrt(2)").execute()
263
+ print(CodeResult.from_stdout(output.stdout).value) # 1.4142135623730951
264
+ ```
265
+
266
+ - The code runs in its own process; only the modules in `PYTHON_ALLOWLIST` can be imported (pandas, numpy, matplotlib, datetime…).
267
+ - `working_directory` in `SafeCodeSettings` confines file access to one folder.
268
+ - With a `HostBridgeServer`, the code can call functions of your service as if they were local — this is how agent-toolbox lets sandboxed code use its other tools.
269
+
270
+ ---
271
+
272
+ ## Working on pycraftcore
273
+
274
+ ### How a module is organised
275
+
276
+ Every package follows the same shape, so a new backend is a new adapter, never a change for the services that use the port:
277
+
278
+ ```text
279
+ pycraftcore/<module>/
280
+ ├── port/ Protocols the services depend on (AsyncRepository, Code, Logger…)
281
+ ├── adapter/ Implementations (SqliteRepository, PythonSafeCode, StandardLogger…)
282
+ ├── configuration/ Pydantic settings objects (RetrySettings, CircuitBreakerSettings…)
283
+ ├── schema/ Data passed across the port (CodeResult, SafeCodeSettings…)
284
+ └── enum/ Fixed choices (ConnectorType, HttpMethod…)
285
+ ```
286
+
287
+ Not every module needs every folder: add a folder when it has something to hold.
288
+
289
+ ### Design rules
290
+
291
+ - Services import the **port** and receive an adapter; they never construct a third-party client themselves.
292
+ - Adapters are thin: they translate between the port and the library, and keep library types out of the port.
293
+ - Settings are validated pydantic models; nothing reads environment variables except the configuration loader.
294
+ - Every public module is typed: `ty` runs in strict mode.
295
+
296
+ ### Adding a module or an adapter
297
+
298
+ 1. Define the protocol in `<module>/port/`.
299
+ 2. Implement it in `<module>/adapter/`, with its settings in `configuration/` and the data it exchanges in `schema/`.
300
+ 3. Export the public names from the package's `__init__.py`.
301
+ 4. Add tests under `tests/<module>/`, and the module to [What's inside](#whats-inside) — with a short example under Using pycraftcore if services will use it directly.
302
+
303
+ ### Tests and checks
304
+
305
+ ```bash
306
+ make install_dev # uv sync --group dev
307
+ make check # ruff + ty + pytest
308
+ make test
309
+ make lint / make fix / make format / make typecheck
310
+ uv run pytest --cov=pycraftcore --cov-report=term-missing # coverage per module
311
+ ```
312
+
313
+ Pre-commit runs the test suite before every commit and push:
314
+
315
+ ```bash
316
+ uv run pre-commit install --hook-type pre-commit --hook-type pre-push
317
+ ```
318
+
319
+ ### Releasing
320
+
321
+ 1. Bump `version` in `pyproject.toml`.
322
+ 2. `make build`.
323
+ 3. `make publish_dev` to TestPyPI (needs `TEST_PYPI_TOKEN`), try it in a service, then `make publish` to PyPI (needs `RELEASE_PYPI_TOKEN`).
324
+ 4. Raise the minimum version in the services that need the change (`pycraftcore>=…` in their `pyproject.toml`), then `uv lock --upgrade-package pycraftcore`.
325
+
326
+ To work on pycraftcore and a service at the same time, point the service at your checkout with an editable path dependency in its `pyproject.toml`: `pycraftcore = { path = "../pycraftcore", editable = true }` under `[tool.uv.sources]`.
@@ -0,0 +1,286 @@
1
+ # pycraftcore
2
+
3
+ The shared technical foundation of the homelab's Python services: configuration, logging, telemetry, resilient HTTP, database repositories, safe SQL and a sandbox for running untrusted Python. Small, swappable building blocks rather than a framework — every module is an interface (a port) plus the implementations behind it (adapters).
4
+
5
+ **[Using pycraftcore](#using-pycraftcore)** — install it and use each module in your service.
6
+ **[Working on pycraftcore](#working-on-pycraftcore)** — how it is organised and how to release it.
7
+
8
+ ```text
9
+ your service (agent-orchestrator, agent-toolbox, …)
10
+ │
11
+ ▼
12
+ ┌─ pycraftcore ─────────────────────────────────────────────────────────────┐
13
+ │ │
14
+ │ set up application_configuration · logger · telemetry · context │
15
+ │ talk out http · resilient_http (retry + circuit_breaker) │
16
+ │ store repository: SQLite · PostgreSQL · MongoDB │
17
+ │ run safely query_language (read-only SQL) · runtime (Python sandbox) │
18
+ │ utilities file_handler · serializer · computation_engine · profiler │
19
+ │ … new modules, same port + adapter shape │
20
+ │ │
21
+ └───────────────────────────────────────────────────────────────────────────┘
22
+ ```
23
+
24
+ ---
25
+
26
+ ## Using pycraftcore
27
+
28
+ ### Install
29
+
30
+ ```bash
31
+ uv add pycraftcore # or: pip install pycraftcore
32
+ ```
33
+
34
+ Requires Python 3.14. Development builds are published to TestPyPI (`pip install -i https://test.pypi.org/simple/ pycraftcore`); use them for testing only.
35
+
36
+ ### What's inside
37
+
38
+ Each module stands on its own: use the ones you need. The modules today (new ones follow the same shape, see [Working on pycraftcore](#working-on-pycraftcore)):
39
+
40
+ | Package | What it gives you |
41
+ |---|---|
42
+ | `application_configuration` | YAML configuration (connectors and operations) driven by environment variables, loaded into typed objects |
43
+ | `authentication` | The auth models a connector can use: none, basic, token |
44
+ | `logger` | One log format for the whole process, with the request id on every line |
45
+ | `context` | `request_id_context`, the request id shared by logs, telemetry and outgoing calls |
46
+ | `telemetry` | OpenTelemetry traces, metrics and logs, exported to an OTLP collector (or nothing) |
47
+ | `http` | Async HTTP clients (httpx, aiohttp), request-id and request-context middlewares, retry policies |
48
+ | `resilient_http` | An HTTP transport with retries and a circuit breaker built in |
49
+ | `retry`, `circuit_breaker` | The two policies on their own, for any async call |
50
+ | `repository` | Async repositories built from a database connector — today SQLite (pooled), PostgreSQL and MongoDB |
51
+ | `query_language` | Checks that a SQL statement is read-only, and translates between dialects, before it reaches a database |
52
+ | `runtime` | Runs untrusted Python in a separate process with memory, time and module limits; optionally lets it call your functions |
53
+ | `file_handler` | Read and write files by extension (today csv, json, yml, md, txt, svg); a new format is a new reader/writer pair |
54
+ | `serializer` | JSON, dict and binary (msgpack) serialisation of dataclasses |
55
+ | `computation_engine` | Numerical helpers: financial arithmetic, integration, interpolation |
56
+ | `profiler` | `@profiled`, an async profiling decorator |
57
+
58
+ ### Configuration
59
+
60
+ A service's configuration is a folder of YAML files, with values taken from environment variables (`${oc.env:NAME}`):
61
+
62
+ | Path | What it holds |
63
+ |---|---|
64
+ | `root.yml` | The environment and the list of connectors and operations in use |
65
+ | `<env>/connector/*.yml` | Where things are: APIs, databases, files, MCP servers, telemetry |
66
+ | `<env>/operation/*.yml` | What you do with them; each operation points at a connector |
67
+
68
+ ```yaml
69
+ # <env>/connector/api.yml
70
+ connector:
71
+ weather:
72
+ name: weather
73
+ type: api
74
+ base_url: https://<api-host>
75
+ timeout: 5
76
+ retry: 3
77
+ auth:
78
+ type: token
79
+ key_name: apikey
80
+ key_value: ${oc.env:WEATHER_API_KEY}
81
+ ```
82
+
83
+ ```yaml
84
+ # <env>/operation/weather.yml
85
+ operation:
86
+ forecast:
87
+ name: forecast
88
+ type: api
89
+ connector: ${connector.weather}
90
+ endpoint: /forecast
91
+ method: GET
92
+ parameters:
93
+ days: 3
94
+ ```
95
+
96
+ ```yaml
97
+ # root.yml
98
+ application_configuration:
99
+ env: ${oc.env:APP_ENV,debug}
100
+ run: async
101
+ connector:
102
+ api:
103
+ weather: ${connector.weather}
104
+ operation:
105
+ forecast: ${operation.forecast}
106
+ ```
107
+
108
+ ```python
109
+ from pathlib import Path
110
+
111
+ from pycraftcore.application_configuration.adapter import (
112
+ LoadApplicationConfiguration,
113
+ OmegaConfigurationReader,
114
+ )
115
+ from pycraftcore.application_configuration.enum import RunTypeEnvironment
116
+ from pycraftcore.logger.adapter import StandardLogger
117
+
118
+ reader = OmegaConfigurationReader(RunTypeEnvironment.debug, Path("config"))
119
+ config = LoadApplicationConfiguration(reader, StandardLogger()).load()
120
+
121
+ connector = config.connector.api("weather") # ApiConnector
122
+ operation = config.operation.api("forecast") # ApiOperation
123
+ ```
124
+
125
+ `config.connector` returns typed connectors per kind (`.api()`, `.database()`, `.file()`, `.mcp()`, `.telemetry()`) and `config.operation` typed operations (`.api()`, `.file()`); the `type` field of each entry decides which fields it takes.
126
+
127
+ ### Logging and the request id
128
+
129
+ ```python
130
+ from pycraftcore.logger import configure_logging
131
+ from pycraftcore.logger.adapter import StandardLogger
132
+
133
+ configure_logging("INFO")
134
+ logger = StandardLogger()
135
+ logger.info("booted")
136
+ ```
137
+
138
+ ```text
139
+ 2026-09-27 10:06:17.679 | INFO | 1bddfefb-… | controller:execute:51 - stream request accepted
140
+ ```
141
+
142
+ - The third column is `request_id_context`. In a web service, `RequestIDMiddleware` (`pycraftcore.http.middleware`) sets it from the `X-Request-ID` header; every line logged during that request shows it, `-` otherwise. Don't write it in your messages.
143
+ - Uvicorn logs use the same format; chatty libraries (httpx, mcp, pymongo…) stay at `WARNING`.
144
+ - Calling `configure_logging` again replaces the handler rather than adding a second one.
145
+
146
+ ### Telemetry
147
+
148
+ ```python
149
+ from pycraftcore.application_configuration.enum import RunTypeEnvironment
150
+ from pycraftcore.telemetry.adapter import OpenTelemetryProvider
151
+
152
+ telemetry = OpenTelemetryProvider(
153
+ service_name="my-service",
154
+ environment=RunTypeEnvironment.debug,
155
+ otlp_endpoint="<collector-host>:<port>", # None: nothing is exported
156
+ )
157
+ tracer = telemetry.tracer("my-service")
158
+ ```
159
+
160
+ Spans and log records carry the request id as an attribute, so logs and traces line up in Grafana.
161
+
162
+ ### Resilient HTTP
163
+
164
+ A transport that retries what is worth retrying and stops calling a backend that keeps failing:
165
+
166
+ ```python
167
+ from pycraftcore.circuit_breaker.configuration import CircuitBreakerSettings
168
+ from pycraftcore.http.configuration import HttpClientSettings, LimitsSettings
169
+ from pycraftcore.http.policy.http_error_policy import is_business_error, is_retryable
170
+ from pycraftcore.resilient_http.adapter import ResilientTransportFactory
171
+ from pycraftcore.resilient_http.configuration import ResilientHttpSettings
172
+ from pycraftcore.retry.configuration import RetrySettings
173
+
174
+ settings = ResilientHttpSettings(
175
+ http=HttpClientSettings(limits=LimitsSettings(timeout=10)),
176
+ retry=RetrySettings(
177
+ retry_count=3, retry_delay=1, max_retry_delay=20, should_retry=is_retryable
178
+ ),
179
+ circuit_breaker=CircuitBreakerSettings(
180
+ failure_threshold=3, recovery_timeout=30, is_excluded=is_business_error, name="weather"
181
+ ),
182
+ )
183
+ client = ResilientTransportFactory(
184
+ settings=settings, trace_manager=tracer, logger=logger
185
+ ).create_async_client()
186
+ ```
187
+
188
+ `client` is an `httpx.AsyncClient`: pass it to any library that accepts one.
189
+
190
+ ### Databases
191
+
192
+ Repositories are built from a database connector and share one interface, `AsyncRepository.execute(statement)`:
193
+
194
+ ```python
195
+ from pycraftcore.repository.adapter import SqliteRepositoryFactory, SqliteSettingsMapper
196
+
197
+ factory = SqliteRepositoryFactory(SqliteSettingsMapper(config.connector.database("users"))())
198
+ repository = await factory.connect()
199
+ rows = await repository.execute("SELECT id, email FROM users LIMIT 10")
200
+ await factory.disconnect()
201
+ ```
202
+
203
+ `PostgresRepositoryFactory` / `PostgresSettingsMapper` and `MongoRepositoryFactory` / `MongoSettingsMapper` work the same way, and so will any database added later: a factory, a settings mapper and a repository behind the same `AsyncRepository` port.
204
+
205
+ ### Read-only SQL
206
+
207
+ ```python
208
+ from pycraftcore.query_language.adapter import SqlHandlerFactory
209
+
210
+ statement = SqlHandlerFactory()("SELECT * FROM users", dialect="sqlite").transpile()
211
+ ```
212
+
213
+ `transpile()` raises when the statement writes, deletes or changes the schema, so only reads reach the database.
214
+
215
+ ### Running untrusted Python
216
+
217
+ ```python
218
+ from pycraftcore.runtime.adapter import PythonSafeCodeFactory
219
+ from pycraftcore.runtime.schema import CodeResult, SafeCodeSettings
220
+
221
+ factory = PythonSafeCodeFactory(settings=SafeCodeSettings(code_timeout=30, max_memory_mb=256))
222
+ output = await factory(code="import math\nresult = math.sqrt(2)").execute()
223
+ print(CodeResult.from_stdout(output.stdout).value) # 1.4142135623730951
224
+ ```
225
+
226
+ - The code runs in its own process; only the modules in `PYTHON_ALLOWLIST` can be imported (pandas, numpy, matplotlib, datetime…).
227
+ - `working_directory` in `SafeCodeSettings` confines file access to one folder.
228
+ - With a `HostBridgeServer`, the code can call functions of your service as if they were local — this is how agent-toolbox lets sandboxed code use its other tools.
229
+
230
+ ---
231
+
232
+ ## Working on pycraftcore
233
+
234
+ ### How a module is organised
235
+
236
+ Every package follows the same shape, so a new backend is a new adapter, never a change for the services that use the port:
237
+
238
+ ```text
239
+ pycraftcore/<module>/
240
+ ├── port/ Protocols the services depend on (AsyncRepository, Code, Logger…)
241
+ ├── adapter/ Implementations (SqliteRepository, PythonSafeCode, StandardLogger…)
242
+ ├── configuration/ Pydantic settings objects (RetrySettings, CircuitBreakerSettings…)
243
+ ├── schema/ Data passed across the port (CodeResult, SafeCodeSettings…)
244
+ └── enum/ Fixed choices (ConnectorType, HttpMethod…)
245
+ ```
246
+
247
+ Not every module needs every folder: add a folder when it has something to hold.
248
+
249
+ ### Design rules
250
+
251
+ - Services import the **port** and receive an adapter; they never construct a third-party client themselves.
252
+ - Adapters are thin: they translate between the port and the library, and keep library types out of the port.
253
+ - Settings are validated pydantic models; nothing reads environment variables except the configuration loader.
254
+ - Every public module is typed: `ty` runs in strict mode.
255
+
256
+ ### Adding a module or an adapter
257
+
258
+ 1. Define the protocol in `<module>/port/`.
259
+ 2. Implement it in `<module>/adapter/`, with its settings in `configuration/` and the data it exchanges in `schema/`.
260
+ 3. Export the public names from the package's `__init__.py`.
261
+ 4. Add tests under `tests/<module>/`, and the module to [What's inside](#whats-inside) — with a short example under Using pycraftcore if services will use it directly.
262
+
263
+ ### Tests and checks
264
+
265
+ ```bash
266
+ make install_dev # uv sync --group dev
267
+ make check # ruff + ty + pytest
268
+ make test
269
+ make lint / make fix / make format / make typecheck
270
+ uv run pytest --cov=pycraftcore --cov-report=term-missing # coverage per module
271
+ ```
272
+
273
+ Pre-commit runs the test suite before every commit and push:
274
+
275
+ ```bash
276
+ uv run pre-commit install --hook-type pre-commit --hook-type pre-push
277
+ ```
278
+
279
+ ### Releasing
280
+
281
+ 1. Bump `version` in `pyproject.toml`.
282
+ 2. `make build`.
283
+ 3. `make publish_dev` to TestPyPI (needs `TEST_PYPI_TOKEN`), try it in a service, then `make publish` to PyPI (needs `RELEASE_PYPI_TOKEN`).
284
+ 4. Raise the minimum version in the services that need the change (`pycraftcore>=…` in their `pyproject.toml`), then `uv lock --upgrade-package pycraftcore`.
285
+
286
+ To work on pycraftcore and a service at the same time, point the service at your checkout with an editable path dependency in its `pyproject.toml`: `pycraftcore = { path = "../pycraftcore", editable = true }` under `[tool.uv.sources]`.
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "pycraftcore"
7
- version = "2.0.0"
7
+ version = "2.0.1"
8
8
  authors = [
9
9
  { name = "Toufik Saouchi", email = "toufik.saouchi@gmail.com" },
10
10
  ]
@@ -4,6 +4,8 @@ from pycraftcore.file_handler.adapter.extension.json.json_reader import JsonFile
4
4
  from pycraftcore.file_handler.adapter.extension.json.json_writer import JsonFileWriter
5
5
  from pycraftcore.file_handler.adapter.extension.markdown.markdown_reader import MarkdownFileReader
6
6
  from pycraftcore.file_handler.adapter.extension.markdown.markdown_writer import MarkdownFileWriter
7
+ from pycraftcore.file_handler.adapter.extension.svg.svg_reader import SvgFileReader
8
+ from pycraftcore.file_handler.adapter.extension.svg.svg_writer import SvgFileWriter
7
9
  from pycraftcore.file_handler.adapter.extension.txt.txt_reader import TxtFileReader
8
10
  from pycraftcore.file_handler.adapter.extension.txt.txt_writer import TxtFileWriter
9
11
  from pycraftcore.file_handler.adapter.extension.yml.yml_reader import YmlFileReader
@@ -19,6 +21,8 @@ __all__ = [
19
21
  "CsvFileWriter",
20
22
  "MarkdownFileWriter",
21
23
  "MarkdownFileReader",
24
+ "SvgFileWriter",
25
+ "SvgFileReader",
22
26
  "TxtFileWriter",
23
27
  "TxtFileReader",
24
28
  ]
@@ -0,0 +1,8 @@
1
+ from pycraftcore.file_handler.port import Reader
2
+
3
+
4
+ class SvgFileReader(Reader):
5
+ @staticmethod
6
+ def _read_full(file_path: str) -> str:
7
+ with open(file_path, encoding="utf-8") as file:
8
+ return file.read()
@@ -0,0 +1,8 @@
1
+ from pycraftcore.file_handler.port import Writer
2
+
3
+
4
+ class SvgFileWriter(Writer):
5
+ @staticmethod
6
+ def write(file_path: str, data: str) -> None:
7
+ with open(file_path, "w", encoding="utf-8") as file:
8
+ file.write(data)
@@ -4,6 +4,8 @@ from pycraftcore.file_handler.adapter.extension.json.json_reader import JsonFile
4
4
  from pycraftcore.file_handler.adapter.extension.json.json_writer import JsonFileWriter
5
5
  from pycraftcore.file_handler.adapter.extension.markdown.markdown_reader import MarkdownFileReader
6
6
  from pycraftcore.file_handler.adapter.extension.markdown.markdown_writer import MarkdownFileWriter
7
+ from pycraftcore.file_handler.adapter.extension.svg.svg_reader import SvgFileReader
8
+ from pycraftcore.file_handler.adapter.extension.svg.svg_writer import SvgFileWriter
7
9
  from pycraftcore.file_handler.adapter.extension.txt.txt_reader import TxtFileReader
8
10
  from pycraftcore.file_handler.adapter.extension.txt.txt_writer import TxtFileWriter
9
11
  from pycraftcore.file_handler.adapter.extension.yml.yml_reader import YmlFileReader
@@ -23,6 +25,7 @@ strategy = FileHandlerStrategy(
23
25
  "csv": {"reader": CsvFileReader(), "writer": CsvFileWriter()},
24
26
  "md": {"reader": MarkdownFileReader(), "writer": MarkdownFileWriter()},
25
27
  "txt": {"reader": TxtFileReader(), "writer": TxtFileWriter()},
28
+ "svg": {"reader": SvgFileReader(), "writer": SvgFileWriter()},
26
29
  }
27
30
  )
28
31