clear-skies 1.22.10__py3-none-any.whl → 2.0.23__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.
Files changed (368) hide show
  1. clear_skies-2.0.23.dist-info/METADATA +76 -0
  2. clear_skies-2.0.23.dist-info/RECORD +265 -0
  3. {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info}/WHEEL +1 -1
  4. clearskies/__init__.py +37 -21
  5. clearskies/action.py +7 -0
  6. clearskies/authentication/__init__.py +8 -39
  7. clearskies/authentication/authentication.py +44 -0
  8. clearskies/authentication/authorization.py +14 -8
  9. clearskies/authentication/authorization_pass_through.py +14 -10
  10. clearskies/authentication/jwks.py +135 -58
  11. clearskies/authentication/public.py +3 -26
  12. clearskies/authentication/secret_bearer.py +515 -44
  13. clearskies/autodoc/formats/oai3_json/__init__.py +2 -2
  14. clearskies/autodoc/formats/oai3_json/oai3_json.py +11 -9
  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 +10 -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 +16 -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 +1118 -280
  42. clearskies/backends/backend.py +54 -85
  43. clearskies/backends/cursor_backend.py +246 -191
  44. clearskies/backends/memory_backend.py +514 -208
  45. clearskies/backends/secrets_backend.py +68 -31
  46. clearskies/column.py +1221 -0
  47. clearskies/columns/__init__.py +71 -0
  48. clearskies/columns/audit.py +306 -0
  49. clearskies/columns/belongs_to_id.py +478 -0
  50. clearskies/columns/belongs_to_model.py +129 -0
  51. clearskies/columns/belongs_to_self.py +109 -0
  52. clearskies/columns/boolean.py +110 -0
  53. clearskies/columns/category_tree.py +273 -0
  54. clearskies/columns/category_tree_ancestors.py +51 -0
  55. clearskies/columns/category_tree_children.py +126 -0
  56. clearskies/columns/category_tree_descendants.py +48 -0
  57. clearskies/columns/created.py +92 -0
  58. clearskies/columns/created_by_authorization_data.py +114 -0
  59. clearskies/columns/created_by_header.py +103 -0
  60. clearskies/columns/created_by_ip.py +90 -0
  61. clearskies/columns/created_by_routing_data.py +102 -0
  62. clearskies/columns/created_by_user_agent.py +89 -0
  63. clearskies/columns/date.py +232 -0
  64. clearskies/columns/datetime.py +284 -0
  65. clearskies/columns/email.py +78 -0
  66. clearskies/columns/float.py +149 -0
  67. clearskies/columns/has_many.py +529 -0
  68. clearskies/columns/has_many_self.py +62 -0
  69. clearskies/columns/has_one.py +21 -0
  70. clearskies/columns/integer.py +158 -0
  71. clearskies/columns/json.py +126 -0
  72. clearskies/columns/many_to_many_ids.py +335 -0
  73. clearskies/columns/many_to_many_ids_with_data.py +274 -0
  74. clearskies/columns/many_to_many_models.py +156 -0
  75. clearskies/columns/many_to_many_pivots.py +132 -0
  76. clearskies/columns/phone.py +162 -0
  77. clearskies/columns/select.py +95 -0
  78. clearskies/columns/string.py +102 -0
  79. clearskies/columns/timestamp.py +164 -0
  80. clearskies/columns/updated.py +107 -0
  81. clearskies/columns/uuid.py +83 -0
  82. clearskies/configs/README.md +105 -0
  83. clearskies/configs/__init__.py +170 -0
  84. clearskies/configs/actions.py +43 -0
  85. clearskies/configs/any.py +15 -0
  86. clearskies/configs/any_dict.py +24 -0
  87. clearskies/configs/any_dict_or_callable.py +25 -0
  88. clearskies/configs/authentication.py +23 -0
  89. clearskies/configs/authorization.py +23 -0
  90. clearskies/configs/boolean.py +18 -0
  91. clearskies/configs/boolean_or_callable.py +20 -0
  92. clearskies/configs/callable_config.py +20 -0
  93. clearskies/configs/columns.py +34 -0
  94. clearskies/configs/conditions.py +30 -0
  95. clearskies/configs/config.py +26 -0
  96. clearskies/configs/datetime.py +20 -0
  97. clearskies/configs/datetime_or_callable.py +21 -0
  98. clearskies/configs/email.py +10 -0
  99. clearskies/configs/email_list.py +17 -0
  100. clearskies/configs/email_list_or_callable.py +17 -0
  101. clearskies/configs/email_or_email_list_or_callable.py +59 -0
  102. clearskies/configs/endpoint.py +23 -0
  103. clearskies/configs/endpoint_list.py +29 -0
  104. clearskies/configs/float.py +18 -0
  105. clearskies/configs/float_or_callable.py +20 -0
  106. clearskies/configs/headers.py +28 -0
  107. clearskies/configs/integer.py +18 -0
  108. clearskies/configs/integer_or_callable.py +20 -0
  109. clearskies/configs/joins.py +30 -0
  110. clearskies/configs/list_any_dict.py +32 -0
  111. clearskies/configs/list_any_dict_or_callable.py +33 -0
  112. clearskies/configs/model_class.py +35 -0
  113. clearskies/configs/model_column.py +67 -0
  114. clearskies/configs/model_columns.py +58 -0
  115. clearskies/configs/model_destination_name.py +26 -0
  116. clearskies/configs/model_to_id_column.py +45 -0
  117. clearskies/configs/readable_model_column.py +11 -0
  118. clearskies/configs/readable_model_columns.py +11 -0
  119. clearskies/configs/schema.py +23 -0
  120. clearskies/configs/searchable_model_columns.py +11 -0
  121. clearskies/configs/security_headers.py +39 -0
  122. clearskies/configs/select.py +28 -0
  123. clearskies/configs/select_list.py +49 -0
  124. clearskies/configs/string.py +31 -0
  125. clearskies/configs/string_dict.py +34 -0
  126. clearskies/configs/string_list.py +47 -0
  127. clearskies/configs/string_list_or_callable.py +48 -0
  128. clearskies/configs/string_or_callable.py +18 -0
  129. clearskies/configs/timedelta.py +20 -0
  130. clearskies/configs/timezone.py +20 -0
  131. clearskies/configs/url.py +25 -0
  132. clearskies/configs/validators.py +45 -0
  133. clearskies/configs/writeable_model_column.py +11 -0
  134. clearskies/configs/writeable_model_columns.py +11 -0
  135. clearskies/configurable.py +78 -0
  136. clearskies/contexts/__init__.py +8 -8
  137. clearskies/contexts/cli.py +129 -43
  138. clearskies/contexts/context.py +93 -56
  139. clearskies/contexts/wsgi.py +79 -33
  140. clearskies/contexts/wsgi_ref.py +87 -0
  141. clearskies/cursors/__init__.py +7 -0
  142. clearskies/cursors/cursor.py +166 -0
  143. clearskies/cursors/from_environment/__init__.py +5 -0
  144. clearskies/cursors/from_environment/mysql.py +51 -0
  145. clearskies/cursors/from_environment/postgresql.py +49 -0
  146. clearskies/cursors/from_environment/sqlite.py +35 -0
  147. clearskies/cursors/mysql.py +61 -0
  148. clearskies/cursors/postgresql.py +61 -0
  149. clearskies/cursors/sqlite.py +62 -0
  150. clearskies/decorators.py +33 -0
  151. clearskies/decorators.pyi +10 -0
  152. clearskies/di/__init__.py +11 -7
  153. clearskies/di/additional_config.py +115 -4
  154. clearskies/di/additional_config_auto_import.py +12 -0
  155. clearskies/di/di.py +714 -125
  156. clearskies/di/inject/__init__.py +23 -0
  157. clearskies/di/inject/akeyless_sdk.py +16 -0
  158. clearskies/di/inject/by_class.py +24 -0
  159. clearskies/di/inject/by_name.py +22 -0
  160. clearskies/di/inject/di.py +16 -0
  161. clearskies/di/inject/environment.py +15 -0
  162. clearskies/di/inject/input_output.py +19 -0
  163. clearskies/di/inject/now.py +16 -0
  164. clearskies/di/inject/requests.py +16 -0
  165. clearskies/di/inject/secrets.py +15 -0
  166. clearskies/di/inject/utcnow.py +16 -0
  167. clearskies/di/inject/uuid.py +16 -0
  168. clearskies/di/injectable.py +32 -0
  169. clearskies/di/injectable_properties.py +131 -0
  170. clearskies/end.py +219 -0
  171. clearskies/endpoint.py +1303 -0
  172. clearskies/endpoint_group.py +333 -0
  173. clearskies/endpoints/__init__.py +25 -0
  174. clearskies/endpoints/advanced_search.py +519 -0
  175. clearskies/endpoints/callable.py +382 -0
  176. clearskies/endpoints/create.py +201 -0
  177. clearskies/endpoints/delete.py +133 -0
  178. clearskies/endpoints/get.py +267 -0
  179. clearskies/endpoints/health_check.py +181 -0
  180. clearskies/endpoints/list.py +567 -0
  181. clearskies/endpoints/restful_api.py +417 -0
  182. clearskies/endpoints/schema.py +185 -0
  183. clearskies/endpoints/simple_search.py +279 -0
  184. clearskies/endpoints/update.py +188 -0
  185. clearskies/environment.py +7 -3
  186. clearskies/exceptions/__init__.py +19 -0
  187. clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
  188. clearskies/exceptions/missing_dependency.py +2 -0
  189. clearskies/exceptions/moved_permanently.py +3 -0
  190. clearskies/exceptions/moved_temporarily.py +3 -0
  191. clearskies/functional/__init__.py +2 -2
  192. clearskies/functional/json.py +47 -0
  193. clearskies/functional/routing.py +92 -0
  194. clearskies/functional/string.py +19 -11
  195. clearskies/functional/validations.py +61 -9
  196. clearskies/input_outputs/__init__.py +9 -7
  197. clearskies/input_outputs/cli.py +135 -160
  198. clearskies/input_outputs/exceptions/__init__.py +6 -1
  199. clearskies/input_outputs/headers.py +54 -0
  200. clearskies/input_outputs/input_output.py +77 -123
  201. clearskies/input_outputs/programmatic.py +62 -0
  202. clearskies/input_outputs/wsgi.py +36 -48
  203. clearskies/model.py +1874 -193
  204. clearskies/query/__init__.py +12 -0
  205. clearskies/query/condition.py +228 -0
  206. clearskies/query/join.py +136 -0
  207. clearskies/query/query.py +193 -0
  208. clearskies/query/sort.py +27 -0
  209. clearskies/schema.py +82 -0
  210. clearskies/secrets/__init__.py +4 -31
  211. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
  212. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
  213. clearskies/secrets/akeyless.py +421 -155
  214. clearskies/secrets/exceptions/__init__.py +7 -1
  215. clearskies/secrets/exceptions/not_found_error.py +2 -0
  216. clearskies/secrets/exceptions/permissions_error.py +2 -0
  217. clearskies/secrets/secrets.py +12 -11
  218. clearskies/security_header.py +17 -0
  219. clearskies/security_headers/__init__.py +8 -8
  220. clearskies/security_headers/cache_control.py +47 -109
  221. clearskies/security_headers/cors.py +38 -92
  222. clearskies/security_headers/csp.py +76 -150
  223. clearskies/security_headers/hsts.py +14 -15
  224. clearskies/typing.py +11 -0
  225. clearskies/validator.py +36 -0
  226. clearskies/validators/__init__.py +33 -0
  227. clearskies/validators/after_column.py +61 -0
  228. clearskies/validators/before_column.py +15 -0
  229. clearskies/validators/in_the_future.py +29 -0
  230. clearskies/validators/in_the_future_at_least.py +13 -0
  231. clearskies/validators/in_the_future_at_most.py +12 -0
  232. clearskies/validators/in_the_past.py +29 -0
  233. clearskies/validators/in_the_past_at_least.py +12 -0
  234. clearskies/validators/in_the_past_at_most.py +12 -0
  235. clearskies/validators/maximum_length.py +25 -0
  236. clearskies/validators/maximum_value.py +28 -0
  237. clearskies/validators/minimum_length.py +25 -0
  238. clearskies/validators/minimum_value.py +28 -0
  239. clearskies/{input_requirements → validators}/required.py +18 -9
  240. clearskies/validators/timedelta.py +58 -0
  241. clearskies/validators/unique.py +28 -0
  242. clear_skies-1.22.10.dist-info/METADATA +0 -47
  243. clear_skies-1.22.10.dist-info/RECORD +0 -213
  244. clearskies/application.py +0 -29
  245. clearskies/authentication/auth0_jwks.py +0 -118
  246. clearskies/authentication/auth_exception.py +0 -2
  247. clearskies/authentication/jwks_jwcrypto.py +0 -51
  248. clearskies/backends/api_get_only_backend.py +0 -48
  249. clearskies/backends/example_backend.py +0 -43
  250. clearskies/backends/file_backend.py +0 -48
  251. clearskies/backends/json_backend.py +0 -7
  252. clearskies/backends/restful_api_advanced_search_backend.py +0 -103
  253. clearskies/binding_config.py +0 -16
  254. clearskies/column_types/__init__.py +0 -203
  255. clearskies/column_types/audit.py +0 -249
  256. clearskies/column_types/belongs_to.py +0 -271
  257. clearskies/column_types/boolean.py +0 -60
  258. clearskies/column_types/category_tree.py +0 -304
  259. clearskies/column_types/column.py +0 -373
  260. clearskies/column_types/created.py +0 -26
  261. clearskies/column_types/created_by_authorization_data.py +0 -26
  262. clearskies/column_types/created_by_header.py +0 -24
  263. clearskies/column_types/created_by_ip.py +0 -17
  264. clearskies/column_types/created_by_routing_data.py +0 -25
  265. clearskies/column_types/created_by_user_agent.py +0 -17
  266. clearskies/column_types/created_micro.py +0 -26
  267. clearskies/column_types/datetime.py +0 -109
  268. clearskies/column_types/datetime_micro.py +0 -13
  269. clearskies/column_types/email.py +0 -18
  270. clearskies/column_types/float.py +0 -43
  271. clearskies/column_types/has_many.py +0 -179
  272. clearskies/column_types/has_one.py +0 -58
  273. clearskies/column_types/integer.py +0 -41
  274. clearskies/column_types/json.py +0 -25
  275. clearskies/column_types/many_to_many.py +0 -278
  276. clearskies/column_types/many_to_many_with_data.py +0 -162
  277. clearskies/column_types/phone.py +0 -48
  278. clearskies/column_types/select.py +0 -11
  279. clearskies/column_types/string.py +0 -24
  280. clearskies/column_types/timestamp.py +0 -73
  281. clearskies/column_types/updated.py +0 -26
  282. clearskies/column_types/updated_micro.py +0 -26
  283. clearskies/column_types/uuid.py +0 -25
  284. clearskies/columns.py +0 -123
  285. clearskies/condition_parser.py +0 -172
  286. clearskies/contexts/build_context.py +0 -54
  287. clearskies/contexts/convert_to_application.py +0 -190
  288. clearskies/contexts/extract_handler.py +0 -37
  289. clearskies/contexts/test.py +0 -94
  290. clearskies/decorators/__init__.py +0 -39
  291. clearskies/decorators/auth0_jwks.py +0 -22
  292. clearskies/decorators/authorization.py +0 -10
  293. clearskies/decorators/binding_classes.py +0 -9
  294. clearskies/decorators/binding_modules.py +0 -9
  295. clearskies/decorators/bindings.py +0 -9
  296. clearskies/decorators/create.py +0 -10
  297. clearskies/decorators/delete.py +0 -10
  298. clearskies/decorators/docs.py +0 -14
  299. clearskies/decorators/get.py +0 -10
  300. clearskies/decorators/jwks.py +0 -26
  301. clearskies/decorators/merge.py +0 -124
  302. clearskies/decorators/patch.py +0 -10
  303. clearskies/decorators/post.py +0 -10
  304. clearskies/decorators/public.py +0 -11
  305. clearskies/decorators/response_headers.py +0 -10
  306. clearskies/decorators/return_raw_response.py +0 -9
  307. clearskies/decorators/schema.py +0 -10
  308. clearskies/decorators/secret_bearer.py +0 -24
  309. clearskies/decorators/security_headers.py +0 -10
  310. clearskies/di/standard_dependencies.py +0 -151
  311. clearskies/di/test_module/__init__.py +0 -6
  312. clearskies/di/test_module/another_module/__init__.py +0 -2
  313. clearskies/di/test_module/module_class.py +0 -5
  314. clearskies/handlers/__init__.py +0 -41
  315. clearskies/handlers/advanced_search.py +0 -271
  316. clearskies/handlers/base.py +0 -479
  317. clearskies/handlers/callable.py +0 -191
  318. clearskies/handlers/create.py +0 -35
  319. clearskies/handlers/crud_by_method.py +0 -18
  320. clearskies/handlers/database_connector.py +0 -32
  321. clearskies/handlers/delete.py +0 -61
  322. clearskies/handlers/exceptions/__init__.py +0 -5
  323. clearskies/handlers/exceptions/not_found.py +0 -3
  324. clearskies/handlers/get.py +0 -156
  325. clearskies/handlers/health_check.py +0 -59
  326. clearskies/handlers/input_processing.py +0 -79
  327. clearskies/handlers/list.py +0 -530
  328. clearskies/handlers/mygrations.py +0 -82
  329. clearskies/handlers/request_method_routing.py +0 -47
  330. clearskies/handlers/restful_api.py +0 -218
  331. clearskies/handlers/routing.py +0 -62
  332. clearskies/handlers/schema_helper.py +0 -128
  333. clearskies/handlers/simple_routing.py +0 -206
  334. clearskies/handlers/simple_routing_route.py +0 -192
  335. clearskies/handlers/simple_search.py +0 -136
  336. clearskies/handlers/update.py +0 -96
  337. clearskies/handlers/write.py +0 -193
  338. clearskies/input_requirements/__init__.py +0 -78
  339. clearskies/input_requirements/after.py +0 -36
  340. clearskies/input_requirements/before.py +0 -36
  341. clearskies/input_requirements/in_the_future_at_least.py +0 -19
  342. clearskies/input_requirements/in_the_future_at_most.py +0 -19
  343. clearskies/input_requirements/in_the_past_at_least.py +0 -19
  344. clearskies/input_requirements/in_the_past_at_most.py +0 -19
  345. clearskies/input_requirements/maximum_length.py +0 -19
  346. clearskies/input_requirements/maximum_value.py +0 -19
  347. clearskies/input_requirements/minimum_length.py +0 -22
  348. clearskies/input_requirements/minimum_value.py +0 -19
  349. clearskies/input_requirements/requirement.py +0 -25
  350. clearskies/input_requirements/time_delta.py +0 -38
  351. clearskies/input_requirements/unique.py +0 -18
  352. clearskies/mocks/__init__.py +0 -7
  353. clearskies/mocks/input_output.py +0 -124
  354. clearskies/mocks/models.py +0 -142
  355. clearskies/models.py +0 -350
  356. clearskies/security_headers/base.py +0 -12
  357. clearskies/tests/simple_api/models/__init__.py +0 -2
  358. clearskies/tests/simple_api/models/status.py +0 -23
  359. clearskies/tests/simple_api/models/user.py +0 -21
  360. clearskies/tests/simple_api/users_api.py +0 -64
  361. {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info/licenses}/LICENSE +0 -0
  362. /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
  363. /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
  364. /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
  365. /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
  366. /clearskies/{secrets/exceptions → exceptions}/not_found.py +0 -0
  367. /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
  368. /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
@@ -0,0 +1,23 @@
1
+ from clearskies.di.inject.by_class import ByClass
2
+ from clearskies.di.inject.by_name import ByName
3
+ from clearskies.di.inject.di import Di
4
+ from clearskies.di.inject.environment import Environment
5
+ from clearskies.di.inject.input_output import InputOutput
6
+ from clearskies.di.inject.now import Now
7
+ from clearskies.di.inject.requests import Requests
8
+ from clearskies.di.inject.secrets import Secrets
9
+ from clearskies.di.inject.utcnow import Utcnow
10
+ from clearskies.di.inject.uuid import Uuid
11
+
12
+ __all__ = [
13
+ "ByClass",
14
+ "ByName",
15
+ "Di",
16
+ "Environment",
17
+ "InputOutput",
18
+ "Now",
19
+ "Requests",
20
+ "Secrets",
21
+ "Utcnow",
22
+ "Uuid",
23
+ ]
@@ -0,0 +1,16 @@
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
+ self.initiated_guard(instance)
16
+ return self._di.build_from_name("akeyless_sdk", cache=self.cache)
@@ -0,0 +1,24 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class ByClass(Injectable):
9
+ def __init__(self, cls: type, cache: bool = True):
10
+ if not isinstance(cls, type):
11
+ raise TypeError(
12
+ f"I expected a class for the first argument to clearskies.di.inject.ByClass, but I received an object of type '{cls.__class__.__name__}' instead."
13
+ )
14
+ self.cls = cls
15
+ self.cache = cache
16
+
17
+ def __get__(self, instance, parent) -> Any:
18
+ if instance is None:
19
+ return self # type: ignore
20
+
21
+ self.initiated_guard(instance)
22
+ if self.cls in self._di._class_overrides_by_class:
23
+ return self._di.build_class(self._di._class_overrides_by_class[self.cls], cache=self.cache)
24
+ return self._di.build_class(self.cls, cache=self.cache)
@@ -0,0 +1,22 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class ByName(Injectable):
9
+ def __init__(self, name: str, cache: bool = True):
10
+ if not isinstance(name, str):
11
+ raise TypeError(
12
+ f"I expected a string for the first argument to clearskies.di.inject.ByName, but I received an object of type '{name.__class__.__name__}' instead."
13
+ )
14
+ self.name = name
15
+ self.cache = cache
16
+
17
+ def __get__(self, instance, parent) -> Any:
18
+ if instance is None:
19
+ return self # type: ignore
20
+
21
+ self.initiated_guard(instance)
22
+ return self._di.build_from_name(self.name, cache=self.cache)
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class Di(Injectable):
9
+ def __init__(self):
10
+ pass
11
+
12
+ def __get__(self, instance, parent) -> Any:
13
+ if instance is None:
14
+ return self # type: ignore
15
+ self.initiated_guard(instance)
16
+ return self._di
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from clearskies.di.injectable import Injectable
4
+ from clearskies.environment import Environment as EnvironmentDependency
5
+
6
+
7
+ class Environment(Injectable):
8
+ def __init__(self, cache: bool = True):
9
+ self.cache = cache
10
+
11
+ def __get__(self, instance, parent) -> EnvironmentDependency:
12
+ if instance is None:
13
+ return self # type: ignore
14
+ self.initiated_guard(instance)
15
+ return self._di.build_from_name("environment", cache=self.cache)
@@ -0,0 +1,19 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies.input_outputs.input_output import InputOutput as InputOuputDependency
9
+
10
+
11
+ class InputOutput(Injectable):
12
+ def __init__(self):
13
+ pass
14
+
15
+ def __get__(self, instance, parent) -> InputOuputDependency:
16
+ if instance is None:
17
+ return self # type: ignore
18
+ self.initiated_guard(instance)
19
+ return self._di.build_from_name("input_output", cache=True)
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class Now(Injectable):
9
+ def __init__(self, cache: bool = False):
10
+ self.cache = cache
11
+
12
+ def __get__(self, instance, parent) -> datetime.datetime:
13
+ if instance is None:
14
+ return self # type: ignore
15
+ self.initiated_guard(instance)
16
+ return self._di.build_from_name("now", cache=self.cache)
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import requests
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class Requests(Injectable):
9
+ def __init__(self, cache: bool = True):
10
+ self.cache = cache
11
+
12
+ def __get__(self, instance, parent) -> requests.Session:
13
+ if instance is None:
14
+ return self # type: ignore
15
+ self.initiated_guard(instance)
16
+ return self._di.build_from_name("requests", cache=self.cache)
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ from clearskies.di.injectable import Injectable
4
+ from clearskies.secrets.secrets import Secrets as SecretsHelper
5
+
6
+
7
+ class Secrets(Injectable):
8
+ def __init__(self, cache: bool = True):
9
+ self.cache = cache
10
+
11
+ def __get__(self, instance, parent) -> SecretsHelper:
12
+ if instance is None:
13
+ return self # type: ignore
14
+ self.initiated_guard(instance)
15
+ return self._di.build_from_name("secrets", cache=self.cache)
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class Utcnow(Injectable):
9
+ def __init__(self, cache: bool = False):
10
+ self.cache = cache
11
+
12
+ def __get__(self, instance, parent) -> datetime.datetime:
13
+ if instance is None:
14
+ return self # type: ignore
15
+ self.initiated_guard(instance)
16
+ return self._di.build_from_name("utcnow", cache=self.cache)
@@ -0,0 +1,16 @@
1
+ from __future__ import annotations
2
+
3
+ import types
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+
8
+ class Uuid(Injectable):
9
+ def __init__(self, cache: bool = True):
10
+ self.cache = cache
11
+
12
+ def __get__(self, instance, parent) -> types.ModuleType:
13
+ if instance is None:
14
+ return self # type: ignore
15
+ self.initiated_guard(instance)
16
+ return self._di.build_from_name("uuid", cache=self.cache) # type: ignore
@@ -0,0 +1,32 @@
1
+ from abc import ABC, abstractmethod
2
+ from typing import Any
3
+
4
+
5
+ class Injectable(ABC):
6
+ _di: Any = None
7
+
8
+ def initiated_guard(self, instance):
9
+ if self._di:
10
+ return
11
+
12
+ reference = instance.__class__.__name__ + "."
13
+ my_id = id(self)
14
+ cls = instance.__class__
15
+ for attribute_name in dir(instance):
16
+ if id(getattr(cls, attribute_name)) != my_id:
17
+ continue
18
+ reference += attribute_name
19
+ raise ValueError(
20
+ f"There was an attempt to get a value out of '{reference}' but the injectable hasn't been properly "
21
+ + "initialized. This usually means that objects are being created outside of the normal Di system. "
22
+ + "This most often happens when working with classes that are both configurable and have injectables: "
23
+ + "in this case, your class can't make use of any injectables in the __init__ function, as the injectables "
24
+ + "will only be provided later. Any use of injectables must be deferred until after object creation."
25
+ )
26
+
27
+ def set_di(self, di):
28
+ self._di = di
29
+
30
+ @abstractmethod
31
+ def __get__(self, instance, parent):
32
+ pass
@@ -0,0 +1,131 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Self
4
+
5
+ from clearskies.di.injectable import Injectable
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies.di import Di
9
+
10
+
11
+ class InjectableProperties:
12
+ """
13
+ Fetch dependencies via properties rather than constructor arguments.
14
+
15
+ This class allows you to specify dependencies by setting them as class properties instead of constructor
16
+ arguments. This is common in clearskies as it helps make easily reusable classes - configuration can
17
+ go in the constructor of the class, allowing the developer to directly instantiate it, and then the DI system
18
+ will come by afterwards and provide the necessary dependencies.
19
+
20
+ After adding InjectableProperties as a parent of your class, you have two ways to specify your dependencies:
21
+
22
+ 1. By using the classes in the `clearskies.di.inject.*`module.
23
+ 2. By directly attaching objects which also use the `InjectableProperties` class.
24
+
25
+ The following table shows the dependencies that can be injected as properties via the clearskies.di.inject module:
26
+
27
+ | Class | Type | Result |
28
+ |----------------------------------|--------------------------------------|-------------------------------------------------|
29
+ | clearskies.di.inject.ByClass | N/A | The specified class will be built |
30
+ | clearskies.di.inject.ByName | N/A | The specified dependnecy name will be built |
31
+ | clearskies.di.inject.Cursor | N/A | The PyMySQL cursor |
32
+ | clearskies.di.inject.Di | N/A | The dependency injection container itself |
33
+ | clearskies.di.inject.Environment | clearskies.Environment | The environment helper |
34
+ | clearskies.di.inject.InputOutput | clearskies.input_outputs.InputOutput | The InputOutput object for the current request |
35
+ | clearskies.di.inject.Now | datetime.datetime | The current time (no timezone) |
36
+ | clearskies.di.inject.Requests | requests.Session | A requests session |
37
+ | clearskies.di.inject.Utcnow | datetime.datetime | The current time (tzinfo=datetime.timezone.utc) |
38
+
39
+ Note: now/utcnow are not cached, so you'll get the current time everytime you get a value out of the class property,
40
+ unless a specific time has been set on the dependency injection container.
41
+
42
+ Here's an example:
43
+
44
+ ```python
45
+ import clearskies
46
+ import time
47
+ import clearskies.decorators
48
+
49
+
50
+ class MyOtherThing(clearskies.di.InjectableProperties):
51
+ now = clearskies.di.inject.Now()
52
+
53
+
54
+ class ReusableClass(clearskies.Configurable, clearskies.di.InjectableProperties):
55
+ my_int = clearskies.configs.Integer(required=True)
56
+ some_number = clearskies.di.inject.ByName("some_number")
57
+ my_other_thing = clearskies.di.inject.ByClass(MyOtherThing)
58
+
59
+ @clearskies.decorators.parameters_to_properties
60
+ def __init__(self, my_int: int):
61
+ self.finalize_and_validate_configuration()
62
+
63
+ def my_value(self) -> int:
64
+ return self.my_int * self.some_number
65
+
66
+
67
+ class MyClass(clearskies.di.InjectableProperties):
68
+ reusable = ReusableClass(5)
69
+
70
+
71
+ class MyOtherClass(clearskies.di.InjectableProperties):
72
+ reusable = ReusableClass(10)
73
+
74
+
75
+ di = clearskies.di.Di(
76
+ bindings={
77
+ "some_number": 10,
78
+ }
79
+ )
80
+
81
+ my_class = di.build(MyClass)
82
+ print(my_class.reusable.my_value()) # prints 50
83
+
84
+ my_other_class = di.build(MyOtherClass)
85
+ print(my_other_class.reusable.my_value()) # prints 100
86
+
87
+ start = my_class.reusable.my_other_thing.now
88
+ time.sleep(1)
89
+ stop = my_class.reusable.my_other_thing.now
90
+ print((stop - start).seconds) # prints 1
91
+ ```
92
+ """
93
+
94
+ _injectables_loaded: str = ""
95
+
96
+ @classmethod
97
+ def injectable_properties(cls, di: Di):
98
+ # you would think that I would be able to just use a simple true/false flag attached to the class,
99
+ # but I'm having this weird issue where (when I tried that) the flag was being shared between classes.
100
+ # It shouldn't happen like that, but it is, so there is probably something subtle going on that I
101
+ # haven't figured out yet, but this also works identitally, so :shrug:.
102
+ # Also, keep track of the id of DI. We use class level caching but tests often use multiple DI containers
103
+ # in one run, which means that we need to re-inject dependencies if we get a new DI container
104
+ cache_name = str(cls) + str(id(di))
105
+ if cache_name == cls._injectables_loaded:
106
+ return
107
+
108
+ injectable_descriptors: list[Any] = []
109
+ injectable_properties: list[Self] = []
110
+ for attribute_name in dir(cls):
111
+ # Per the docs above, we want to inject properties for one of two things: the injectables from clearskies.di.inject,
112
+ # and any object that itself extends this class. This is mildly tricky because the injectables are descriptors, and
113
+ # so we get them using getattr on the class, while if it's not a descriptor, then we want to use getattr on self.
114
+ # The important part here is that we modify descriptors at the class level, so the actual injected values have to
115
+ # be stored in self, and not in the descriptor object. When it's not a descriptor, then we can modify the object
116
+ # directly (since we're operating at the object level, not class level). Either way, while we go, let's keep track
117
+ # of what our dependencies are and which ones are cached, so we only have to list the objects attributes the first time.
118
+ attribute = getattr(cls, attribute_name)
119
+
120
+ if di.has_class_override(attribute.__class__):
121
+ setattr(cls, attribute_name, di.get_override_by_class(attribute))
122
+ continue
123
+
124
+ if issubclass(attribute.__class__, Injectable):
125
+ attribute.set_di(di)
126
+ continue
127
+
128
+ if hasattr(attribute, "injectable_properties"):
129
+ attribute.injectable_properties(di)
130
+
131
+ cls._injectables_loaded = cache_name
clearskies/end.py ADDED
@@ -0,0 +1,219 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ if TYPE_CHECKING:
7
+ from clearskies import typing
8
+ from clearskies.input_outputs import InputOutput
9
+
10
+ from clearskies import configs, configurable, di, exceptions
11
+ from clearskies.authentication import Authorization, Public
12
+ from clearskies.functional import string
13
+
14
+
15
+ class End(
16
+ ABC,
17
+ configurable.Configurable,
18
+ di.InjectableProperties,
19
+ ):
20
+ """
21
+ DRY for endpoint and endpoint groups.
22
+
23
+ This class is just here to hold some common functionality between Endpoints and EndpointGroups.
24
+ The two classes have plenty of overlap but are different enough that I don't want either to inherit
25
+ from the other.
26
+ """
27
+
28
+ di = di.inject.Di()
29
+
30
+ url = configs.String(required=False, default="")
31
+
32
+ authentication = configs.Authentication(default=None)
33
+
34
+ authorization = configs.Authorization(default=None)
35
+
36
+ internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
37
+ external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
38
+ response_headers = configs.StringListOrCallable(default=[])
39
+ authentication = configs.Authentication(default=Public())
40
+ authorization = configs.Authorization(default=Authorization())
41
+ security_headers = configs.SecurityHeaders(default=[])
42
+
43
+ cors_header: SecurityHeader = None # type: ignore
44
+ has_cors: bool = False
45
+
46
+ def add_url_prefix(self, prefix: str) -> None:
47
+ self.url = (prefix.rstrip("/") + "/" + self.url.lstrip("/")).lstrip("/")
48
+
49
+ def top_level_authentication_and_authorization(self, input_output: InputOutput) -> None:
50
+ """
51
+ Handle authentication and authorization for this endpoint.
52
+
53
+ In the event of an AuthN/AuthZ issue, raise an exception. Otherwise, return None
54
+ """
55
+ if not self.authentication:
56
+ return
57
+ try:
58
+ if not self.authentication.authenticate(input_output):
59
+ raise exceptions.Authentication("Not Authenticated")
60
+ except exceptions.ClientError as client_error:
61
+ raise exceptions.Authentication(str(client_error))
62
+ if self.authorization:
63
+ try:
64
+ if not self.authorization.gate(input_output.authorization_data, input_output):
65
+ raise exceptions.Authorization("Not Authorized")
66
+ except exceptions.ClientError as client_error:
67
+ raise exceptions.Authorization(str(client_error))
68
+
69
+ def __call__(self, input_output: InputOutput) -> Any:
70
+ """
71
+ Execute the endpoint.
72
+
73
+ This function mostly just checks AuthN/AuthZ and then passes along control to the handle method.
74
+ It also checks for all the appropriate exceptions from clearskies.exceptions and turns those into the
75
+ expected response. As a result, when building a new endpoint, you normally modify the handle method
76
+ rather than this one.
77
+ """
78
+ # these two configs can have arbitrary classes attached, which may use injectable properties. Because they are
79
+ # hiding in configs, the system for automatically discovering these won't work, so we have to manually check them.
80
+ # We can't do this in the constructor because self.di hasn't been populated yet, and we can't do this in
81
+ # our own injectable_properties class method because we need to operate at the instance level
82
+ for config_name in ["authentication", "authorization"]:
83
+ config = getattr(self, config_name)
84
+ if config and hasattr(config, "injectable_properties"):
85
+ config.injectable_properties(self.di)
86
+
87
+ response = self.populate_routing_data(input_output)
88
+ if response:
89
+ return response
90
+
91
+ # catch everything when we do an AuthN/AuthZ check because we allow custom-defined classes,
92
+ # and this gives more flexibility (or possibly forgiveness) for how they raise exceptions.
93
+ try:
94
+ self.top_level_authentication_and_authorization(input_output)
95
+ except exceptions.Authentication as auth_error:
96
+ return self.error(input_output, str(auth_error), 401)
97
+ except exceptions.Authorization as auth_error:
98
+ return self.error(input_output, str(auth_error), 403)
99
+ except exceptions.NotFound as not_found:
100
+ return self.error(input_output, str(not_found), 404)
101
+ except exceptions.MovedPermanently as redirect:
102
+ return self.redirect(input_output, str(redirect), 302)
103
+ except exceptions.MovedTemporarily as redirect:
104
+ return self.redirect(input_output, str(redirect), 307)
105
+
106
+ try:
107
+ response = self.handle(input_output)
108
+ except exceptions.ClientError as client_error:
109
+ return self.error(input_output, str(client_error), 400)
110
+ except exceptions.InputErrors as input_errors:
111
+ return self.input_errors(input_output, input_errors.errors)
112
+ except exceptions.Authentication as auth_error:
113
+ return self.error(input_output, str(auth_error), 401)
114
+ except exceptions.Authorization as auth_error:
115
+ return self.error(input_output, str(auth_error), 403)
116
+ except exceptions.NotFound as auth_error:
117
+ return self.error(input_output, str(auth_error), 404)
118
+ except exceptions.MovedPermanently as redirect:
119
+ return self.redirect(input_output, str(redirect), 302)
120
+ except exceptions.MovedTemporarily as redirect:
121
+ return self.redirect(input_output, str(redirect), 307)
122
+
123
+ return response
124
+
125
+ def populate_routing_data(self, input_output: InputOutput) -> Any:
126
+ raise NotImplementedError()
127
+
128
+ def add_response_headers(self, input_output: InputOutput) -> None:
129
+ if self.response_headers:
130
+ if callable(self.response_headers):
131
+ response_headers = self.di.call_function(
132
+ self.response_headers, **input_output.get_context_for_callables()
133
+ )
134
+ else:
135
+ response_headers = self.response_headers
136
+
137
+ for index, response_header in enumerate(response_headers):
138
+ if not isinstance(response_header, str):
139
+ raise TypeError(
140
+ f"Invalid response header in entry #{index + 1}: the header should be a string, but I was given a type of '{response_header.__class__.__name__}' instead."
141
+ )
142
+ parts = response_header.split(":", 1)
143
+ if len(parts) != 2:
144
+ raise ValueError(
145
+ f"Invalid response header in entry #{index + 1}: the header should be a string in the form of 'key: value' but the given header did not have a colon to separate key and value."
146
+ )
147
+ input_output.response_headers.add(parts[0], parts[1])
148
+ for security_header in self.security_headers:
149
+ security_header.set_headers_for_input_output(input_output)
150
+
151
+ def respond(self, input_output: InputOutput, response: typing.response, status_code: int) -> Any:
152
+ self.add_response_headers(input_output)
153
+ return input_output.respond(response, status_code)
154
+
155
+ def respond_json(self, input_output: InputOutput, response_data: dict[str, Any], status_code: int) -> Any:
156
+ if "content-type" not in input_output.response_headers:
157
+ input_output.response_headers.add("content-type", "application/json")
158
+ return self.respond(input_output, self.normalize_response(response_data), status_code)
159
+
160
+ def normalize_response(self, response_data: dict[str, Any]) -> dict[str, Any]:
161
+ if "status" not in response_data:
162
+ raise ValueError("Huh, status got left out somehow")
163
+ return {
164
+ self.auto_case_internal_column_name("status"): self.auto_case_internal_column_name(response_data["status"]),
165
+ self.auto_case_internal_column_name("error"): response_data.get("error", ""),
166
+ self.auto_case_internal_column_name("data"): response_data.get("data", []),
167
+ self.auto_case_internal_column_name("pagination"): self.normalize_pagination(
168
+ response_data.get("pagination", {})
169
+ ),
170
+ self.auto_case_internal_column_name("input_errors"): response_data.get("input_errors", {}),
171
+ }
172
+
173
+ def normalize_pagination(self, pagination: dict[str, Any]) -> dict[str, Any]:
174
+ # pagination isn't always relevant so if it is completely empty then leave it that way
175
+ if not pagination:
176
+ return pagination
177
+ return {
178
+ self.auto_case_internal_column_name("number_results"): pagination.get("number_results", 0),
179
+ self.auto_case_internal_column_name("limit"): pagination.get("limit", 0),
180
+ self.auto_case_internal_column_name("next_page"): {
181
+ self.auto_case_internal_column_name(key): value
182
+ for (key, value) in pagination.get("next_page", {}).items()
183
+ },
184
+ }
185
+
186
+ def auto_case_internal_column_name(self, column_name: str) -> str:
187
+ if self.external_casing:
188
+ return string.swap_casing(column_name, "snake_case", self.external_casing)
189
+ return column_name
190
+
191
+ def auto_case_column_name(self, column_name: str, internal_to_external: bool) -> str:
192
+ if not self.internal_casing:
193
+ return column_name
194
+ if internal_to_external:
195
+ return string.swap_casing(
196
+ column_name,
197
+ self.internal_casing,
198
+ self.external_casing,
199
+ )
200
+ return string.swap_casing(
201
+ column_name,
202
+ self.external_casing,
203
+ self.internal_casing,
204
+ )
205
+
206
+ def error(self, input_output: InputOutput, message: str, status_code: int) -> Any:
207
+ """Return a client-side error (e.g. 400)."""
208
+ return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
209
+
210
+ def input_errors(self, input_output: InputOutput, errors: dict[str, str], status_code: int = 200) -> Any:
211
+ """Return input errors to the client."""
212
+ return self.respond_json(input_output, {"status": "input_errors", "input_errors": errors}, status_code)
213
+
214
+ def redirect(self, input_output: InputOutput, location: str, status_code: int) -> Any:
215
+ """Return a redirect response (e.g. 302)."""
216
+ return self.respond_json(input_output, {"status": "redirect", "location": location}, status_code)
217
+
218
+ def handle(self, input_output: InputOutput) -> Any:
219
+ raise NotImplementedError()