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,20 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.configs import config
6
+
7
+
8
+ class Timezone(config.Config):
9
+ def __set__(self, instance, value: datetime.timezone | None):
10
+ if value and not isinstance(value, datetime.timezone):
11
+ error_prefix = self._error_prefix(instance)
12
+ raise TypeError(
13
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a timezone (datetime.timezone)."
14
+ )
15
+ instance._set_config(self, value)
16
+
17
+ def __get__(self, instance, parent) -> datetime.timezone:
18
+ if not instance:
19
+ return self # type: ignore
20
+ return instance._get_config(self)
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from clearskies.configs import string
4
+ from clearskies.functional import routing
5
+
6
+
7
+ class Url(string.String):
8
+ def __set__(self, instance, value: str):
9
+ if value is None:
10
+ return
11
+
12
+ if not isinstance(value, str):
13
+ error_prefix = self._error_prefix(instance)
14
+ raise TypeError(
15
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a url parameter"
16
+ )
17
+ value = value.strip("/")
18
+
19
+ if value:
20
+ try:
21
+ routing.extract_url_parameter_name_map(value)
22
+ except ValueError as e:
23
+ error_prefix = self._error_prefix(instance)
24
+ raise ValueError(f"{error_prefix} {e}")
25
+ instance._set_config(self, value)
@@ -0,0 +1,45 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ from clearskies.configs import config
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies import typing
9
+
10
+
11
+ class Validators(config.Config):
12
+ """
13
+ Validator config.
14
+
15
+ A config that accepts various things that are accepted as validators in model columns:
16
+
17
+ 1. An instance of clearskies.validators.Validator
18
+ 2. A list of clearskies.validators.Validator
19
+
20
+ Incoming values are normalized to a list so that a list always comes out even if a non-list is provided.
21
+ """
22
+
23
+ def __set__(
24
+ self,
25
+ instance,
26
+ value: typing.validator | list[typing.validator],
27
+ ):
28
+ if not isinstance(value, list):
29
+ value = [value]
30
+
31
+ for index, item in enumerate(value):
32
+ if hasattr(item, "additional_write_columns") and hasattr(item, "check"):
33
+ continue
34
+
35
+ error_prefix = self._error_prefix(instance)
36
+ raise TypeError(
37
+ f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1} when a Validator is required"
38
+ )
39
+
40
+ instance._set_config(self, [*value])
41
+
42
+ def __get__(self, instance, parent) -> list[typing.validator]:
43
+ if not instance:
44
+ return self # type: ignore
45
+ return instance._get_config(self)
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from clearskies.configs import model_column
4
+
5
+
6
+ class WriteableModelColumn(model_column.ModelColumn):
7
+ def get_allowed_columns(self, model_class, column_configs):
8
+ return [name for (name, column) in column_configs.items() if column.is_writeable]
9
+
10
+ def my_description(self):
11
+ return "writeable column"
@@ -0,0 +1,11 @@
1
+ from __future__ import annotations
2
+
3
+ from clearskies.configs import model_columns
4
+
5
+
6
+ class WriteableModelColumns(model_columns.ModelColumns):
7
+ def get_allowed_columns(self, model_class, column_configs):
8
+ return [name for (name, column) in column_configs.items() if column.is_writeable]
9
+
10
+ def my_description(self):
11
+ return "writeable column"
@@ -0,0 +1,78 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import Any
4
+
5
+ from clearskies.configs import config
6
+
7
+
8
+ class Configurable:
9
+ _config: dict[str, Any] | None = None
10
+ _descriptor_config_map: dict[int, str] | None = None
11
+
12
+ def _set_config(self, descriptor, value):
13
+ if not self._config:
14
+ self._config = {}
15
+
16
+ self._config[self._descriptor_to_name(descriptor)] = value
17
+
18
+ def _get_config(self, descriptor):
19
+ if not self._config:
20
+ self._config = {}
21
+
22
+ name = self._descriptor_to_name(descriptor)
23
+ if name not in self._config:
24
+ raise KeyError(f"Attempt to fetch a config value named '{name}' but no value has been set for this config")
25
+ return self._config[name]
26
+
27
+ @classmethod
28
+ def _get_config_object(cls, attribute_name):
29
+ return getattr(cls, attribute_name)
30
+
31
+ @classmethod
32
+ def get_descriptor_config_map(cls):
33
+ if cls._descriptor_config_map:
34
+ return cls._descriptor_config_map
35
+
36
+ descriptor_config_map = {}
37
+ for attribute_name in dir(cls):
38
+ descriptor = getattr(cls, attribute_name)
39
+ if not isinstance(descriptor, config.Config):
40
+ continue
41
+
42
+ descriptor_config_map[id(descriptor)] = attribute_name
43
+
44
+ cls._descriptor_config_map = descriptor_config_map
45
+ return cls._descriptor_config_map
46
+
47
+ def _descriptor_to_name(self, descriptor):
48
+ descriptor_config_map = self.get_descriptor_config_map()
49
+ if id(descriptor) not in descriptor_config_map:
50
+ raise ValueError(
51
+ f"The reason behind this error is kinda long and complicated, but doens't really matter. To make it go away, just add `_descriptor_config_map = None` to the definition of {self.__class__.__name__}"
52
+ )
53
+ return descriptor_config_map[id(descriptor)]
54
+
55
+ def finalize_and_validate_configuration(self):
56
+ my_class = self.__class__
57
+ if not self._config:
58
+ self._config = {}
59
+
60
+ # now it's time to check for required values and provide defaults
61
+ attribute_names = self.get_descriptor_config_map().values()
62
+ for attribute_name in attribute_names:
63
+ config = getattr(my_class, attribute_name)
64
+ if attribute_name not in self._config:
65
+ self._config[attribute_name] = config.default
66
+
67
+ if config.required and self._config.get(attribute_name) is None:
68
+ raise ValueError(
69
+ f"Missing required configuration property '{attribute_name}' for class '{my_class.__name__}'"
70
+ )
71
+
72
+ # loop through a second time to have the configs check their values
73
+ # we do this as a separate step because we want to make sure required and default
74
+ # values are specified before we have the configs do their validation.
75
+ for attribute_name in attribute_names:
76
+ getattr(my_class, attribute_name).finalize_and_validate_configuration(self)
77
+ if attribute_name not in self._config:
78
+ self._config[attribute_name] = None
@@ -1,11 +1,11 @@
1
- from .build_context import build_context
2
- from .cli import cli
3
- from .test import test
4
- from .wsgi import wsgi
1
+ from clearskies.contexts.cli import Cli
2
+ from clearskies.contexts.context import Context
3
+ from clearskies.contexts.wsgi import Wsgi
4
+ from clearskies.contexts.wsgi_ref import WsgiRef
5
5
 
6
6
  __all__ = [
7
- "build_context",
8
- "cli",
9
- "test",
10
- "wsgi",
7
+ "Cli",
8
+ "Context",
9
+ "Wsgi",
10
+ "WsgiRef",
11
11
  ]
@@ -1,44 +1,130 @@
1
- from ..authentication import public
2
- from ..input_outputs import CLI as CLIInputOutput
3
- from ..input_outputs import exceptions
4
- from .build_context import build_context
5
- from .context import Context
6
-
7
-
8
- class CLI(Context):
9
- def __init__(self, di):
10
- super().__init__(di)
11
-
12
- def finalize_handler_config(self, config):
13
- return {
14
- "authentication": public(),
15
- **config,
16
- }
17
-
18
- def __call__(self):
19
- if self.handler is None:
20
- raise ValueError("Cannot execute CLI context without first configuring it")
21
-
22
- try:
23
- return self.handler(self.di.build(CLIInputOutput))
24
- except exceptions.CLINotFound:
25
- print("help (aka 404 not found)!")
26
-
27
-
28
- def cli(
29
- application,
30
- di_class=None,
31
- bindings=None,
32
- binding_classes=None,
33
- binding_modules=None,
34
- additional_configs=None,
35
- ):
36
- return build_context(
37
- CLI,
38
- application,
39
- di_class=di_class,
40
- bindings=bindings,
41
- binding_classes=binding_classes,
42
- binding_modules=binding_modules,
43
- additional_configs=additional_configs,
1
+ from clearskies.contexts.context import Context
2
+ from clearskies.input_outputs import Cli as CliInputOutput
3
+
4
+
5
+ class Cli(Context):
6
+ """
7
+ Run an application via a CLI command.
8
+
9
+ This context converts a clearskies application into a CLI command. Here's a simple example:
10
+
11
+ ```
12
+ #!/usr/bin/env python
13
+ import clearskies
14
+
15
+
16
+ def my_function():
17
+ return "Hello World!"
18
+
19
+
20
+ cli = clearskies.contexts.Cli(my_function)
21
+ cli()
22
+ ```
23
+
24
+ Which you can then run as expected:
25
+
26
+ ```
27
+ $ ./example.py
28
+ Hello World!
29
+ ```
30
+
31
+ Routing is still supported, with routes and route parameters becoming CLI args:
32
+
33
+ ```
34
+ #!/usr/bin/env python
35
+ import clearskies
36
+
37
+
38
+ def my_function(name):
39
+ return f"Hello {name}!"
40
+
41
+
42
+ cli = clearskies.contexts.Cli(
43
+ clearskies.endpoints.Callable(
44
+ my_function,
45
+ url="/hello/:name",
46
+ return_standard_response=False,
47
+ )
44
48
  )
49
+ cli()
50
+ ```
51
+
52
+ With a url of `/hello/:name` you would invoke like so:
53
+
54
+ ```
55
+ ./example.py hello Bob
56
+ Hello Bob!
57
+ ```
58
+
59
+ If the endpoint expects a request method you can provide it by setting the `-X` or `--request_method=`
60
+ kwargs. So for tihs example:
61
+
62
+ ```
63
+ #!/usr/bin/env python
64
+ import clearskies
65
+
66
+
67
+ def my_function(name):
68
+ return f"Hello {name}!"
69
+
70
+
71
+ cli = clearskies.contexts.Cli(
72
+ clearskies.endpoints.Callable(
73
+ my_function,
74
+ url="/hello/:name",
75
+ request_methods=["POST"],
76
+ )
77
+ )
78
+ cli()
79
+ ```
80
+
81
+ And then calling it successfully:
82
+
83
+ ```
84
+ ./example.py hello Bob --request_method=POST
85
+
86
+ ./example.py hello Bob -X POST
87
+ ```
88
+
89
+ You can pass data as a json string with the -d flag or set individual named arguments. The following
90
+ example just reflects the request data back to the client:
91
+
92
+ ```
93
+ #!/usr/bin/env python
94
+ import clearskies
95
+
96
+
97
+ def my_function(request_data):
98
+ return request_data
99
+
100
+
101
+ cli = clearskies.contexts.Cli(
102
+ clearskies.endpoints.Callable(
103
+ my_function,
104
+ )
105
+ )
106
+ cli()
107
+ ```
108
+
109
+ And these three calls are identical:
110
+
111
+ ```
112
+ ./example.py -d '{"hello": "world"}'
113
+
114
+ echo '{"hello": "world"}' | ./test.py
115
+
116
+ ./test.py --hello=world
117
+ ```
118
+
119
+ Although note that the first two are going to be preferred over the third, simply because with the
120
+ third there's simply no way to specify the type of a variable. As a result, you may run into issues
121
+ with strict type checking on endpoints.
122
+
123
+ ### Context Callables
124
+
125
+ When using the Cli context, an additional named argument is made available to any callables invoked by clearskies:
126
+ `sys_argv`. This contains `sys.argv`.
127
+ """
128
+
129
+ def __call__(self): # type: ignore
130
+ return self.execute_application(CliInputOutput())
@@ -1,62 +1,99 @@
1
- from ..handlers import callable as callable_module
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from types import ModuleType
5
+ from typing import TYPE_CHECKING, Any, Callable
6
+
7
+ from clearskies import exceptions
8
+ from clearskies.di import Di
9
+ from clearskies.di.additional_config import AdditionalConfig
10
+ from clearskies.input_outputs import InputOutput, Programmatic
11
+
12
+ if TYPE_CHECKING:
13
+ from clearskies.endpoint import Endpoint
14
+ from clearskies.endpoint_group import EndpointGroup
2
15
 
3
16
 
4
17
  class Context:
5
- di = None
6
- handler = None
7
-
8
- def __init__(self, di):
9
- self.di = di
10
-
11
- def configure(self, application):
12
- self.handler = self.extract_handler(application)
13
-
14
- def bind(self, key, value):
15
- self.di.bind(key, value)
16
-
17
- def build(self, key):
18
- return self.di.build(key)
19
-
20
- def mock_class(self, class_or_name, replacement):
21
- self.di.mock_class(class_or_name, replacement)
22
-
23
- def extract_handler(self, application):
24
- """
25
- This accepts the application passed in to the context and returns the handler
26
-
27
- Most importantly, it doesn't technically have to be an application: if passed a simple function
28
- it will assume you mean to use the callable handler, and if passed a dictionary with 'handler_class'
29
- and 'handler_config' keys, it will build a handler from that.
30
- """
31
- # applications will have a handler_class property
32
- if hasattr(application, "handler_class"):
33
- handler = self.di.build(application.handler_class, cache=False)
34
- handler.configure(self.finalize_handler_config(application.handler_config))
35
- return handler
36
-
37
- # check for a dictionary with the same thing (in case the developer doesn't want to bother with
38
- # an application
39
- if hasattr(application, "__getitem__") and "handler_class" in application:
40
- if not "handler_config" in application:
41
- raise ValueError(
42
- "context was passed a dictionary-like object with 'handler_class', but not "
43
- + "'handler_config'. Both are required to execute the handler"
18
+ """Context: a flexible way to connect applications to hosting strategies."""
19
+
20
+ di: Di = None # type: ignore
21
+
22
+ """
23
+ The application to execute.
24
+
25
+ This can be a callable, an endpoint, or an endpoint group. If passed a callable, the callable can request any
26
+ standard or defined dependencies and should return the desired response. It can also raise any exception from
27
+ exceptions.
28
+ """
29
+ application: Callable | Endpoint | EndpointGroup = None # type: ignore
30
+
31
+ def __init__(
32
+ self,
33
+ application: Callable | Endpoint | EndpointGroup,
34
+ classes: type | list[type] = [],
35
+ modules: ModuleType | list[ModuleType] = [],
36
+ bindings: dict[str, Any] = {},
37
+ additional_configs: AdditionalConfig | list[AdditionalConfig] = [],
38
+ class_overrides: dict[type, Any] = {},
39
+ overrides: dict[str, type] = {},
40
+ now: datetime.datetime | None = None,
41
+ utcnow: datetime.datetime | None = None,
42
+ ):
43
+ self.di = Di(
44
+ classes=classes,
45
+ modules=modules,
46
+ bindings=bindings,
47
+ additional_configs=additional_configs,
48
+ class_overrides=class_overrides,
49
+ overrides=overrides,
50
+ now=now,
51
+ utcnow=utcnow,
52
+ )
53
+ self.application = application
54
+
55
+ def execute_application(self, input_output: InputOutput):
56
+ self.di.add_binding("input_output", input_output)
57
+ self.di.add_class_override(InputOutput, input_output)
58
+
59
+ if hasattr(self.application, "injectable_properties"):
60
+ self.application.injectable_properties(self.di)
61
+ return self.application(input_output)
62
+ elif callable(self.application):
63
+ try:
64
+ return input_output.respond(
65
+ self.di.call_function(self.application, **input_output.get_context_for_callables())
44
66
  )
45
- handler = self.di.build(application["handler_class"], cache=False)
46
- handler.configure(self.finalize_handler_config(application["handler_config"]))
47
- return handler
48
-
49
- # if we get a callable, then use the callable handler class
50
- if callable(application):
51
- handler = self.di.build(callable_module.Callable, cache=False)
52
- handler.configure(self.finalize_handler_config({"callable": application}))
53
- return handler
54
-
55
- raise ValueError(
56
- "The context received an object it did not know how to handle! You should pass in either an instance "
57
- + "of clearskies.Application, a dictionary with 'handler_class' and 'handler_config' keys, or a "
58
- + "function/lambda to be executed"
67
+ except exceptions.ClientError as e:
68
+ return input_output.respond(str(e), 400)
69
+ except exceptions.Authentication as e:
70
+ return input_output.respond(str(e), 401)
71
+ except exceptions.Authorization as e:
72
+ return input_output.respond(str(e), 403)
73
+ except exceptions.NotFound as e:
74
+ return input_output.respond(str(e), 404)
75
+ except exceptions.MovedPermanently as e:
76
+ return input_output.respond(str(e), 302)
77
+ except exceptions.MovedTemporarily as e:
78
+ return input_output.respond(str(e), 307)
79
+
80
+ def __call__(
81
+ self,
82
+ url: str = "",
83
+ request_method: str = "GET",
84
+ body: str | dict[str, Any] | list[Any] = "",
85
+ query_parameters: dict[str, str] = {},
86
+ request_headers: dict[str, str] = {},
87
+ ):
88
+ return self.execute_application(
89
+ Programmatic(
90
+ url=url,
91
+ request_method=request_method,
92
+ body=body,
93
+ query_parameters=query_parameters,
94
+ request_headers=request_headers,
95
+ )
59
96
  )
60
97
 
61
- def finalize_handler_config(self, config):
62
- return config
98
+ def build(self, thing: Any, cache: bool = False) -> Any:
99
+ return self.di.build(thing, cache=cache)
@@ -1,33 +1,79 @@
1
- from ..input_outputs import WSGI as WSGIInputOutput
2
- from .build_context import build_context
3
- from .context import Context
4
-
5
-
6
- class WSGI(Context):
7
- def __init__(self, di):
8
- super().__init__(di)
9
-
10
- def __call__(self, env, start_response):
11
- if self.handler is None:
12
- raise ValueError("Cannot execute WSGI context without first configuring it")
13
-
14
- return self.handler(WSGIInputOutput(env, start_response))
15
-
16
-
17
- def wsgi(
18
- application,
19
- di_class=None,
20
- bindings=None,
21
- binding_classes=None,
22
- binding_modules=None,
23
- additional_configs=None,
24
- ):
25
- return build_context(
26
- WSGI,
27
- application,
28
- di_class=None,
29
- bindings=bindings,
30
- binding_classes=binding_classes,
31
- binding_modules=binding_modules,
32
- additional_configs=additional_configs,
33
- )
1
+ from __future__ import annotations
2
+
3
+ from clearskies.contexts.context import Context
4
+ from clearskies.input_outputs import Wsgi as WsgiInputOutput
5
+
6
+
7
+ class Wsgi(Context):
8
+ """
9
+ Connect your application to a WSGI server.
10
+
11
+ The Wsgi context is used to connect a clearskies application to a WSGI server of your choice. As with all
12
+ contexts, you first create it and pass in the application (a callable, endpoint, or endpoint group) as well
13
+ as any dependency injection parameters. Then, you call the context from inside of the function invoked by
14
+ your WSGI server, passing along the `environment` and `start_response` variables, and returning the response
15
+ from the context. Here's a simple example:
16
+
17
+ ```
18
+ import clearskies
19
+
20
+
21
+ def hello_world():
22
+ return "Hello World!"
23
+
24
+
25
+ wsgi = clearskies.contexts.Wsgi(hello_world)
26
+
27
+
28
+ def application(environment, start_response):
29
+ return wsgi(environment, start_response)
30
+ ```
31
+
32
+ You would then launch your WSGI server. For instance, here's how to launch it with uwsgi, which automatically
33
+ looks for a function called `application` and treats that as the WSGI starting point:
34
+
35
+ ```
36
+ uwsgi --http :9090 --wsgi-file test.py
37
+ ```
38
+
39
+ You could then:
40
+
41
+ ```
42
+ curl 'http://localhost:9090'
43
+ ```
44
+
45
+ And see the response from this "hello world" app. Note than in the above example I create the context outside
46
+ of the application function. Of course, you can do the opposite:
47
+
48
+ ```
49
+ import clearskies
50
+
51
+
52
+ def hello_world():
53
+ return "Hello World!"
54
+
55
+
56
+ def application(environment, start_response):
57
+ wsgi = clearskies.contexts.Wsgi(hello_world)
58
+ return wsgi(environment, start_response)
59
+ ```
60
+
61
+ The difference is that most wsgi servers will cache any objects created outside of the handler function (e.g. `application`
62
+ in this case). When you first create the context clearskies will configure and validate any endpoints attached.
63
+ Also, it will create an instance of the dependency injection container and cache it. If the context object is created
64
+ outside of the handler, and the server caches objects in this csae, then this validation will only happen once and
65
+ the DI cache will store objects in between HTTP calls. If you create your context inside the handler function, then
66
+ you'll end up with an empty cache everytime and you'll have slower responses because of clearskies checking the
67
+ application configuration everytime. Note that the DI system for clearskies grants you full cache control, so
68
+ by and large it's normal and expected that you'll persist the cache between requests by creating the context outside
69
+ of any handler functions.
70
+
71
+ ### Context for Callables
72
+
73
+ When using this context, one additional named property becomes available to any callables invoked by clearskies:
74
+ `wsgi_environment`. This contains the environment object passed in by the WSGI server to clearskies.
75
+
76
+ """
77
+
78
+ def __call__(self, env, start_response): # type: ignore
79
+ return self.execute_application(WsgiInputOutput(env, start_response))