clear-skies 2.0.12__tar.gz → 2.0.14__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.

Potentially problematic release.


This version of clear-skies might be problematic. Click here for more details.

Files changed (280) hide show
  1. {clear_skies-2.0.12 → clear_skies-2.0.14}/CHANGELOG.md +19 -0
  2. clear_skies-2.0.14/LATEST_CHANGELOG.md +5 -0
  3. {clear_skies-2.0.12 → clear_skies-2.0.14}/PKG-INFO +1 -1
  4. {clear_skies-2.0.12 → clear_skies-2.0.14}/pyproject.toml +1 -1
  5. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/callable_config.py +1 -1
  6. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/contexts/wsgi.py +1 -3
  7. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/contexts/wsgi_ref.py +1 -1
  8. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/di.py +2 -1
  9. clear_skies-2.0.14/src/clearskies/di/inject/akeyless_sdk.py +15 -0
  10. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/wsgi.py +1 -0
  11. clear_skies-2.0.14/src/clearskies/secrets/akeyless.py +529 -0
  12. clear_skies-2.0.14/src/clearskies/secrets/exceptions/__init__.py +7 -0
  13. clear_skies-2.0.14/src/clearskies/secrets/exceptions/not_found_error.py +2 -0
  14. clear_skies-2.0.14/src/clearskies/secrets/exceptions/permissions_error.py +2 -0
  15. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/secrets/secrets.py +0 -5
  16. {clear_skies-2.0.12 → clear_skies-2.0.14}/uv.lock +1 -1
  17. clear_skies-2.0.12/LATEST_CHANGELOG.md +0 -6
  18. clear_skies-2.0.12/src/clearskies/secrets/akeyless.py +0 -187
  19. clear_skies-2.0.12/src/clearskies/secrets/exceptions/__init__.py +0 -1
  20. clear_skies-2.0.12/src/clearskies/secrets/exceptions/not_found.py +0 -2
  21. {clear_skies-2.0.12 → clear_skies-2.0.14}/.editorconfig +0 -0
  22. {clear_skies-2.0.12 → clear_skies-2.0.14}/.github/workflows/create-version.yaml +0 -0
  23. {clear_skies-2.0.12 → clear_skies-2.0.14}/.github/workflows/docs.yaml +0 -0
  24. {clear_skies-2.0.12 → clear_skies-2.0.14}/.github/workflows/run-tests.yml +0 -0
  25. {clear_skies-2.0.12 → clear_skies-2.0.14}/.github/workflows/tests-matrix.yaml +0 -0
  26. {clear_skies-2.0.12 → clear_skies-2.0.14}/.github/workflows/tests.yaml +0 -0
  27. {clear_skies-2.0.12 → clear_skies-2.0.14}/.gitignore +0 -0
  28. {clear_skies-2.0.12 → clear_skies-2.0.14}/.pre-commit-config.yaml +0 -0
  29. {clear_skies-2.0.12 → clear_skies-2.0.14}/.python-version +0 -0
  30. {clear_skies-2.0.12 → clear_skies-2.0.14}/.vscode/extensions.json +0 -0
  31. {clear_skies-2.0.12 → clear_skies-2.0.14}/.vscode/settings.json +0 -0
  32. {clear_skies-2.0.12 → clear_skies-2.0.14}/LICENSE +0 -0
  33. {clear_skies-2.0.12 → clear_skies-2.0.14}/README.md +0 -0
  34. {clear_skies-2.0.12 → clear_skies-2.0.14}/build.sh +0 -0
  35. {clear_skies-2.0.12 → clear_skies-2.0.14}/cliff.toml +0 -0
  36. {clear_skies-2.0.12 → clear_skies-2.0.14}/ruff.toml +0 -0
  37. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/__init__.py +0 -0
  38. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/action.py +0 -0
  39. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/authentication/__init__.py +0 -0
  40. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/authentication/authentication.py +0 -0
  41. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/authentication/authorization.py +0 -0
  42. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/authentication/authorization_pass_through.py +0 -0
  43. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/authentication/jwks.py +0 -0
  44. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/authentication/public.py +0 -0
  45. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/authentication/secret_bearer.py +0 -0
  46. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/__init__.py +0 -0
  47. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/__init__.py +0 -0
  48. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/__init__.py +0 -0
  49. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/oai3_json.py +0 -0
  50. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +0 -0
  51. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/parameter.py +0 -0
  52. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/request.py +0 -0
  53. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/response.py +0 -0
  54. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/schema/__init__.py +0 -0
  55. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/schema/array.py +0 -0
  56. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/schema/default.py +0 -0
  57. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/schema/enum.py +0 -0
  58. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/schema/object.py +0 -0
  59. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/formats/oai3_json/test.json +0 -0
  60. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/py.typed +0 -0
  61. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/request/__init__.py +0 -0
  62. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/request/header.py +0 -0
  63. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/request/json_body.py +0 -0
  64. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/request/parameter.py +0 -0
  65. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/request/request.py +0 -0
  66. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/request/url_parameter.py +0 -0
  67. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/request/url_path.py +0 -0
  68. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/response/__init__.py +0 -0
  69. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/response/response.py +0 -0
  70. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/__init__.py +0 -0
  71. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/array.py +0 -0
  72. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/base64.py +0 -0
  73. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/boolean.py +0 -0
  74. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/date.py +0 -0
  75. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/datetime.py +0 -0
  76. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/double.py +0 -0
  77. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/enum.py +0 -0
  78. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/integer.py +0 -0
  79. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/long.py +0 -0
  80. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/number.py +0 -0
  81. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/object.py +0 -0
  82. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/password.py +0 -0
  83. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/schema.py +0 -0
  84. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/autodoc/schema/string.py +0 -0
  85. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/backends/__init__.py +0 -0
  86. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/backends/api_backend.py +0 -0
  87. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/backends/backend.py +0 -0
  88. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/backends/cursor_backend.py +0 -0
  89. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/backends/memory_backend.py +0 -0
  90. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/backends/secrets_backend.py +0 -0
  91. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/column.py +0 -0
  92. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/__init__.py +0 -0
  93. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/audit.py +0 -0
  94. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/belongs_to_id.py +0 -0
  95. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/belongs_to_model.py +0 -0
  96. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/belongs_to_self.py +0 -0
  97. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/boolean.py +0 -0
  98. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/category_tree.py +0 -0
  99. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/category_tree_ancestors.py +0 -0
  100. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/category_tree_children.py +0 -0
  101. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/category_tree_descendants.py +0 -0
  102. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/created.py +0 -0
  103. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/created_by_authorization_data.py +0 -0
  104. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/created_by_header.py +0 -0
  105. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/created_by_ip.py +0 -0
  106. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/created_by_routing_data.py +0 -0
  107. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/created_by_user_agent.py +0 -0
  108. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/date.py +0 -0
  109. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/datetime.py +0 -0
  110. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/email.py +0 -0
  111. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/float.py +0 -0
  112. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/has_many.py +0 -0
  113. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/has_many_self.py +0 -0
  114. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/has_one.py +0 -0
  115. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/integer.py +0 -0
  116. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/json.py +0 -0
  117. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/many_to_many_ids.py +0 -0
  118. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/many_to_many_ids_with_data.py +0 -0
  119. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/many_to_many_models.py +0 -0
  120. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/many_to_many_pivots.py +0 -0
  121. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/phone.py +0 -0
  122. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/select.py +0 -0
  123. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/string.py +0 -0
  124. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/timestamp.py +0 -0
  125. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/updated.py +0 -0
  126. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/columns/uuid.py +0 -0
  127. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/README.md +0 -0
  128. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/__init__.py +0 -0
  129. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/actions.py +0 -0
  130. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/any.py +0 -0
  131. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/any_dict.py +0 -0
  132. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/any_dict_or_callable.py +0 -0
  133. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/authentication.py +0 -0
  134. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/authorization.py +0 -0
  135. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/boolean.py +0 -0
  136. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/boolean_or_callable.py +0 -0
  137. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/columns.py +0 -0
  138. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/conditions.py +0 -0
  139. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/config.py +0 -0
  140. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/datetime.py +0 -0
  141. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/datetime_or_callable.py +0 -0
  142. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/email.py +0 -0
  143. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/email_list.py +0 -0
  144. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/email_list_or_callable.py +0 -0
  145. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/email_or_email_list_or_callable.py +0 -0
  146. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/endpoint.py +0 -0
  147. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/endpoint_list.py +0 -0
  148. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/float.py +0 -0
  149. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/float_or_callable.py +0 -0
  150. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/headers.py +0 -0
  151. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/integer.py +0 -0
  152. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/integer_or_callable.py +0 -0
  153. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/joins.py +0 -0
  154. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/list_any_dict.py +0 -0
  155. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/list_any_dict_or_callable.py +0 -0
  156. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/model_class.py +0 -0
  157. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/model_column.py +0 -0
  158. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/model_columns.py +0 -0
  159. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/model_destination_name.py +0 -0
  160. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/model_to_id_column.py +0 -0
  161. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/readable_model_column.py +0 -0
  162. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/readable_model_columns.py +0 -0
  163. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/schema.py +0 -0
  164. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/searchable_model_columns.py +0 -0
  165. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/security_headers.py +0 -0
  166. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/select.py +0 -0
  167. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/select_list.py +0 -0
  168. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/string.py +0 -0
  169. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/string_dict.py +0 -0
  170. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/string_list.py +0 -0
  171. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/string_list_or_callable.py +0 -0
  172. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/string_or_callable.py +0 -0
  173. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/timedelta.py +0 -0
  174. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/timezone.py +0 -0
  175. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/url.py +0 -0
  176. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/validators.py +0 -0
  177. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/writeable_model_column.py +0 -0
  178. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configs/writeable_model_columns.py +0 -0
  179. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/configurable.py +0 -0
  180. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/contexts/__init__.py +0 -0
  181. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/contexts/cli.py +0 -0
  182. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/contexts/context.py +0 -0
  183. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/decorators.py +0 -0
  184. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/decorators.pyi +0 -0
  185. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/__init__.py +0 -0
  186. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/additional_config.py +0 -0
  187. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/additional_config_auto_import.py +0 -0
  188. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/__init__.py +0 -0
  189. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/by_class.py +0 -0
  190. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/by_name.py +0 -0
  191. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/di.py +0 -0
  192. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/environment.py +0 -0
  193. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/input_output.py +0 -0
  194. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/now.py +0 -0
  195. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/requests.py +0 -0
  196. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/secrets.py +0 -0
  197. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/utcnow.py +0 -0
  198. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/inject/uuid.py +0 -0
  199. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/injectable.py +0 -0
  200. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/injectable_properties.py +0 -0
  201. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/test_module/__init__.py +0 -0
  202. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/test_module/another_module/__init__.py +0 -0
  203. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/di/test_module/module_class.py +0 -0
  204. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/end.py +0 -0
  205. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoint.py +0 -0
  206. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoint_group.py +0 -0
  207. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/__init__.py +0 -0
  208. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/advanced_search.py +0 -0
  209. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/callable.py +0 -0
  210. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/create.py +0 -0
  211. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/delete.py +0 -0
  212. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/get.py +0 -0
  213. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/health_check.py +0 -0
  214. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/list.py +0 -0
  215. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/restful_api.py +0 -0
  216. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/schema.py +0 -0
  217. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/simple_search.py +0 -0
  218. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/endpoints/update.py +0 -0
  219. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/environment.py +0 -0
  220. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/__init__.py +0 -0
  221. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/authentication.py +0 -0
  222. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/authorization.py +0 -0
  223. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/client_error.py +0 -0
  224. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/input_errors.py +0 -0
  225. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/missing_dependency.py +0 -0
  226. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/moved_permanently.py +0 -0
  227. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/moved_temporarily.py +0 -0
  228. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/exceptions/not_found.py +0 -0
  229. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/functional/__init__.py +0 -0
  230. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/functional/routing.py +0 -0
  231. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/functional/string.py +0 -0
  232. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/functional/validations.py +0 -0
  233. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/__init__.py +0 -0
  234. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/cli.py +0 -0
  235. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/exceptions/__init__.py +0 -0
  236. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/exceptions/cli_input_error.py +0 -0
  237. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/exceptions/cli_not_found.py +0 -0
  238. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/headers.py +0 -0
  239. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/input_output.py +0 -0
  240. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/programmatic.py +0 -0
  241. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/input_outputs/py.typed +0 -0
  242. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/model.py +0 -0
  243. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/py.typed +0 -0
  244. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/query/__init__.py +0 -0
  245. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/query/condition.py +0 -0
  246. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/query/join.py +0 -0
  247. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/query/query.py +0 -0
  248. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/query/sort.py +0 -0
  249. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/schema.py +0 -0
  250. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/secrets/__init__.py +0 -0
  251. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/secrets/additional_configs/__init__.py +0 -0
  252. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +0 -0
  253. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -0
  254. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_header.py +0 -0
  255. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_headers/__init__.py +0 -0
  256. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_headers/cache_control.py +0 -0
  257. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_headers/cors.py +0 -0
  258. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_headers/csp.py +0 -0
  259. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_headers/hsts.py +0 -0
  260. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_headers/x_content_type_options.py +0 -0
  261. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/security_headers/x_frame_options.py +0 -0
  262. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/test_base.py +0 -0
  263. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/typing.py +0 -0
  264. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validator.py +0 -0
  265. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/__init__.py +0 -0
  266. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/after_column.py +0 -0
  267. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/before_column.py +0 -0
  268. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/in_the_future.py +0 -0
  269. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/in_the_future_at_least.py +0 -0
  270. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/in_the_future_at_most.py +0 -0
  271. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/in_the_past.py +0 -0
  272. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/in_the_past_at_least.py +0 -0
  273. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/in_the_past_at_most.py +0 -0
  274. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/maximum_length.py +0 -0
  275. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/maximum_value.py +0 -0
  276. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/minimum_length.py +0 -0
  277. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/minimum_value.py +0 -0
  278. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/required.py +0 -0
  279. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/timedelta.py +0 -0
  280. {clear_skies-2.0.12 → clear_skies-2.0.14}/src/clearskies/validators/unique.py +0 -0
@@ -5,9 +5,26 @@ All notable changes to this project will be documented in this file.
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [2.0.14] - 2025-10-16
9
+
10
+ ### Added
11
+ - Add auto guess for akeyless secrets by @tnijboer in [#27](https://github.com/clearskies-py/clearskies/pull/27)
12
+
13
+ ## [2.0.13] - 2025-10-10
14
+
15
+ ### Changed
16
+ - Bump version to v2.0.13 by @github-actions[bot]
17
+ - Broken imports by @cmancone in [#25](https://github.com/clearskies-py/clearskies/pull/25)
18
+ - Broken imports by @cmancone
19
+
20
+ ### Fixed
21
+ - Callable be none by @cmancone in [#26](https://github.com/clearskies-py/clearskies/pull/26)
22
+ - Callable be none
23
+
8
24
  ## [2.0.12] - 2025-10-09
9
25
 
10
26
  ### Changed
27
+ - Bump version to v2.0.12 by @github-actions[bot]
11
28
  - Use dict for Header class by @cmancone in [#23](https://github.com/clearskies-py/clearskies/pull/23)
12
29
  - Make headers a dict
13
30
 
@@ -813,6 +830,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
813
830
  * @ made their first contribution
814
831
  * @tnijboer made their first contribution
815
832
  * @conormancone-cimpress made their first contribution
833
+ [2.0.14]: https://github.com/clearskies-py/clearskies/compare/v2.0.13..v2.0.14
834
+ [2.0.13]: https://github.com/clearskies-py/clearskies/compare/v2.0.12..v2.0.13
816
835
  [2.0.12]: https://github.com/clearskies-py/clearskies/compare/v2.0.11..v2.0.12
817
836
  [2.0.11]: https://github.com/clearskies-py/clearskies/compare/v2.0.10..v2.0.11
818
837
  [2.0.10]: https://github.com/clearskies-py/clearskies/compare/v2.0.9..v2.0.10
@@ -0,0 +1,5 @@
1
+ ## [2.0.14] - 2025-10-16
2
+
3
+ ### Added
4
+ - Add auto guess for akeyless secrets by @tnijboer in [#27](https://github.com/clearskies-py/clearskies/pull/27)
5
+
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: clear-skies
3
- Version: 2.0.12
3
+ Version: 2.0.14
4
4
  Summary: A framework for building backends in the cloud
5
5
  Project-URL: Documentation, https://clearskies.io/
6
6
  Project-URL: Repository, https://github.com/clearskies-py/clearskies
@@ -1,7 +1,7 @@
1
1
  [project]
2
2
  name = "clear-skies"
3
3
  description = "A framework for building backends in the cloud"
4
- version = "2.0.12"
4
+ version = "2.0.14"
5
5
  license = "MIT"
6
6
  readme = "./README.md"
7
7
  authors = [{name = "Conor Mancone", email = "cmancone@gmail.com"}]
@@ -7,7 +7,7 @@ from clearskies.configs import config
7
7
 
8
8
  class Callable(config.Config):
9
9
  def __set__(self, instance, value: CallableType):
10
- if not callable(value):
10
+ if value is not None and not callable(value):
11
11
  error_prefix = self._error_prefix(instance)
12
12
  raise TypeError(
13
13
  f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a Callable."
@@ -3,9 +3,7 @@ from __future__ import annotations
3
3
  from typing import TYPE_CHECKING
4
4
 
5
5
  from clearskies.contexts.context import Context
6
-
7
- if TYPE_CHECKING:
8
- from clearskies.input_outputs import Wsgi as WsgiInputOutput
6
+ from clearskies.input_outputs import Wsgi as WsgiInputOutput
9
7
 
10
8
 
11
9
  class Wsgi(Context):
@@ -7,12 +7,12 @@ from wsgiref.simple_server import make_server
7
7
  from wsgiref.util import setup_testing_defaults
8
8
 
9
9
  from clearskies.contexts.context import Context
10
+ from clearskies.input_outputs import Wsgi as WsgiInputOutput
10
11
 
11
12
  if TYPE_CHECKING:
12
13
  from clearskies.di import AdditionalConfig
13
14
  from clearskies.endpoint import Endpoint
14
15
  from clearskies.endpoint_group import EndpointGroup
15
- from clearskies.input_outputs import Wsgi as WsgiInputOutput
16
16
 
17
17
 
18
18
  class WsgiRef(Context):
@@ -591,6 +591,7 @@ class Di:
591
591
  4. The Di class itself if it has a matching `provide_[name]` function (aka the builtins)
592
592
  """
593
593
  if name in self._prepared and cache:
594
+ self.inject_properties(self._prepared[name].__class__)
594
595
  return self._prepared[name]
595
596
 
596
597
  if name in self._bindings:
@@ -985,7 +986,7 @@ class Di:
985
986
  def provide_global_table_prefix(self):
986
987
  return ""
987
988
 
988
- def provide_akeyles_sdk(self):
989
+ def provide_akeyless_sdk(self):
989
990
  import akeyless # type: ignore[import-untyped]
990
991
 
991
992
  return akeyless
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from types import ModuleType
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class AkeylessSDK(Injectable):
9
+ def __init__(self, cache: bool = True):
10
+ self.cache = cache
11
+
12
+ def __get__(self, instance, parent) -> ModuleType:
13
+ if instance is None:
14
+ return self # type: ignore
15
+ return self._di.build_from_name("akeyless_sdk", cache=self.cache)
@@ -1,4 +1,5 @@
1
1
  from __future__ import annotations
2
+ from urllib.parse import parse_qs
2
3
 
3
4
  import json
4
5
  from typing import Callable
@@ -0,0 +1,529 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ import json
5
+ import logging
6
+ from types import ModuleType
7
+ from typing import TYPE_CHECKING, Any
8
+
9
+ from typing_extensions import override
10
+
11
+ from clearskies import configs, secrets
12
+ from clearskies.decorators import parameters_to_properties
13
+ from clearskies.di import inject
14
+ from clearskies.secrets.exceptions import PermissionsError
15
+
16
+ if TYPE_CHECKING:
17
+ from akeyless import ListItemsOutput, V2Api
18
+
19
+
20
+ class Akeyless(secrets.Secrets):
21
+ """
22
+ Backend for managing secrets using the Akeyless Vault.
23
+
24
+ This class provides integration with Akeyless vault services, allowing you to store, retrieve,
25
+ and manage secrets. It supports different types of secrets (static, dynamic, rotated) and
26
+ includes authentication mechanisms for AWS IAM, SAML, and JWT.
27
+ """
28
+
29
+ """
30
+ HTTP client for making API requests
31
+ """
32
+ requests = inject.Requests()
33
+
34
+ """
35
+ Environment configuration for retrieving environment variables
36
+ """
37
+ environment = inject.Environment()
38
+
39
+ """
40
+ The Akeyless SDK module injected by the dependency injection system
41
+ """
42
+ akeyless: ModuleType = inject.ByName("akeyless_sdk") # type: ignore
43
+
44
+ """
45
+ The access ID for the Akeyless service
46
+
47
+ This must match the pattern p-[0-9a-zA-Z]+ (e.g., "p-abc123")
48
+ """
49
+ access_id = configs.String(required=True, regexp=r"^p-[\d\w]+$")
50
+
51
+ """
52
+ The authentication method to use
53
+
54
+ Must be one of "aws_iam", "saml", or "jwt"
55
+ """
56
+ access_type = configs.Select(["aws_iam", "saml", "jwt"], required=True)
57
+
58
+ """
59
+ The Akeyless API host to connect to
60
+
61
+ Defaults to "https://api.akeyless.io"
62
+ """
63
+ api_host = configs.String(default="https://api.akeyless.io")
64
+
65
+ """
66
+ The environment variable key that contains the JWT when using JWT authentication
67
+
68
+ This is required when access_type is "jwt"
69
+ """
70
+ jwt_env_key = configs.String(required=False)
71
+
72
+ """
73
+ The SAML profile name when using SAML authentication
74
+
75
+ Must match the pattern [0-9a-zA-Z-]+ if provided
76
+ """
77
+ profile = configs.String(regexp=r"^[\d\w-]+$")
78
+
79
+ """
80
+ Whether to automatically guess the secret type
81
+
82
+ When enabled, the system will check the secret type (static, dynamic, rotated)
83
+ and call the appropriate method to retrieve it.
84
+ """
85
+ auto_guess_type = configs.Boolean(default=False)
86
+
87
+ """
88
+ When the current token expires
89
+ """
90
+ _token_refresh: datetime.datetime # type: ignore
91
+
92
+ """
93
+ The current authentication token
94
+ """
95
+ _token: str
96
+
97
+ """
98
+ The configured V2Api client
99
+ """
100
+ _api: V2Api
101
+
102
+ @parameters_to_properties
103
+ def __init__(
104
+ self,
105
+ access_id: str,
106
+ access_type: str,
107
+ jwt_env_key: str | None = None,
108
+ api_host: str | None = None,
109
+ profile: str | None = None,
110
+ auto_guess_type: bool = False,
111
+ ):
112
+ """
113
+ Initialize the Akeyless backend with the specified configuration.
114
+
115
+ The access_id must be provided and follow the format p-[0-9a-zA-Z]+. The access_type must be
116
+ one of "aws_iam", "saml", or "jwt". If using JWT authentication, jwt_env_key must be provided.
117
+ """
118
+ self.finalize_and_validate_configuration()
119
+ self.logger = logging.getLogger(self.__class__.__name__)
120
+
121
+ def configure(self) -> None:
122
+ """
123
+ Perform additional configuration validation.
124
+
125
+ Ensures that when using JWT authentication, the jwt_env_key is provided. Raises ValueError
126
+ if access_type is "jwt" and jwt_env_key is not provided.
127
+ """
128
+ if self.access_type == "jwt" and not self.jwt_env_key:
129
+ raise ValueError("When using the JWT access type for Akeyless you must provide jwt_env_key")
130
+
131
+ @property
132
+ def api(self) -> V2Api:
133
+ """
134
+ Get the configured V2Api client.
135
+
136
+ Creates a new API client if one doesn't exist yet, using the configured api_host.
137
+ """
138
+ if not hasattr(self, "_api"):
139
+ configuration = self.akeyless.Configuration(host=self.api_host)
140
+ self._api = self.akeyless.V2Api(self.akeyless.ApiClient(configuration))
141
+ return self._api
142
+
143
+ def create(self, path: str, value: Any) -> bool:
144
+ """
145
+ Create a new secret at the given path.
146
+
147
+ Checks permissions before creating the secret and raises PermissionsError if the user doesn't
148
+ have write permission for the path. The value is converted to a string before storage.
149
+ """
150
+ if not "write" in self.describe_permissions(path):
151
+ raise PermissionsError(f"You do not have permission the secret '{path}'")
152
+
153
+ res = self.api.create_secret(self.akeyless.CreateSecret(name=path, value=str(value), token=self._get_token()))
154
+ return True
155
+
156
+ def get(
157
+ self,
158
+ path: str,
159
+ silent_if_not_found: bool = False,
160
+ json_attribute: str | None = None,
161
+ args: dict[str, Any] | None = None,
162
+ ) -> str:
163
+ """
164
+ Get the secret at the given path.
165
+
166
+ When auto_guess_type is enabled, this method automatically determines if the secret is static,
167
+ dynamic, or rotated and calls the appropriate method to retrieve it. If silent_if_not_found is
168
+ True, returns an empty string when the secret is not found. If json_attribute is provided,
169
+ treats the secret as JSON and returns the specified attribute.
170
+ """
171
+ if not self.auto_guess_type:
172
+ return self.get_static_secret(path, silent_if_not_found=silent_if_not_found, json_attribute=json_attribute)
173
+
174
+ try:
175
+ secret = self.describe_secret(path)
176
+ except Exception as e:
177
+ if e.status == 404: # type: ignore
178
+ if silent_if_not_found:
179
+ return ""
180
+ raise e
181
+ else:
182
+ raise ValueError(
183
+ f"describe-secret call failed for path {path}: perhaps a permissions issue? Akeless says {e}"
184
+ )
185
+
186
+ self.logger.debug(f"Auto-detected secret type '{secret.item_type}' for secret '{path}'")
187
+ match secret.item_type.lower():
188
+ case "dynamic_secret":
189
+ return str(
190
+ self.get_dynamic_secret(
191
+ path,
192
+ json_attribute=json_attribute,
193
+ args=args,
194
+ )
195
+ )
196
+ case "rotated_secret":
197
+ return str(self.get_rotated_secret(path, json_attribute=json_attribute, args=args))
198
+ case "static_secret":
199
+ return self.get_static_secret(
200
+ path, json_attribute=json_attribute, silent_if_not_found=silent_if_not_found
201
+ )
202
+ case _:
203
+ raise ValueError(f"Unsupported secret type for auto-detection: '{secret.item_type}'")
204
+
205
+ def get_static_secret(self, path: str, silent_if_not_found: bool = False, json_attribute: str | None = None) -> str:
206
+ """
207
+ Get a static secret from the given path.
208
+
209
+ Checks permissions before retrieving the secret and raises PermissionsError if the user doesn't
210
+ have read permission. If silent_if_not_found is True, returns an empty string when the secret
211
+ is not found. If json_attribute is provided, treats the secret as JSON and returns the specified attribute.
212
+ """
213
+ if not "read" in self.describe_permissions(path):
214
+ raise PermissionsError(f"You do not have permission the secret '{path}'")
215
+
216
+ try:
217
+ res: dict[str, object] = self.api.get_secret_value( # type: ignore
218
+ self.akeyless.GetSecretValue(
219
+ names=[path], token=self._get_token(), json=True if json_attribute else False
220
+ )
221
+ )
222
+ except Exception as e:
223
+ if e.status == 404: # type: ignore
224
+ if silent_if_not_found:
225
+ return ""
226
+ raise KeyError(f"Secret '{path}' not found")
227
+ raise e
228
+ if json_attribute:
229
+ return self._get_nested_attribute(res[path], json_attribute) # type: ignore
230
+ return str(res[path])
231
+
232
+ def get_dynamic_secret(
233
+ self, path: str, json_attribute: str | None = None, args: dict[str, Any] | None = None
234
+ ) -> Any:
235
+ """
236
+ Get a dynamic secret from the given path.
237
+
238
+ Dynamic secrets are generated on-demand, such as database credentials. Checks permissions
239
+ before retrieving the secret and raises PermissionsError if the user doesn't have read
240
+ permission. If json_attribute is provided, treats the result as JSON and returns the
241
+ specified attribute.
242
+ """
243
+ if not "read" in self.describe_permissions(path):
244
+ raise PermissionsError(f"You do not have permission the secret '{path}'")
245
+
246
+ kwargs = {
247
+ "name": path,
248
+ "token": self._get_token(),
249
+ }
250
+ if args:
251
+ kwargs["args"] = args # type: ignore
252
+ res: dict[str, Any] = self.api.get_dynamic_secret_value(self.akeyless.GetDynamicSecretValue(**kwargs)) # type: ignore
253
+ if json_attribute:
254
+ return self._get_nested_attribute(res, json_attribute)
255
+ return res
256
+
257
+ def get_rotated_secret(
258
+ self, path: str, json_attribute: str | None = None, args: dict[str, Any] | None = None
259
+ ) -> Any:
260
+ """
261
+ Get a rotated secret from the given path.
262
+
263
+ Rotated secrets are automatically replaced on a schedule. Checks permissions before
264
+ retrieving the secret and raises PermissionsError if the user doesn't have read
265
+ permission. If json_attribute is provided, treats the result as JSON and returns the
266
+ specified attribute.
267
+ """
268
+ if not "read" in self.describe_permissions(path):
269
+ raise PermissionsError(f"You do not have permission the secret '{path}'")
270
+
271
+ kwargs = {
272
+ "names": path,
273
+ "token": self._get_token(),
274
+ "json": True if json_attribute else False,
275
+ }
276
+ if args:
277
+ kwargs["args"] = args # type: ignore
278
+
279
+ res: dict[str, str] = self._api.get_rotated_secret_value(self.akeyless.GetRotatedSecretValue(**kwargs))["value"] # type: ignore
280
+ if json_attribute:
281
+ return self._get_nested_attribute(res, json_attribute)
282
+ return res
283
+
284
+ def describe_secret(self, path: str) -> Any:
285
+ """
286
+ Get metadata about a secret.
287
+
288
+ Checks permissions before retrieving metadata and raises PermissionsError if the user
289
+ doesn't have read permission for the path.
290
+ """
291
+ if not "read" in self.describe_permissions(path):
292
+ raise PermissionsError(f"You do not have permission the secret '{path}'")
293
+
294
+ return self.api.describe_item(self.akeyless.DescribeItem(name=path, token=self._get_token()))
295
+
296
+ def list_secrets(self, path: str) -> list[Any]:
297
+ """
298
+ List all secrets at the given path.
299
+
300
+ Checks permissions before listing secrets and raises PermissionsError if the user doesn't
301
+ have list permission for the path. Returns an empty list if no secrets are found.
302
+ """
303
+ if not "list" in self.describe_permissions(path):
304
+ raise PermissionsError(f"You do not have permission the secrets in '{path}'")
305
+
306
+ res: ListItemsOutput = self.api.list_items( # type: ignore
307
+ self.akeyless.ListItems(
308
+ path=path,
309
+ token=self._get_token(),
310
+ )
311
+ )
312
+ if not res.items:
313
+ return []
314
+
315
+ return [item.item_name for item in res.items]
316
+
317
+ def update(self, path: str, value: Any) -> None:
318
+ """
319
+ Update an existing secret.
320
+
321
+ Checks permissions before updating the secret and raises PermissionsError if the user
322
+ doesn't have write permission for the path. The value is converted to a string before storage.
323
+ """
324
+ if not "write" in self.describe_permissions(path):
325
+ raise PermissionsError(f"You do not have permission the secret '{path}'")
326
+
327
+ res = self.api.update_secret_val(
328
+ self.akeyless.UpdateSecretVal(name=path, value=str(value), token=self._get_token())
329
+ )
330
+
331
+ def upsert(self, path: str, value: Any) -> None:
332
+ """
333
+ Create or update a secret.
334
+
335
+ This method attempts to update an existing secret, and if that fails, it tries to create
336
+ a new one. The value is converted to a string before storage.
337
+ """
338
+ try:
339
+ self.update(path, value)
340
+ except Exception as e:
341
+ self.create(path, value)
342
+
343
+ def list_sub_folders(self, main_folder: str) -> list[str]:
344
+ """
345
+ Return the list of secrets/sub folders in the given folder.
346
+
347
+ Checks permissions before listing subfolders and raises PermissionsError if the user doesn't
348
+ have list permission for the path. Returns the relative subfolder names without the parent path.
349
+ """
350
+ if not "list" in self.describe_permissions(main_folder):
351
+ raise PermissionsError(f"You do not have permission to list sub folders in '{main_folder}'")
352
+
353
+ items = self.api.list_items(self.akeyless.ListItems(path=main_folder, token=self._get_token()))
354
+
355
+ # akeyless will return the absolute path and end in a slash but we only want the folder name
356
+ main_folder_string_len = len(main_folder)
357
+ return [sub_folder[main_folder_string_len:-1] for sub_folder in items.folders] # type: ignore
358
+
359
+ def get_ssh_certificate(self, cert_issuer: str, cert_username: str, path_to_public_file: str) -> Any:
360
+ """
361
+ Get an SSH certificate from Akeyless.
362
+
363
+ Reads the public key from the specified file path and requests a certificate for the given
364
+ username and issuer from Akeyless.
365
+ """
366
+ with open(path_to_public_file, "r") as fp:
367
+ public_key = fp.read()
368
+
369
+ res = self.api.get_ssh_certificate(
370
+ self.akeyless.GetSSHCertificate(
371
+ cert_username=cert_username,
372
+ cert_issuer_name=cert_issuer,
373
+ public_key_data=public_key,
374
+ token=self._get_token(),
375
+ )
376
+ )
377
+
378
+ return res.data # type: ignore
379
+
380
+ def _get_token(self) -> str:
381
+ """
382
+ Get an authentication token for Akeyless API calls.
383
+
384
+ Returns a cached token if available and not expired (within 10 seconds), otherwise obtains
385
+ a new one using the configured authentication method. Tokens are valid for about an hour,
386
+ but we set the refresh time to 30 minutes to be safe.
387
+ """
388
+ # AKeyless tokens live for an hour
389
+ if (
390
+ hasattr(self, "_token_refresh")
391
+ and hasattr(self, "_token")
392
+ and (self._token_refresh - datetime.datetime.now()).total_seconds() > 10
393
+ ):
394
+ return self._token
395
+
396
+ auth_method_name = f"auth_{self.access_type}"
397
+ if not hasattr(self, auth_method_name):
398
+ raise ValueError(f"Requested Akeyless authentication with unsupported auth method: '{self.access_type}'")
399
+
400
+ self._token_refresh = datetime.datetime.now() + datetime.timedelta(hours=0.5)
401
+ self._token = getattr(self, auth_method_name)()
402
+ return self._token
403
+
404
+ def auth_aws_iam(self):
405
+ """
406
+ Authenticate using AWS IAM.
407
+
408
+ Uses the akeyless_cloud_id package to generate a cloud ID and authenticates with Akeyless
409
+ using the configured access_id.
410
+ """
411
+ from akeyless_cloud_id import CloudId # type: ignore
412
+
413
+ res = self.api.auth(
414
+ self.akeyless.Auth(access_id=self.access_id, access_type="aws_iam", cloud_id=CloudId().generate())
415
+ )
416
+ return res.token # type: ignore
417
+
418
+ def auth_saml(self):
419
+ """
420
+ Authenticate using SAML.
421
+
422
+ Uses the akeyless CLI to generate credentials and then retrieves a token either directly
423
+ from the credentials file or by making an API call to convert the credentials to a token.
424
+ """
425
+ import json
426
+ import os
427
+ from pathlib import Path
428
+
429
+ os.system(f"akeyless list-items --profile {self.profile} --path /not/a/real/path > /dev/null 2>&1")
430
+ home = str(Path.home())
431
+ with open(f"{home}/.akeyless/.tmp_creds/{self.profile}-{self.access_id}", "r") as creds_file:
432
+ credentials = creds_file.read()
433
+ credentials_json = json.loads(credentials)
434
+ if "token" in credentials_json:
435
+ return credentials_json["token"]
436
+ # and now we can turn that into a token
437
+ response = self.requests.post(
438
+ "https://rest.akeyless.io/",
439
+ data={
440
+ "cmd": "static-creds-auth",
441
+ "access-id": self.access_id,
442
+ "creds": credentials.strip(),
443
+ },
444
+ )
445
+ return response.json()["token"]
446
+
447
+ def auth_jwt(self):
448
+ """
449
+ Authenticate using JWT.
450
+
451
+ Retrieves the JWT from the environment variable specified by jwt_env_key and authenticates
452
+ with Akeyless. Raises ValueError if jwt_env_key is not specified.
453
+ """
454
+ if not self.jwt_env_key:
455
+ raise ValueError(
456
+ "To use AKeyless JWT Auth, "
457
+ "you must specify the name of the ENV key to load the JWT from when configuring AKeyless"
458
+ )
459
+ res = self.api.auth(
460
+ self.akeyless.Auth(access_id=self.access_id, access_type="jwt", jwt=self.environment.get(self.jwt_env_key))
461
+ )
462
+ return res.token # type: ignore
463
+
464
+ def describe_permissions(self, path: str, type: str = "item") -> list[str]:
465
+ """
466
+ List permissions for a path.
467
+
468
+ Returns a list of permission strings (e.g., "read", "write", "list") that the current
469
+ authentication token has for the specified path.
470
+ """
471
+ return self.api.describe_permissions(
472
+ self.akeyless.DescribePermissions(token=self._get_token(), path=path, type=type)
473
+ ).client_permissions # type: ignore
474
+
475
+ def _get_nested_attribute(self, data: dict[str, Any] | str, attr_path: str) -> Any:
476
+ """
477
+ Extract a nested attribute from JSON data.
478
+
479
+ Parses the provided data as JSON if it's a string. Traverses the nested structure using
480
+ the dot-separated path (e.g., "database.username"). Raises ValueError if the data cannot
481
+ be parsed as JSON, or KeyError if the attribute path doesn't exist in the data.
482
+ """
483
+ keys = attr_path.split(".", 1)
484
+ if not isinstance(data, dict):
485
+ try:
486
+ data = json.loads(data)
487
+ except Exception:
488
+ raise ValueError(f"Could not parse secret as JSON to get attribute '{attr_path}'")
489
+ if len(keys) == 1:
490
+ if not isinstance(data, dict) or keys[0] not in data:
491
+ raise KeyError(f"Secret does not contain attribute '{attr_path}'")
492
+ return data[keys[0]] # type: ignore
493
+ return self._get_nested_attribute(data[keys[0]], keys[1]) # type: ignore
494
+
495
+
496
+ class AkeylessSaml(Akeyless):
497
+ """Convenience class for SAML authentication with Akeyless."""
498
+
499
+ def __init__(self, access_id: str, api_host: str = "", profile: str = ""):
500
+ """
501
+ Initialize with SAML authentication.
502
+
503
+ Sets access_type to "saml" and passes the remaining parameters to the parent class.
504
+ """
505
+ return super().__init__(access_id, "saml", api_host=api_host, profile=profile)
506
+
507
+
508
+ class AkeylessJwt(Akeyless):
509
+ """Convenience class for JWT authentication with Akeyless."""
510
+
511
+ def __init__(self, access_id: str, jwt_env_key: str = "", api_host: str = "", profile: str = ""):
512
+ """
513
+ Initialize with JWT authentication.
514
+
515
+ Sets access_type to "jwt" and passes the remaining parameters to the parent class.
516
+ """
517
+ return super().__init__(access_id, "jwt", jwt_env_key=jwt_env_key, api_host=api_host, profile=profile)
518
+
519
+
520
+ class AkeylessAwsIam(Akeyless):
521
+ """Convenience class for AWS IAM authentication with Akeyless."""
522
+
523
+ def __init__(self, access_id: str, api_host: str = ""):
524
+ """
525
+ Initialize with AWS IAM authentication.
526
+
527
+ Sets access_type to "aws_iam" and passes the remaining parameters to the parent class.
528
+ """
529
+ return super().__init__(access_id, "aws_iam", api_host=api_host)
@@ -0,0 +1,7 @@
1
+ from clearskies.secrets.exceptions.not_found_error import NotFoundError
2
+ from clearskies.secrets.exceptions.permissions_error import PermissionsError
3
+
4
+ __all__ = [
5
+ "NotFoundError",
6
+ "PermissionsError",
7
+ ]
@@ -0,0 +1,2 @@
1
+ class NotFoundError(Exception):
2
+ pass
@@ -0,0 +1,2 @@
1
+ class PermissionsError(Exception):
2
+ pass