clear-skies 1.22.30__py3-none-any.whl → 2.0.0__py3-none-any.whl

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 (344) hide show
  1. {clear_skies-1.22.30.dist-info → clear_skies-2.0.0.dist-info}/METADATA +5 -7
  2. clear_skies-2.0.0.dist-info/RECORD +248 -0
  3. {clear_skies-1.22.30.dist-info → clear_skies-2.0.0.dist-info}/WHEEL +1 -1
  4. clearskies/__init__.py +42 -25
  5. clearskies/action.py +7 -0
  6. clearskies/authentication/__init__.py +8 -41
  7. clearskies/authentication/authentication.py +42 -0
  8. clearskies/authentication/authorization.py +4 -9
  9. clearskies/authentication/authorization_pass_through.py +11 -9
  10. clearskies/authentication/jwks.py +128 -58
  11. clearskies/authentication/public.py +3 -38
  12. clearskies/authentication/secret_bearer.py +516 -54
  13. clearskies/autodoc/formats/oai3_json/__init__.py +1 -1
  14. clearskies/autodoc/formats/oai3_json/oai3_json.py +9 -7
  15. clearskies/autodoc/formats/oai3_json/parameter.py +6 -3
  16. clearskies/autodoc/formats/oai3_json/request.py +7 -5
  17. clearskies/autodoc/formats/oai3_json/response.py +7 -4
  18. clearskies/autodoc/formats/oai3_json/schema/object.py +4 -1
  19. clearskies/autodoc/request/__init__.py +2 -0
  20. clearskies/autodoc/request/header.py +4 -6
  21. clearskies/autodoc/request/json_body.py +4 -6
  22. clearskies/autodoc/request/parameter.py +8 -0
  23. clearskies/autodoc/request/request.py +7 -4
  24. clearskies/autodoc/request/url_parameter.py +4 -6
  25. clearskies/autodoc/request/url_path.py +4 -6
  26. clearskies/autodoc/schema/__init__.py +4 -2
  27. clearskies/autodoc/schema/array.py +5 -6
  28. clearskies/autodoc/schema/boolean.py +4 -10
  29. clearskies/autodoc/schema/date.py +0 -3
  30. clearskies/autodoc/schema/datetime.py +1 -4
  31. clearskies/autodoc/schema/double.py +0 -3
  32. clearskies/autodoc/schema/enum.py +4 -2
  33. clearskies/autodoc/schema/integer.py +4 -9
  34. clearskies/autodoc/schema/long.py +0 -3
  35. clearskies/autodoc/schema/number.py +4 -9
  36. clearskies/autodoc/schema/object.py +5 -7
  37. clearskies/autodoc/schema/password.py +0 -3
  38. clearskies/autodoc/schema/schema.py +11 -0
  39. clearskies/autodoc/schema/string.py +4 -10
  40. clearskies/backends/__init__.py +55 -20
  41. clearskies/backends/api_backend.py +1100 -284
  42. clearskies/backends/backend.py +40 -84
  43. clearskies/backends/cursor_backend.py +236 -186
  44. clearskies/backends/memory_backend.py +519 -226
  45. clearskies/backends/secrets_backend.py +75 -31
  46. clearskies/column.py +1232 -0
  47. clearskies/columns/__init__.py +71 -0
  48. clearskies/columns/audit.py +205 -0
  49. clearskies/columns/belongs_to_id.py +483 -0
  50. clearskies/columns/belongs_to_model.py +128 -0
  51. clearskies/columns/belongs_to_self.py +105 -0
  52. clearskies/columns/boolean.py +109 -0
  53. clearskies/columns/category_tree.py +275 -0
  54. clearskies/columns/category_tree_ancestors.py +51 -0
  55. clearskies/columns/category_tree_children.py +127 -0
  56. clearskies/columns/category_tree_descendants.py +48 -0
  57. clearskies/columns/created.py +94 -0
  58. clearskies/columns/created_by_authorization_data.py +116 -0
  59. clearskies/columns/created_by_header.py +99 -0
  60. clearskies/columns/created_by_ip.py +92 -0
  61. clearskies/columns/created_by_routing_data.py +96 -0
  62. clearskies/columns/created_by_user_agent.py +92 -0
  63. clearskies/columns/date.py +230 -0
  64. clearskies/columns/datetime.py +278 -0
  65. clearskies/columns/email.py +76 -0
  66. clearskies/columns/float.py +149 -0
  67. clearskies/columns/has_many.py +505 -0
  68. clearskies/columns/has_many_self.py +56 -0
  69. clearskies/columns/has_one.py +14 -0
  70. clearskies/columns/integer.py +156 -0
  71. clearskies/columns/json.py +122 -0
  72. clearskies/columns/many_to_many_ids.py +333 -0
  73. clearskies/columns/many_to_many_ids_with_data.py +270 -0
  74. clearskies/columns/many_to_many_models.py +154 -0
  75. clearskies/columns/many_to_many_pivots.py +133 -0
  76. clearskies/columns/phone.py +158 -0
  77. clearskies/columns/select.py +91 -0
  78. clearskies/columns/string.py +98 -0
  79. clearskies/columns/timestamp.py +160 -0
  80. clearskies/columns/updated.py +110 -0
  81. clearskies/columns/uuid.py +86 -0
  82. clearskies/configs/README.md +105 -0
  83. clearskies/configs/__init__.py +159 -0
  84. clearskies/configs/actions.py +43 -0
  85. clearskies/configs/any.py +13 -0
  86. clearskies/configs/any_dict.py +22 -0
  87. clearskies/configs/any_dict_or_callable.py +23 -0
  88. clearskies/configs/authentication.py +23 -0
  89. clearskies/configs/authorization.py +23 -0
  90. clearskies/configs/boolean.py +16 -0
  91. clearskies/configs/boolean_or_callable.py +18 -0
  92. clearskies/configs/callable_config.py +18 -0
  93. clearskies/configs/columns.py +34 -0
  94. clearskies/configs/conditions.py +30 -0
  95. clearskies/configs/config.py +21 -0
  96. clearskies/configs/datetime.py +18 -0
  97. clearskies/configs/datetime_or_callable.py +19 -0
  98. clearskies/configs/endpoint.py +23 -0
  99. clearskies/configs/float.py +16 -0
  100. clearskies/configs/float_or_callable.py +18 -0
  101. clearskies/configs/integer.py +16 -0
  102. clearskies/configs/integer_or_callable.py +18 -0
  103. clearskies/configs/joins.py +30 -0
  104. clearskies/configs/list_any_dict.py +30 -0
  105. clearskies/configs/list_any_dict_or_callable.py +31 -0
  106. clearskies/configs/model_class.py +35 -0
  107. clearskies/configs/model_column.py +65 -0
  108. clearskies/configs/model_columns.py +56 -0
  109. clearskies/configs/model_destination_name.py +25 -0
  110. clearskies/configs/model_to_id_column.py +43 -0
  111. clearskies/configs/readable_model_column.py +9 -0
  112. clearskies/configs/readable_model_columns.py +9 -0
  113. clearskies/configs/schema.py +23 -0
  114. clearskies/configs/searchable_model_columns.py +9 -0
  115. clearskies/configs/security_headers.py +39 -0
  116. clearskies/configs/select.py +26 -0
  117. clearskies/configs/select_list.py +47 -0
  118. clearskies/configs/string.py +29 -0
  119. clearskies/configs/string_dict.py +32 -0
  120. clearskies/configs/string_list.py +32 -0
  121. clearskies/configs/string_list_or_callable.py +35 -0
  122. clearskies/configs/string_or_callable.py +18 -0
  123. clearskies/configs/timedelta.py +18 -0
  124. clearskies/configs/timezone.py +18 -0
  125. clearskies/configs/url.py +23 -0
  126. clearskies/configs/validators.py +45 -0
  127. clearskies/configs/writeable_model_column.py +9 -0
  128. clearskies/configs/writeable_model_columns.py +9 -0
  129. clearskies/configurable.py +76 -0
  130. clearskies/contexts/__init__.py +8 -8
  131. clearskies/contexts/cli.py +5 -42
  132. clearskies/contexts/context.py +78 -56
  133. clearskies/contexts/wsgi.py +13 -30
  134. clearskies/contexts/wsgi_ref.py +49 -0
  135. clearskies/di/__init__.py +10 -7
  136. clearskies/di/additional_config.py +115 -4
  137. clearskies/di/additional_config_auto_import.py +12 -0
  138. clearskies/di/di.py +742 -121
  139. clearskies/di/inject/__init__.py +23 -0
  140. clearskies/di/inject/by_class.py +21 -0
  141. clearskies/di/inject/by_name.py +18 -0
  142. clearskies/di/inject/di.py +13 -0
  143. clearskies/di/inject/environment.py +14 -0
  144. clearskies/di/inject/input_output.py +20 -0
  145. clearskies/di/inject/now.py +13 -0
  146. clearskies/di/inject/requests.py +13 -0
  147. clearskies/di/inject/secrets.py +14 -0
  148. clearskies/di/inject/utcnow.py +13 -0
  149. clearskies/di/inject/uuid.py +15 -0
  150. clearskies/di/injectable.py +29 -0
  151. clearskies/di/injectable_properties.py +131 -0
  152. clearskies/end.py +183 -0
  153. clearskies/endpoint.py +1309 -0
  154. clearskies/endpoint_group.py +297 -0
  155. clearskies/endpoints/__init__.py +23 -0
  156. clearskies/endpoints/advanced_search.py +526 -0
  157. clearskies/endpoints/callable.py +387 -0
  158. clearskies/endpoints/create.py +202 -0
  159. clearskies/endpoints/delete.py +139 -0
  160. clearskies/endpoints/get.py +275 -0
  161. clearskies/endpoints/health_check.py +181 -0
  162. clearskies/endpoints/list.py +573 -0
  163. clearskies/endpoints/restful_api.py +427 -0
  164. clearskies/endpoints/simple_search.py +286 -0
  165. clearskies/endpoints/update.py +190 -0
  166. clearskies/environment.py +5 -3
  167. clearskies/exceptions/__init__.py +17 -0
  168. clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
  169. clearskies/exceptions/moved_permanently.py +3 -0
  170. clearskies/exceptions/moved_temporarily.py +3 -0
  171. clearskies/exceptions/not_found.py +2 -0
  172. clearskies/functional/__init__.py +2 -2
  173. clearskies/functional/routing.py +92 -0
  174. clearskies/functional/string.py +19 -11
  175. clearskies/functional/validations.py +61 -9
  176. clearskies/input_outputs/__init__.py +9 -7
  177. clearskies/input_outputs/cli.py +130 -142
  178. clearskies/input_outputs/exceptions/__init__.py +1 -1
  179. clearskies/input_outputs/headers.py +45 -0
  180. clearskies/input_outputs/input_output.py +91 -122
  181. clearskies/input_outputs/programmatic.py +69 -0
  182. clearskies/input_outputs/wsgi.py +23 -38
  183. clearskies/model.py +489 -184
  184. clearskies/parameters_to_properties.py +31 -0
  185. clearskies/query/__init__.py +12 -0
  186. clearskies/query/condition.py +223 -0
  187. clearskies/query/join.py +136 -0
  188. clearskies/query/query.py +196 -0
  189. clearskies/query/sort.py +27 -0
  190. clearskies/schema.py +82 -0
  191. clearskies/secrets/__init__.py +3 -31
  192. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
  193. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
  194. clearskies/secrets/akeyless.py +88 -147
  195. clearskies/secrets/secrets.py +8 -8
  196. clearskies/security_header.py +8 -0
  197. clearskies/security_headers/__init__.py +8 -8
  198. clearskies/security_headers/cache_control.py +47 -110
  199. clearskies/security_headers/cors.py +40 -95
  200. clearskies/security_headers/csp.py +76 -151
  201. clearskies/security_headers/hsts.py +14 -16
  202. clearskies/test_base.py +8 -0
  203. clearskies/typing.py +11 -0
  204. clearskies/validator.py +25 -0
  205. clearskies/validators/__init__.py +33 -0
  206. clearskies/validators/after_column.py +62 -0
  207. clearskies/validators/before_column.py +13 -0
  208. clearskies/validators/in_the_future.py +32 -0
  209. clearskies/validators/in_the_future_at_least.py +11 -0
  210. clearskies/validators/in_the_future_at_most.py +10 -0
  211. clearskies/validators/in_the_past.py +32 -0
  212. clearskies/validators/in_the_past_at_least.py +10 -0
  213. clearskies/validators/in_the_past_at_most.py +10 -0
  214. clearskies/validators/maximum_length.py +26 -0
  215. clearskies/validators/maximum_value.py +29 -0
  216. clearskies/validators/minimum_length.py +26 -0
  217. clearskies/validators/minimum_value.py +29 -0
  218. clearskies/validators/required.py +35 -0
  219. clearskies/validators/timedelta.py +59 -0
  220. clearskies/validators/unique.py +31 -0
  221. clear_skies-1.22.30.dist-info/RECORD +0 -214
  222. clearskies/application.py +0 -29
  223. clearskies/authentication/auth0_jwks.py +0 -118
  224. clearskies/authentication/auth_exception.py +0 -2
  225. clearskies/authentication/jwks_jwcrypto.py +0 -51
  226. clearskies/backends/api_get_only_backend.py +0 -48
  227. clearskies/backends/example_backend.py +0 -43
  228. clearskies/backends/file_backend.py +0 -48
  229. clearskies/backends/json_backend.py +0 -7
  230. clearskies/backends/restful_api_advanced_search_backend.py +0 -103
  231. clearskies/binding_config.py +0 -16
  232. clearskies/column_types/__init__.py +0 -203
  233. clearskies/column_types/audit.py +0 -249
  234. clearskies/column_types/belongs_to.py +0 -271
  235. clearskies/column_types/boolean.py +0 -60
  236. clearskies/column_types/category_tree.py +0 -304
  237. clearskies/column_types/column.py +0 -373
  238. clearskies/column_types/created.py +0 -26
  239. clearskies/column_types/created_by_authorization_data.py +0 -26
  240. clearskies/column_types/created_by_header.py +0 -24
  241. clearskies/column_types/created_by_ip.py +0 -17
  242. clearskies/column_types/created_by_routing_data.py +0 -25
  243. clearskies/column_types/created_by_user_agent.py +0 -17
  244. clearskies/column_types/created_micro.py +0 -26
  245. clearskies/column_types/datetime.py +0 -109
  246. clearskies/column_types/datetime_micro.py +0 -12
  247. clearskies/column_types/email.py +0 -18
  248. clearskies/column_types/float.py +0 -43
  249. clearskies/column_types/has_many.py +0 -179
  250. clearskies/column_types/has_one.py +0 -60
  251. clearskies/column_types/integer.py +0 -41
  252. clearskies/column_types/json.py +0 -25
  253. clearskies/column_types/many_to_many.py +0 -278
  254. clearskies/column_types/many_to_many_with_data.py +0 -162
  255. clearskies/column_types/phone.py +0 -48
  256. clearskies/column_types/select.py +0 -11
  257. clearskies/column_types/string.py +0 -24
  258. clearskies/column_types/timestamp.py +0 -73
  259. clearskies/column_types/updated.py +0 -26
  260. clearskies/column_types/updated_micro.py +0 -26
  261. clearskies/column_types/uuid.py +0 -25
  262. clearskies/columns.py +0 -123
  263. clearskies/condition_parser.py +0 -172
  264. clearskies/contexts/build_context.py +0 -54
  265. clearskies/contexts/convert_to_application.py +0 -190
  266. clearskies/contexts/extract_handler.py +0 -37
  267. clearskies/contexts/test.py +0 -94
  268. clearskies/decorators/__init__.py +0 -41
  269. clearskies/decorators/allow_non_json_bodies.py +0 -9
  270. clearskies/decorators/auth0_jwks.py +0 -22
  271. clearskies/decorators/authorization.py +0 -10
  272. clearskies/decorators/binding_classes.py +0 -9
  273. clearskies/decorators/binding_modules.py +0 -9
  274. clearskies/decorators/bindings.py +0 -9
  275. clearskies/decorators/create.py +0 -10
  276. clearskies/decorators/delete.py +0 -10
  277. clearskies/decorators/docs.py +0 -14
  278. clearskies/decorators/get.py +0 -10
  279. clearskies/decorators/jwks.py +0 -26
  280. clearskies/decorators/merge.py +0 -124
  281. clearskies/decorators/patch.py +0 -10
  282. clearskies/decorators/post.py +0 -10
  283. clearskies/decorators/public.py +0 -11
  284. clearskies/decorators/response_headers.py +0 -10
  285. clearskies/decorators/return_raw_response.py +0 -9
  286. clearskies/decorators/schema.py +0 -10
  287. clearskies/decorators/secret_bearer.py +0 -24
  288. clearskies/decorators/security_headers.py +0 -10
  289. clearskies/di/standard_dependencies.py +0 -151
  290. clearskies/handlers/__init__.py +0 -41
  291. clearskies/handlers/advanced_search.py +0 -271
  292. clearskies/handlers/base.py +0 -479
  293. clearskies/handlers/callable.py +0 -192
  294. clearskies/handlers/create.py +0 -35
  295. clearskies/handlers/crud_by_method.py +0 -18
  296. clearskies/handlers/database_connector.py +0 -32
  297. clearskies/handlers/delete.py +0 -61
  298. clearskies/handlers/exceptions/__init__.py +0 -5
  299. clearskies/handlers/exceptions/not_found.py +0 -3
  300. clearskies/handlers/get.py +0 -156
  301. clearskies/handlers/health_check.py +0 -59
  302. clearskies/handlers/input_processing.py +0 -79
  303. clearskies/handlers/list.py +0 -530
  304. clearskies/handlers/mygrations.py +0 -82
  305. clearskies/handlers/request_method_routing.py +0 -47
  306. clearskies/handlers/restful_api.py +0 -218
  307. clearskies/handlers/routing.py +0 -62
  308. clearskies/handlers/schema_helper.py +0 -128
  309. clearskies/handlers/simple_routing.py +0 -206
  310. clearskies/handlers/simple_routing_route.py +0 -197
  311. clearskies/handlers/simple_search.py +0 -136
  312. clearskies/handlers/update.py +0 -102
  313. clearskies/handlers/write.py +0 -193
  314. clearskies/input_requirements/__init__.py +0 -78
  315. clearskies/input_requirements/after.py +0 -36
  316. clearskies/input_requirements/before.py +0 -36
  317. clearskies/input_requirements/in_the_future_at_least.py +0 -19
  318. clearskies/input_requirements/in_the_future_at_most.py +0 -19
  319. clearskies/input_requirements/in_the_past_at_least.py +0 -19
  320. clearskies/input_requirements/in_the_past_at_most.py +0 -19
  321. clearskies/input_requirements/maximum_length.py +0 -19
  322. clearskies/input_requirements/maximum_value.py +0 -19
  323. clearskies/input_requirements/minimum_length.py +0 -22
  324. clearskies/input_requirements/minimum_value.py +0 -19
  325. clearskies/input_requirements/required.py +0 -23
  326. clearskies/input_requirements/requirement.py +0 -25
  327. clearskies/input_requirements/time_delta.py +0 -38
  328. clearskies/input_requirements/unique.py +0 -18
  329. clearskies/mocks/__init__.py +0 -7
  330. clearskies/mocks/input_output.py +0 -124
  331. clearskies/mocks/models.py +0 -142
  332. clearskies/models.py +0 -350
  333. clearskies/security_headers/base.py +0 -12
  334. clearskies/tests/simple_api/models/__init__.py +0 -2
  335. clearskies/tests/simple_api/models/status.py +0 -23
  336. clearskies/tests/simple_api/models/user.py +0 -21
  337. clearskies/tests/simple_api/users_api.py +0 -64
  338. {clear_skies-1.22.30.dist-info → clear_skies-2.0.0.dist-info}/LICENSE +0 -0
  339. /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
  340. /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
  341. /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
  342. /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
  343. /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
  344. /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
clearskies/schema.py ADDED
@@ -0,0 +1,82 @@
1
+ from __future__ import annotations
2
+
3
+ from collections import OrderedDict
4
+ from typing import TYPE_CHECKING, Self
5
+
6
+ if TYPE_CHECKING:
7
+ from clearskies import Column
8
+
9
+
10
+ class Schema:
11
+ """
12
+ Define a schema by extending and declaring columns.
13
+
14
+ ```python
15
+ from clearskies.schema import Schema
16
+ from clearskies.validators import Required, Unique
17
+
18
+ import clearskies.columns
19
+
20
+
21
+ class Person(Schema):
22
+ id = clearskies.columns.Uuid()
23
+ name = clearskies.columns.String(validators=[Required()])
24
+ date_of_birth = clearskies.columns.Datetime(validators=[Required(), InThePast()])
25
+ email = clearskies.columns.Email()
26
+ ```
27
+ """
28
+
29
+ id_column_name: str = ""
30
+ _columns: dict[str, Column] = {}
31
+
32
+ @classmethod
33
+ def destination_name(cls: type[Self]) -> str:
34
+ raise NotImplementedError()
35
+
36
+ def __init__(self):
37
+ self._data = {}
38
+
39
+ @classmethod
40
+ def get_columns(cls: type[Self], overrides={}) -> dict[str, Column]:
41
+ """
42
+ Return an ordered dictionary with the configuration for the columns.
43
+
44
+ Generally, this method is meant for internal use. It just pulls the column configuration
45
+ information out of class attributes. It doesn't return the fully prepared columns,
46
+ so you probably can't use the return value of this function. For that, see
47
+ `model.columns()`.
48
+ """
49
+ # no caching if we have overrides
50
+ if cls._columns and not overrides:
51
+ return cls._columns
52
+
53
+ overrides = {**overrides}
54
+ columns: dict[str, Column] = OrderedDict()
55
+ for attribute_name in dir(cls):
56
+ attribute = getattr(cls, attribute_name)
57
+ # use duck typing instead of isinstance to decide which attribute is a column.
58
+ # We have to do this to avoid circular imports.
59
+ if not hasattr(attribute, "from_backend") and not hasattr(attribute, "to_backend"):
60
+ continue
61
+
62
+ if attribute_name in overrides:
63
+ columns[attribute_name] = overrides[attribute_name]
64
+ del overrides[attribute_name]
65
+ columns[attribute_name] = attribute
66
+
67
+ for attribute_name, column in overrides.items():
68
+ columns[attribute_name] = column # type: ignore
69
+
70
+ if not overrides:
71
+ cls._columns = columns
72
+
73
+ # now go through and finalize everything. We have to do this after setting cls._columns, because finalization
74
+ # sometimes depends on fetching the list of columns, so if we do it before caching the answer, we may end up
75
+ # creating circular loops. I don't *think* this will cause painful side-effects, but we'll find out!
76
+ for column_name, column in cls._columns.items():
77
+ column.finalize_configuration(cls, column_name)
78
+
79
+ return columns
80
+
81
+ def __bool__(self):
82
+ return False
@@ -1,34 +1,6 @@
1
- from .akeyless import AKeyless, AKeylessAdditionalConfig
2
- from . import additional_configs
3
- from ..binding_config import BindingConfig
4
-
5
-
6
- def akeyless(*args, **kwargs):
7
- return BindingConfig(AKeyless, *args, **kwargs)
8
-
9
-
10
- def akeyless_aws_iam_auth(access_id=None, api_host=None):
11
- return AKeylessAdditionalConfig("aws_iam", access_id=access_id, api_host=api_host)
12
-
13
-
14
- def akeyless_saml_auth(access_id=None, api_host=None, profile=None):
15
- return AKeylessAdditionalConfig("saml", access_id=access_id, api_host=api_host, profile=profile)
16
-
17
-
18
- def akeyless_jwt_auth(jwt_env_key, access_id=None, api_host=None):
19
- return AKeylessAdditionalConfig("jwt", jwt_env_key=jwt_env_key, access_id=access_id, api_host=api_host)
20
-
21
-
22
- def akeyless_access_key_auth(access_id=None, api_host=None):
23
- return AKeylessAdditionalConfig("access_key", access_id=access_id, api_host=api_host)
24
-
1
+ # from .akeyless import AKeyless, AKeylessAdditionalConfig
2
+ from clearskies.secrets.secrets import Secrets
25
3
 
26
4
  __all__ = [
27
- "AKeyless",
28
- "additional_configs",
29
- "akeyless",
30
- "akeyless_aws_iam_auth",
31
- "akeyless_saml_auth",
32
- "akeyless_jwt_auth",
33
- "akeyless_access_key_auth",
5
+ "Secrets",
34
6
  ]
@@ -14,7 +14,9 @@ class MySQLConnectionDynamicProducer(clearskies.di.additional_config.AdditionalC
14
14
  def provide_connection_details(self, environment, secrets):
15
15
  if not secrets:
16
16
  raise ValueError(
17
- "I was asked to connect to a database via an AKeyless dynamic producer but AKeyless itself wasn't configured. Try setting the AKeyless auth method via clearskies.secrets.akeyless_[jwt|saml|aws_iam]_auth()"
17
+ "I was asked to connect to a database via an AKeyless dynamic producer, \
18
+ but AKeyless itself wasn't configured. \
19
+ Try setting the AKeyless auth method via clearskies.secrets.akeyless_[jwt|saml|aws_iam]_auth()"
18
20
  )
19
21
 
20
22
  producer_name = (
@@ -24,21 +26,30 @@ class MySQLConnectionDynamicProducer(clearskies.di.additional_config.AdditionalC
24
26
  )
25
27
  if not producer_name:
26
28
  raise ValueError(
27
- "I was asked to connect to a database via an AKeyless dynamic producer, but I wasn't told the path to the dynamic producer. This can be set in an environment variable named 'akeyless_mysql_dynamic_producer' or it can be set in the configuration via the producer_name kwarg."
29
+ "I was asked to connect to a database via an AKeyless dynamic producer, \
30
+ but I wasn't told the path to the dynamic producer. \
31
+ This can be set in an environment variable named 'akeyless_mysql_dynamic_producer'\
32
+ or it can be set in the configuration via the producer_name kwarg."
28
33
  )
29
34
  database_name = (
30
35
  self._database_name if self._database_name is not None else environment.get("db_database", silent=True)
31
36
  )
32
37
  if not database_name:
33
38
  raise ValueError(
34
- "I was asked to connect to a database via an AKeyless dynamic producer, but I wasn't told the name of the database. This can be set in an environment variable named 'db_database' or it can be set in the configuration via the database_name kwarg."
39
+ "I was asked to connect to a database via an AKeyless dynamic producer, \
40
+ but I wasn't told the name of the database. \
41
+ This can be set in an environment variable named 'db_database' \
42
+ or it can be set in the configuration via the database_name kwarg."
35
43
  )
36
44
  database_host = (
37
45
  self._database_host if self._database_host is not None else environment.get("db_host", silent=True)
38
46
  )
39
47
  if not database_host:
40
48
  raise ValueError(
41
- "I was asked to connect to a database via an AKeyless dynamic producer, but I wasn't told the host name of the database. This can be set in an environment variable named 'db_host' or it can be set in the configuration via the database_host kwarg."
49
+ "I was asked to connect to a database via an AKeyless dynamic producer, \
50
+ but I wasn't told the host name of the database. \
51
+ This can be set in an environment variable named 'db_host' \
52
+ or it can be set in the configuration via the database_host kwarg."
42
53
  )
43
54
  credentials = secrets.get_dynamic_secret(producer_name)
44
55
 
@@ -1,9 +1,10 @@
1
- import clearskies.di
2
- from pathlib import Path
1
+ import os
3
2
  import socket
4
3
  import subprocess
5
- import os
6
4
  import time
5
+ from pathlib import Path
6
+
7
+ import clearskies.di
7
8
 
8
9
 
9
10
  class MySQLConnectionDynamicProducerViaSSHCertBastion(clearskies.di.additional_config.AdditionalConfig):
@@ -93,7 +94,11 @@ class MySQLConnectionDynamicProducerViaSSHCertBastion(clearskies.di.additional_c
93
94
  if default is not None:
94
95
  return default
95
96
  raise ValueError(
96
- f"I was asked to connect to a database via an AKeyless dynamic producer through an SSH bastion with certificate auth, but I wasn't given a required configuration value: '{config_key_name}'. This can be set in the call to `clearskies.backends.akeyless.mysql_connection_dynamic_producer_via_ssh_cert_bastion()` by providing the '{config_key_name}' argument, or by setting an environment variable named '{environment_key_name}'."
97
+ f"I was asked to connect to a database via an AKeyless dynamic producer through an SSH bastion"
98
+ "with certificate auth, but I wasn't given a required configuration value: '{config_key_name}'."
99
+ "This can be set in the call to "
100
+ "`clearskies.backends.akeyless.mysql_connection_dynamic_producer_via_ssh_cert_bastion()` by providing the "
101
+ "'{config_key_name}' argument, or by setting an environment variable named '{environment_key_name}'."
97
102
  )
98
103
 
99
104
  def _create_tunnel(
@@ -115,7 +120,8 @@ class MySQLConnectionDynamicProducerViaSSHCertBastion(clearskies.di.additional_c
115
120
 
116
121
  if not os.path.isfile(public_key_file_path):
117
122
  raise ValueError(
118
- f"I was asked to connect to AKeyless SSH with the public key file in '{public_key_file_path}', but this file does not exist"
123
+ f"I was asked to connect to AKeyless SSH with the public key file in '{public_key_file_path}',"
124
+ "but this file does not exist"
119
125
  )
120
126
 
121
127
  ssh_certificate = secrets.get_ssh_certificate(cert_issuer_name, bastion_username, public_key_file_path)
@@ -1,170 +1,110 @@
1
1
  import datetime
2
- from clearskies.di import AdditionalConfig
3
- from .exceptions import NotFound
4
-
5
-
6
- class AKeylessAdditionalConfig(AdditionalConfig):
7
- _allowed_auth_methods = ["aws_iam", "saml", "jwt", "access_key"]
8
- _auth_method = None
9
- _kwargs = None
10
-
11
- _auth_method_allowed_kwargs = {
12
- "aws_iam": [],
13
- "saml": ["profile"],
14
- "access_key": [],
15
- "jwt": ["jwt_env_key"],
16
- }
17
-
18
- _validate_kwargs = {
19
- "aws_iam": lambda kwargs: "",
20
- "saml": lambda kwargs: "",
21
- "access_key": lambda kwargs: "",
22
- "jwt": lambda kwargs: ""
23
- if "jwt_env_key" in kwargs
24
- else "Must provide 'jwt_env_key' with the name of the environment variable that contains the JWT when using akeyless_jwt_auth()",
25
- }
26
-
27
- def __init__(self, auth_method, **kwargs):
28
- if auth_method not in self._allowed_auth_methods:
29
- raise ValueError(
30
- f"Internal clearskies error: attempt to use unsupported akeyless auth method, {auth_method}"
31
- )
32
- self._auth_method = auth_method
33
- allowed_kwargs = set(["access_id", "api_host", *self._auth_method_allowed_kwargs[auth_method]])
34
- error = self._validate_kwargs[auth_method](kwargs)
35
- if error:
36
- raise ValueError(error)
37
- extra_keys = set(kwargs.keys()) - allowed_kwargs
38
- if len(extra_keys):
39
- raise ValueError(
40
- f"Unexpected keys were passed into akeyless_{auth_method}: "
41
- + ", ".join(extra_keys)
42
- + ". The expected keys are: "
43
- + ", ".join(allowed_kwargs)
44
- )
45
- self._kwargs = kwargs
46
-
47
- def provide_secrets(self, requests, environment):
48
- secrets = AKeyless(requests, environment)
49
- secrets.configure(access_type=self._auth_method, **self._kwargs)
50
- return secrets
51
-
52
-
53
- class AKeyless:
54
- _akeyless = None
55
- _access_id = None
56
- _access_type = None
57
- _api_host = None
58
- _token_refresh = None
59
- _token = None
60
- _environment = None
61
- _jwt_env_key = None
62
- _requests = None
63
- _api = None
64
-
65
- def __init__(self, requests, environment):
66
- self._requests = requests
67
- self._environment = environment
68
- import akeyless
69
-
70
- self._akeyless = akeyless
71
-
72
- def configure(self, access_id=None, access_type=None, jwt_env_key=None, api_host=None, profile=None):
73
- self._access_id = access_id if access_id is not None else self._environment.get("akeyless_access_id")
74
- self._access_type = access_type if access_type is not None else self._environment.get("akeyless_access_type")
75
- self._jwt_env_key = jwt_env_key
76
- self._api_host = api_host if api_host is not None else self._environment.get("akeyless_api_host", silent=True)
77
- self._profile = profile if profile is not None else "default"
78
-
79
- if not self._api_host:
80
- self._api_host = "https://api.akeyless.io"
81
-
82
- configuration = self._akeyless.Configuration(host=self._api_host)
83
- api_client = self._akeyless.ApiClient(configuration)
84
- self._api = self._akeyless.V2Api(api_client)
85
-
86
- def create(self, path, value):
87
- self._configure_guard()
88
- res = self._api.create_secret(self._akeyless.CreateSecret(name=path, value=str(value), token=self._get_token()))
2
+ from typing import Any
3
+
4
+ import clearskies.configs
5
+ from clearskies.di import InjectableProperties, inject
6
+
7
+
8
+ class Akeyless(clearskies.Configurable, clearskies.di.InjectableProperties):
9
+ requests = clearskies.di.inject.Requests()
10
+ environment = clearskies.di.inject.Environment()
11
+ akeyless = clearskies.di.inject.ByName("akeyless")
12
+
13
+ access_id = clearskies.configs.String(required=True, regexp=r"^p-[\d\w]+$")
14
+ access_type = clearskies.configs.Select(["aws_iam", "saml", "jwt"], required=True)
15
+ api_host = clearskies.configs.String(default="https://api.akeyless.io")
16
+ profile = clearskies.configs.String(regexp=r"^[\d\w\-]+$")
17
+
18
+ _token_refresh: datetime.datetime = None # type: ignore
19
+ _token: str = ""
20
+ _api: Any = None
21
+
22
+ def __init__(self, access_id: str, access_type: str, jwt_env_key: str = "", api_host: str = "", profile: str = ""):
23
+ self.access_id = access_id
24
+ self.access_type = access_type
25
+ self.jwt_env_key = jwt_env_key
26
+ self.api_host = api_host
27
+ self.profile = profile
28
+ if self.access_type == "jwt" and not self.jwt_env_key:
29
+ raise ValueError("When using the JWT access type for Akeyless you must provide jwt_env_key")
30
+
31
+ self.finalize_and_validate_configuration()
32
+
33
+ @property
34
+ def api(self) -> Any:
35
+ if self._api is None:
36
+ configuration = self.akeyless.Configuration(host=self.api_host)
37
+ self._api = self.akeyless.V2Api(self.akeyless.ApiClient(configuration))
38
+ return self._api
39
+
40
+ def create(self, path: str, value: Any) -> bool:
41
+ res = self.api.create_secret(self.akeyless.CreateSecret(name=path, value=str(value), token=self._get_token()))
89
42
  return True
90
43
 
91
- def get(self, path, silent_if_not_found=False):
92
- self._configure_guard()
93
-
44
+ def get(self, path: str, silent_if_not_found: bool = False) -> str:
94
45
  try:
95
- res = self._api.get_secret_value(self._akeyless.GetSecretValue(names=[path], token=self._get_token()))
46
+ res = self._api.get_secret_value(self.akeyless.GetSecretValue(names=[path], token=self._get_token()))
96
47
  except Exception as e:
97
- if e.status == 404:
48
+ if e.status == 404: # type: ignore
98
49
  if silent_if_not_found:
99
- return None
100
- raise NotFound(f"Secret '{path}' not found")
50
+ return ""
51
+ raise KeyError(f"Secret '{path}' not found")
101
52
  raise e
102
53
  return res[path]
103
54
 
104
- def get_dynamic_secret(self, path, args=None):
105
- self._configure_guard()
106
-
55
+ def get_dynamic_secret(self, path: str, args: dict[str, Any] | None = None) -> Any:
107
56
  kwargs = {
108
57
  "name": path,
109
58
  "token": self._get_token(),
110
59
  }
111
60
  if args:
112
- kwargs["args"] = args
113
-
114
- res = self._api.get_dynamic_secret_value(self._akeyless.GetDynamicSecretValue(**kwargs))
115
- return res
61
+ kwargs["args"] = args # type: ignore
116
62
 
117
- def get_rotated_secret(self, path, args=None):
118
- self._configure_guard()
63
+ return self._api.get_dynamic_secret_value(self.akeyless.GetDynamicSecretValue(**kwargs))
119
64
 
65
+ def get_rotated_secret(self, path: str, args: dict[str, Any] | None = None) -> Any:
120
66
  kwargs = {
121
67
  "names": path,
122
68
  "token": self._get_token(),
123
69
  }
124
70
  if args:
125
- kwargs["args"] = args
71
+ kwargs["args"] = args # type: ignore
126
72
 
127
- res = self._api.get_rotated_secret_value(self._akeyless.GetRotatedSecretValue(**kwargs))
73
+ res = self._api.get_rotated_secret_value(self.akeyless.GetRotatedSecretValue(**kwargs))
128
74
  return res
129
75
 
130
- def list_secrets(self, path):
131
- self._configure_guard()
132
- res = self._api.list_items(self._akeyless.ListItems(path=path, token=self._get_token()))
76
+ def list_secrets(self, path: str) -> list[Any]:
77
+ res = self._api.list_items(self.akeyless.ListItems(path=path, token=self._get_token()))
133
78
  if not res.items:
134
79
  return []
135
80
 
136
81
  return [item.item_name for item in res.items]
137
82
 
138
- def update(self, path, value):
139
- self._configure_guard()
83
+ def update(self, path: str, value: Any) -> None:
140
84
  res = self._api.update_secret_val(
141
- self._akeyless.UpdateSecretVal(name=path, value=str(value), token=self._get_token())
85
+ self.akeyless.UpdateSecretVal(name=path, value=str(value), token=self._get_token())
142
86
  )
143
- return True
144
87
 
145
- def upsert(self, path, value):
88
+ def upsert(self, path: str, value: Any) -> None:
146
89
  try:
147
- if self.update(path, value):
148
- return True
90
+ self.update(path, value)
149
91
  except Exception as e:
150
- return self.create(path, value)
92
+ self.create(path, value)
151
93
 
152
94
  def list_sub_folders(self, main_folder: str) -> list[str]:
153
95
  """Return the list of secrets/sub folders in the given folder."""
154
- items = self._api.list_items(self._akeyless.ListItems(path=main_folder, token=self._get_token()))
96
+ items = self._api.list_items(self.akeyless.ListItems(path=main_folder, token=self._get_token()))
155
97
 
156
98
  # akeyless will return the absolute path and end in a slash but we only want the folder name
157
99
  main_folder_string_len = len(main_folder)
158
100
  return [sub_folder[main_folder_string_len:-1] for sub_folder in items.folders]
159
101
 
160
- def get_ssh_certificate(self, cert_issuer, cert_username, path_to_public_file):
161
- self._configure_guard()
162
-
102
+ def get_ssh_certificate(self, cert_issuer: str, cert_username: str, path_to_public_file: str) -> Any:
163
103
  with open(path_to_public_file, "r") as fp:
164
104
  public_key = fp.read()
165
105
 
166
106
  res = self._api.get_ssh_certificate(
167
- self._akeyless.GetSSHCertificate(
107
+ self.akeyless.GetSSHCertificate(
168
108
  cert_username=cert_username,
169
109
  cert_issuer_name=cert_issuer,
170
110
  public_key_data=public_key,
@@ -174,28 +114,24 @@ class AKeyless:
174
114
 
175
115
  return res.data
176
116
 
177
- def _configure_guard(self):
178
- if not self._access_id:
179
- raise ValueError("Must call configure method before using secrets.AKeyless")
180
-
181
- def _get_token(self):
117
+ def _get_token(self) -> str:
182
118
  # AKeyless tokens live for an hour
183
119
  if self._token is not None and (self._token_refresh - datetime.datetime.now()).total_seconds() > 10:
184
120
  return self._token
185
121
 
186
- auth_method_name = f"auth_{self._access_type}"
122
+ auth_method_name = f"auth_{self.access_type}"
187
123
  if not hasattr(self, auth_method_name):
188
- raise ValueError(f"Requested AKeyless authentication with unsupported auth method: '{self._access_type}'")
124
+ raise ValueError(f"Requested Akeyless authentication with unsupported auth method: '{self.access_type}'")
189
125
 
190
126
  self._token_refresh = datetime.datetime.now() + datetime.timedelta(hours=0.5)
191
127
  self._token = getattr(self, auth_method_name)()
192
128
  return self._token
193
129
 
194
130
  def auth_aws_iam(self):
195
- from akeyless_cloud_id import CloudId
131
+ from akeyless_cloud_id import CloudId # type: ignore
196
132
 
197
133
  res = self._api.auth(
198
- self._akeyless.Auth(access_id=self._access_id, access_type="aws_iam", cloud_id=CloudId().generate())
134
+ self.akeyless.Auth(access_id=self.access_id, access_type="aws_iam", cloud_id=CloudId().generate())
199
135
  )
200
136
  return res.token
201
137
 
@@ -203,39 +139,44 @@ class AKeyless:
203
139
  import os
204
140
  from pathlib import Path
205
141
 
206
- os.system(f"akeyless list-items --profile {self._profile} --path /not/a/real/path > /dev/null 2>&1")
142
+ os.system(f"akeyless list-items --profile {self.profile} --path /not/a/real/path > /dev/null 2>&1")
207
143
  home = str(Path.home())
208
- with open(f"{home}/.akeyless/.tmp_creds/{self._profile}-{self._access_id}", "r") as creds_file:
144
+ with open(f"{home}/.akeyless/.tmp_creds/{self.profile}-{self.access_id}", "r") as creds_file:
209
145
  credentials = creds_file.read()
210
146
 
211
147
  # and now we can turn that into a token
212
- response = self._requests.post(
148
+ response = self.requests.post(
213
149
  "https://rest.akeyless.io/",
214
150
  data={
215
151
  "cmd": "static-creds-auth",
216
- "access-id": self._access_id,
152
+ "access-id": self.access_id,
217
153
  "creds": credentials.strip(),
218
154
  },
219
155
  )
220
156
  return response.json()["token"]
221
157
 
222
158
  def auth_jwt(self):
223
- if not self._jwt_env_key:
159
+ if not self.jwt_env_key:
224
160
  raise ValueError(
225
- "To use AKeyless JWT Auth, you must specify the name of the ENV key to load the JWT from when configuring AKeyless"
161
+ "To use AKeyless JWT Auth, "
162
+ "you must specify the name of the ENV key to load the JWT from when configuring AKeyless"
226
163
  )
227
164
  res = self._api.auth(
228
- self._akeyless.Auth(
229
- access_id=self._access_id, access_type="jwt", jwt=self._environment.get(self._jwt_env_key)
230
- )
165
+ self.akeyless.Auth(access_id=self.access_id, access_type="jwt", jwt=self.environment.get(self.jwt_env_key))
231
166
  )
232
167
  return res.token
233
168
 
234
- def auth_access_key(self):
235
- access_key = self._environment.get("akeyless_access_key", silent=True)
236
- if not access_key:
237
- print(
238
- "To use AKeyless access key auth, you must specify your AKeyless access key in the 'akeyless_access_key' environment variable"
239
- )
240
- res = self._api.auth(self._akeyless.Auth(access_id=self._access_id, access_key=access_key))
241
- return res.token
169
+
170
+ class AkeylessSaml(Akeyless):
171
+ def __init__(self, access_id: str, api_host: str = "", profile: str = ""):
172
+ return super().__init__(access_id, "saml", api_host=api_host, profile=profile)
173
+
174
+
175
+ class AkeylessJwt(Akeyless):
176
+ def __init__(self, access_id: str, jwt_env_key: str = "", api_host: str = "", profile: str = ""):
177
+ return super().__init__(access_id, "jwt", jwt_env_key=jwt_env_key, api_host=api_host, profile=profile)
178
+
179
+
180
+ class AkeylessAwsIam(Akeyless):
181
+ def __init__(self, access_id: str, api_host: str = ""):
182
+ return super().__init__(access_id, "aws_iam", api_host=api_host)
@@ -1,38 +1,38 @@
1
- from abc import ABC
1
+ from typing import Any
2
2
 
3
3
 
4
4
  class Secrets:
5
- def create(self, path, value):
5
+ def create(self, path: str, value: str) -> None:
6
6
  raise NotImplementedError(
7
7
  "It looks like you tried to use the secret system in clearskies, but didn't specify a secret manager."
8
8
  )
9
9
 
10
- def get(self, path, silent_if_not_found=False):
10
+ def get(self, path: str, silent_if_not_found: bool = False) -> str:
11
11
  raise NotImplementedError(
12
12
  "It looks like you tried to use the secret system in clearskies, but didn't specify a secret manager."
13
13
  )
14
14
 
15
- def get_dynamic_secret(self, path):
15
+ def get_dynamic_secret(self, path: str, args: dict[str, Any] | None = None) -> Any:
16
16
  raise NotImplementedError(
17
17
  "It looks like you tried to use the secret system in clearskies, but didn't specify a secret manager."
18
18
  )
19
19
 
20
- def list_secrets(self, path):
20
+ def list_secrets(self, path: str) -> list[Any]:
21
21
  raise NotImplementedError(
22
22
  "It looks like you tried to use the secret system in clearskies, but didn't specify a secret manager."
23
23
  )
24
24
 
25
- def update(self, path, value):
25
+ def update(self, path: str, value: Any) -> None:
26
26
  raise NotImplementedError(
27
27
  "It looks like you tried to use the secret system in clearskies, but didn't specify a secret manager."
28
28
  )
29
29
 
30
- def upsert(self, path, value):
30
+ def upsert(self, path: str, value: Any) -> None:
31
31
  raise NotImplementedError(
32
32
  "It looks like you tried to use the secret system in clearskies, but didn't specify a secret manager."
33
33
  )
34
34
 
35
- def list_sub_folders(self, path, value):
35
+ def list_sub_folders(self, path: str) -> list[Any]:
36
36
  raise NotImplementedError(
37
37
  "It looks like you tried to use the secret system in clearskies, but didn't specify a secret manager."
38
38
  )
@@ -0,0 +1,8 @@
1
+ from clearskies.configurable import Configurable
2
+
3
+
4
+ class SecurityHeader(Configurable):
5
+ is_cors = False
6
+
7
+ def set_headers_for_input_output(self, input_output):
8
+ raise NotImplementedError()
@@ -1,11 +1,11 @@
1
- from .cache_control import cache_control
2
- from .cors import cors
3
- from .csp import csp
4
- from .hsts import hsts
1
+ from clearskies.security_headers.cache_control import CacheControl
2
+ from clearskies.security_headers.cors import Cors
3
+ from clearskies.security_headers.csp import Csp
4
+ from clearskies.security_headers.hsts import Hsts
5
5
 
6
6
  __all__ = [
7
- "cache_control",
8
- "cors",
9
- "csp",
10
- "hsts",
7
+ "CacheControl",
8
+ "Cors",
9
+ "Csp",
10
+ "Hsts",
11
11
  ]