clear-skies 1.22.31__py3-none-any.whl → 2.0.1__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 (345) hide show
  1. {clear_skies-1.22.31.dist-info → clear_skies-2.0.1.dist-info}/METADATA +12 -14
  2. clear_skies-2.0.1.dist-info/RECORD +249 -0
  3. {clear_skies-1.22.31.dist-info → clear_skies-2.0.1.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 +46 -0
  8. clearskies/authentication/authorization.py +8 -9
  9. clearskies/authentication/authorization_pass_through.py +11 -9
  10. clearskies/authentication/jwks.py +133 -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 +53 -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 +1229 -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 +162 -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 +24 -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/endpoint_list.py +28 -0
  100. clearskies/configs/float.py +16 -0
  101. clearskies/configs/float_or_callable.py +18 -0
  102. clearskies/configs/integer.py +16 -0
  103. clearskies/configs/integer_or_callable.py +18 -0
  104. clearskies/configs/joins.py +30 -0
  105. clearskies/configs/list_any_dict.py +30 -0
  106. clearskies/configs/list_any_dict_or_callable.py +31 -0
  107. clearskies/configs/model_class.py +35 -0
  108. clearskies/configs/model_column.py +65 -0
  109. clearskies/configs/model_columns.py +56 -0
  110. clearskies/configs/model_destination_name.py +25 -0
  111. clearskies/configs/model_to_id_column.py +43 -0
  112. clearskies/configs/readable_model_column.py +9 -0
  113. clearskies/configs/readable_model_columns.py +9 -0
  114. clearskies/configs/schema.py +23 -0
  115. clearskies/configs/searchable_model_columns.py +9 -0
  116. clearskies/configs/security_headers.py +39 -0
  117. clearskies/configs/select.py +26 -0
  118. clearskies/configs/select_list.py +47 -0
  119. clearskies/configs/string.py +29 -0
  120. clearskies/configs/string_dict.py +32 -0
  121. clearskies/configs/string_list.py +32 -0
  122. clearskies/configs/string_list_or_callable.py +35 -0
  123. clearskies/configs/string_or_callable.py +18 -0
  124. clearskies/configs/timedelta.py +18 -0
  125. clearskies/configs/timezone.py +18 -0
  126. clearskies/configs/url.py +23 -0
  127. clearskies/configs/validators.py +45 -0
  128. clearskies/configs/writeable_model_column.py +9 -0
  129. clearskies/configs/writeable_model_columns.py +9 -0
  130. clearskies/configurable.py +76 -0
  131. clearskies/contexts/__init__.py +8 -8
  132. clearskies/contexts/cli.py +8 -41
  133. clearskies/contexts/context.py +91 -56
  134. clearskies/contexts/wsgi.py +16 -29
  135. clearskies/contexts/wsgi_ref.py +53 -0
  136. clearskies/di/__init__.py +10 -7
  137. clearskies/di/additional_config.py +115 -4
  138. clearskies/di/additional_config_auto_import.py +12 -0
  139. clearskies/di/di.py +742 -121
  140. clearskies/di/inject/__init__.py +23 -0
  141. clearskies/di/inject/by_class.py +21 -0
  142. clearskies/di/inject/by_name.py +18 -0
  143. clearskies/di/inject/di.py +13 -0
  144. clearskies/di/inject/environment.py +14 -0
  145. clearskies/di/inject/input_output.py +20 -0
  146. clearskies/di/inject/now.py +13 -0
  147. clearskies/di/inject/requests.py +13 -0
  148. clearskies/di/inject/secrets.py +14 -0
  149. clearskies/di/inject/utcnow.py +13 -0
  150. clearskies/di/inject/uuid.py +15 -0
  151. clearskies/di/injectable.py +29 -0
  152. clearskies/di/injectable_properties.py +131 -0
  153. clearskies/end.py +183 -0
  154. clearskies/endpoint.py +1310 -0
  155. clearskies/endpoint_group.py +310 -0
  156. clearskies/endpoints/__init__.py +23 -0
  157. clearskies/endpoints/advanced_search.py +526 -0
  158. clearskies/endpoints/callable.py +388 -0
  159. clearskies/endpoints/create.py +202 -0
  160. clearskies/endpoints/delete.py +139 -0
  161. clearskies/endpoints/get.py +275 -0
  162. clearskies/endpoints/health_check.py +181 -0
  163. clearskies/endpoints/list.py +573 -0
  164. clearskies/endpoints/restful_api.py +427 -0
  165. clearskies/endpoints/simple_search.py +286 -0
  166. clearskies/endpoints/update.py +190 -0
  167. clearskies/environment.py +5 -3
  168. clearskies/exceptions/__init__.py +17 -0
  169. clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
  170. clearskies/exceptions/moved_permanently.py +3 -0
  171. clearskies/exceptions/moved_temporarily.py +3 -0
  172. clearskies/exceptions/not_found.py +2 -0
  173. clearskies/functional/__init__.py +2 -2
  174. clearskies/functional/routing.py +92 -0
  175. clearskies/functional/string.py +19 -11
  176. clearskies/functional/validations.py +61 -9
  177. clearskies/input_outputs/__init__.py +9 -7
  178. clearskies/input_outputs/cli.py +130 -142
  179. clearskies/input_outputs/exceptions/__init__.py +1 -1
  180. clearskies/input_outputs/headers.py +45 -0
  181. clearskies/input_outputs/input_output.py +91 -122
  182. clearskies/input_outputs/programmatic.py +69 -0
  183. clearskies/input_outputs/wsgi.py +23 -38
  184. clearskies/model.py +984 -183
  185. clearskies/parameters_to_properties.py +31 -0
  186. clearskies/query/__init__.py +12 -0
  187. clearskies/query/condition.py +223 -0
  188. clearskies/query/join.py +136 -0
  189. clearskies/query/query.py +196 -0
  190. clearskies/query/sort.py +27 -0
  191. clearskies/schema.py +82 -0
  192. clearskies/secrets/__init__.py +3 -31
  193. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
  194. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
  195. clearskies/secrets/akeyless.py +88 -147
  196. clearskies/secrets/secrets.py +8 -8
  197. clearskies/security_header.py +15 -0
  198. clearskies/security_headers/__init__.py +8 -8
  199. clearskies/security_headers/cache_control.py +47 -110
  200. clearskies/security_headers/cors.py +40 -95
  201. clearskies/security_headers/csp.py +76 -151
  202. clearskies/security_headers/hsts.py +14 -16
  203. clearskies/test_base.py +8 -0
  204. clearskies/typing.py +11 -0
  205. clearskies/validator.py +37 -0
  206. clearskies/validators/__init__.py +33 -0
  207. clearskies/validators/after_column.py +62 -0
  208. clearskies/validators/before_column.py +13 -0
  209. clearskies/validators/in_the_future.py +32 -0
  210. clearskies/validators/in_the_future_at_least.py +11 -0
  211. clearskies/validators/in_the_future_at_most.py +10 -0
  212. clearskies/validators/in_the_past.py +32 -0
  213. clearskies/validators/in_the_past_at_least.py +10 -0
  214. clearskies/validators/in_the_past_at_most.py +10 -0
  215. clearskies/validators/maximum_length.py +26 -0
  216. clearskies/validators/maximum_value.py +29 -0
  217. clearskies/validators/minimum_length.py +26 -0
  218. clearskies/validators/minimum_value.py +29 -0
  219. clearskies/validators/required.py +35 -0
  220. clearskies/validators/timedelta.py +59 -0
  221. clearskies/validators/unique.py +31 -0
  222. clear_skies-1.22.31.dist-info/RECORD +0 -214
  223. clearskies/application.py +0 -29
  224. clearskies/authentication/auth0_jwks.py +0 -118
  225. clearskies/authentication/auth_exception.py +0 -2
  226. clearskies/authentication/jwks_jwcrypto.py +0 -51
  227. clearskies/backends/api_get_only_backend.py +0 -48
  228. clearskies/backends/example_backend.py +0 -43
  229. clearskies/backends/file_backend.py +0 -48
  230. clearskies/backends/json_backend.py +0 -7
  231. clearskies/backends/restful_api_advanced_search_backend.py +0 -103
  232. clearskies/binding_config.py +0 -16
  233. clearskies/column_types/__init__.py +0 -203
  234. clearskies/column_types/audit.py +0 -249
  235. clearskies/column_types/belongs_to.py +0 -271
  236. clearskies/column_types/boolean.py +0 -60
  237. clearskies/column_types/category_tree.py +0 -304
  238. clearskies/column_types/column.py +0 -373
  239. clearskies/column_types/created.py +0 -26
  240. clearskies/column_types/created_by_authorization_data.py +0 -26
  241. clearskies/column_types/created_by_header.py +0 -24
  242. clearskies/column_types/created_by_ip.py +0 -17
  243. clearskies/column_types/created_by_routing_data.py +0 -25
  244. clearskies/column_types/created_by_user_agent.py +0 -17
  245. clearskies/column_types/created_micro.py +0 -26
  246. clearskies/column_types/datetime.py +0 -109
  247. clearskies/column_types/datetime_micro.py +0 -12
  248. clearskies/column_types/email.py +0 -18
  249. clearskies/column_types/float.py +0 -43
  250. clearskies/column_types/has_many.py +0 -179
  251. clearskies/column_types/has_one.py +0 -60
  252. clearskies/column_types/integer.py +0 -41
  253. clearskies/column_types/json.py +0 -25
  254. clearskies/column_types/many_to_many.py +0 -278
  255. clearskies/column_types/many_to_many_with_data.py +0 -162
  256. clearskies/column_types/phone.py +0 -48
  257. clearskies/column_types/select.py +0 -11
  258. clearskies/column_types/string.py +0 -24
  259. clearskies/column_types/timestamp.py +0 -73
  260. clearskies/column_types/updated.py +0 -26
  261. clearskies/column_types/updated_micro.py +0 -26
  262. clearskies/column_types/uuid.py +0 -25
  263. clearskies/columns.py +0 -123
  264. clearskies/condition_parser.py +0 -172
  265. clearskies/contexts/build_context.py +0 -54
  266. clearskies/contexts/convert_to_application.py +0 -190
  267. clearskies/contexts/extract_handler.py +0 -37
  268. clearskies/contexts/test.py +0 -94
  269. clearskies/decorators/__init__.py +0 -41
  270. clearskies/decorators/allow_non_json_bodies.py +0 -9
  271. clearskies/decorators/auth0_jwks.py +0 -22
  272. clearskies/decorators/authorization.py +0 -10
  273. clearskies/decorators/binding_classes.py +0 -9
  274. clearskies/decorators/binding_modules.py +0 -9
  275. clearskies/decorators/bindings.py +0 -9
  276. clearskies/decorators/create.py +0 -10
  277. clearskies/decorators/delete.py +0 -10
  278. clearskies/decorators/docs.py +0 -14
  279. clearskies/decorators/get.py +0 -10
  280. clearskies/decorators/jwks.py +0 -26
  281. clearskies/decorators/merge.py +0 -124
  282. clearskies/decorators/patch.py +0 -10
  283. clearskies/decorators/post.py +0 -10
  284. clearskies/decorators/public.py +0 -11
  285. clearskies/decorators/response_headers.py +0 -10
  286. clearskies/decorators/return_raw_response.py +0 -9
  287. clearskies/decorators/schema.py +0 -10
  288. clearskies/decorators/secret_bearer.py +0 -24
  289. clearskies/decorators/security_headers.py +0 -10
  290. clearskies/di/standard_dependencies.py +0 -151
  291. clearskies/handlers/__init__.py +0 -41
  292. clearskies/handlers/advanced_search.py +0 -271
  293. clearskies/handlers/base.py +0 -479
  294. clearskies/handlers/callable.py +0 -192
  295. clearskies/handlers/create.py +0 -35
  296. clearskies/handlers/crud_by_method.py +0 -18
  297. clearskies/handlers/database_connector.py +0 -32
  298. clearskies/handlers/delete.py +0 -61
  299. clearskies/handlers/exceptions/__init__.py +0 -5
  300. clearskies/handlers/exceptions/not_found.py +0 -3
  301. clearskies/handlers/get.py +0 -156
  302. clearskies/handlers/health_check.py +0 -59
  303. clearskies/handlers/input_processing.py +0 -79
  304. clearskies/handlers/list.py +0 -530
  305. clearskies/handlers/mygrations.py +0 -82
  306. clearskies/handlers/request_method_routing.py +0 -47
  307. clearskies/handlers/restful_api.py +0 -218
  308. clearskies/handlers/routing.py +0 -62
  309. clearskies/handlers/schema_helper.py +0 -128
  310. clearskies/handlers/simple_routing.py +0 -206
  311. clearskies/handlers/simple_routing_route.py +0 -197
  312. clearskies/handlers/simple_search.py +0 -136
  313. clearskies/handlers/update.py +0 -102
  314. clearskies/handlers/write.py +0 -193
  315. clearskies/input_requirements/__init__.py +0 -78
  316. clearskies/input_requirements/after.py +0 -36
  317. clearskies/input_requirements/before.py +0 -36
  318. clearskies/input_requirements/in_the_future_at_least.py +0 -19
  319. clearskies/input_requirements/in_the_future_at_most.py +0 -19
  320. clearskies/input_requirements/in_the_past_at_least.py +0 -19
  321. clearskies/input_requirements/in_the_past_at_most.py +0 -19
  322. clearskies/input_requirements/maximum_length.py +0 -19
  323. clearskies/input_requirements/maximum_value.py +0 -19
  324. clearskies/input_requirements/minimum_length.py +0 -22
  325. clearskies/input_requirements/minimum_value.py +0 -19
  326. clearskies/input_requirements/required.py +0 -23
  327. clearskies/input_requirements/requirement.py +0 -25
  328. clearskies/input_requirements/time_delta.py +0 -38
  329. clearskies/input_requirements/unique.py +0 -18
  330. clearskies/mocks/__init__.py +0 -7
  331. clearskies/mocks/input_output.py +0 -124
  332. clearskies/mocks/models.py +0 -142
  333. clearskies/models.py +0 -350
  334. clearskies/security_headers/base.py +0 -12
  335. clearskies/tests/simple_api/models/__init__.py +0 -2
  336. clearskies/tests/simple_api/models/status.py +0 -23
  337. clearskies/tests/simple_api/models/user.py +0 -21
  338. clearskies/tests/simple_api/users_api.py +0 -64
  339. {clear_skies-1.22.31.dist-info → clear_skies-2.0.1.dist-info}/LICENSE +0 -0
  340. /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
  341. /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
  342. /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
  343. /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
  344. /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
  345. /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
@@ -0,0 +1,35 @@
1
+ from typing import Callable
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class StringListOrCallable(config.Config):
7
+ """
8
+ This is for a configuration that should be a list of strings or a callable that returns a list of strings.
9
+
10
+ This is different than SelectList, which also accepts a list of strings, but
11
+ valdiates that all of those values match against an allow list.
12
+ """
13
+
14
+ def __set__(self, instance, value: list[str] | Callable[..., list[str]]):
15
+ if value is None:
16
+ return
17
+
18
+ if not isinstance(value, list) and not callable(value):
19
+ error_prefix = self._error_prefix(instance)
20
+ raise TypeError(
21
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that should be a list or a callable"
22
+ )
23
+ if isinstance(value, list):
24
+ for index, item in enumerate(value):
25
+ if not isinstance(item, str):
26
+ error_prefix = self._error_prefix(instance)
27
+ raise TypeError(
28
+ f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1}. A string was expected."
29
+ )
30
+ instance._set_config(self, value)
31
+
32
+ def __get__(self, instance, parent) -> list[str] | Callable[..., list[str]]:
33
+ if not instance:
34
+ return self # type: ignore
35
+ return instance._get_config(self)
@@ -0,0 +1,18 @@
1
+ from typing import Callable
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class StringOrCallable(config.Config):
7
+ def __set__(self, instance, value: str | Callable[..., str]):
8
+ if not isinstance(value, str) and not callable(value):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requires a string or a callable."
12
+ )
13
+ instance._set_config(self, value)
14
+
15
+ def __get__(self, instance, parent) -> str | Callable[..., str]:
16
+ if not instance:
17
+ return self # type: ignore
18
+ return instance._get_config(self)
@@ -0,0 +1,18 @@
1
+ import datetime
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class Timedelta(config.Config):
7
+ def __set__(self, instance, value: datetime.timedelta):
8
+ if not isinstance(value, datetime.timedelta):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a datetime.timedelta object."
12
+ )
13
+ instance._set_config(self, value)
14
+
15
+ def __get__(self, instance, parent) -> datetime.timedelta:
16
+ if not instance:
17
+ return self # type: ignore
18
+ return instance._get_config(self)
@@ -0,0 +1,18 @@
1
+ import datetime
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class Timezone(config.Config):
7
+ def __set__(self, instance, value: datetime.timezone | None):
8
+ if value and not isinstance(value, datetime.timezone):
9
+ error_prefix = self._error_prefix(instance)
10
+ raise TypeError(
11
+ f"{error_prefix} attempt to set a value of type '{value.__class__.__name__}' to a parameter that requries a timezone (datetime.timezone)."
12
+ )
13
+ instance._set_config(self, value)
14
+
15
+ def __get__(self, instance, parent) -> datetime.timezone:
16
+ if not instance:
17
+ return self # type: ignore
18
+ return instance._get_config(self)
@@ -0,0 +1,23 @@
1
+ from clearskies.configs import string
2
+ from clearskies.functional import routing
3
+
4
+
5
+ class Url(string.String):
6
+ def __set__(self, instance, value: str):
7
+ if value is None:
8
+ return
9
+
10
+ if not isinstance(value, str):
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 url parameter"
14
+ )
15
+ value = value.strip("/")
16
+
17
+ if value:
18
+ try:
19
+ routing.extract_url_parameter_name_map(value)
20
+ except ValueError as e:
21
+ error_prefix = self._error_prefix(instance)
22
+ raise ValueError(f"{error_prefix} {e}")
23
+ 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,9 @@
1
+ from clearskies.configs import model_column
2
+
3
+
4
+ class WriteableModelColumn(model_column.ModelColumn):
5
+ def get_allowed_columns(self, model_class, column_configs):
6
+ return [name for (name, column) in column_configs.items() if column.is_writeable]
7
+
8
+ def my_description(self):
9
+ return "writeable column"
@@ -0,0 +1,9 @@
1
+ from clearskies.configs import model_columns
2
+
3
+
4
+ class WriteableModelColumns(model_columns.ModelColumns):
5
+ def get_allowed_columns(self, model_class, column_configs):
6
+ return [name for (name, column) in column_configs.items() if column.is_writeable]
7
+
8
+ def my_description(self):
9
+ return "writeable column"
@@ -0,0 +1,76 @@
1
+ from typing import Any
2
+
3
+ from clearskies.configs import config
4
+
5
+
6
+ class Configurable:
7
+ _config: dict[str, Any] | None = None
8
+ _descriptor_config_map: dict[int, str] | None = None
9
+
10
+ def _set_config(self, descriptor, value):
11
+ if not self._config:
12
+ self._config = {}
13
+
14
+ self._config[self._descriptor_to_name(descriptor)] = value
15
+
16
+ def _get_config(self, descriptor):
17
+ if not self._config:
18
+ self._config = {}
19
+
20
+ name = self._descriptor_to_name(descriptor)
21
+ if name not in self._config:
22
+ raise KeyError(f"Attempt to fetch a config value named '{name}' but no value has been set for this config")
23
+ return self._config[name]
24
+
25
+ @classmethod
26
+ def _get_config_object(cls, attribute_name):
27
+ return getattr(cls, attribute_name)
28
+
29
+ @classmethod
30
+ def get_descriptor_config_map(cls):
31
+ if cls._descriptor_config_map:
32
+ return cls._descriptor_config_map
33
+
34
+ descriptor_config_map = {}
35
+ for attribute_name in dir(cls):
36
+ descriptor = getattr(cls, attribute_name)
37
+ if not isinstance(descriptor, config.Config):
38
+ continue
39
+
40
+ descriptor_config_map[id(descriptor)] = attribute_name
41
+
42
+ cls._descriptor_config_map = descriptor_config_map
43
+ return cls._descriptor_config_map
44
+
45
+ def _descriptor_to_name(self, descriptor):
46
+ descriptor_config_map = self.get_descriptor_config_map()
47
+ if id(descriptor) not in descriptor_config_map:
48
+ raise ValueError(
49
+ 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__}"
50
+ )
51
+ return descriptor_config_map[id(descriptor)]
52
+
53
+ def finalize_and_validate_configuration(self):
54
+ my_class = self.__class__
55
+ if not self._config:
56
+ self._config = {}
57
+
58
+ # now it's time to check for required values and provide defaults
59
+ attribute_names = self.get_descriptor_config_map().values()
60
+ for attribute_name in attribute_names:
61
+ config = getattr(my_class, attribute_name)
62
+ if attribute_name not in self._config:
63
+ self._config[attribute_name] = config.default
64
+
65
+ if config.required and self._config.get(attribute_name) is None:
66
+ raise ValueError(
67
+ f"Missing required configuration property '{attribute_name}' for class '{my_class.__name__}'"
68
+ )
69
+
70
+ # loop through a second time to have the configs check their values
71
+ # we do this as a separate step because we want to make sure required and default
72
+ # values are specified before we have the configs do their validation.
73
+ for attribute_name in attribute_names:
74
+ getattr(my_class, attribute_name).finalize_and_validate_configuration(self)
75
+ if attribute_name not in self._config:
76
+ 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,11 @@
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
1
+ from clearskies.contexts.context import Context
2
+ from clearskies.input_outputs import Cli as CliInputOutput
6
3
 
7
4
 
8
- class CLI(Context):
9
- def __init__(self, di):
10
- super().__init__(di)
5
+ class Cli(Context):
6
+ """
7
+ Run an application via a CLI command
8
+ """
11
9
 
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,
44
- )
10
+ def __call__(self): # type: ignore
11
+ return self.execute_application(CliInputOutput())
@@ -1,62 +1,97 @@
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
+ import clearskies.endpoint
8
+ import clearskies.endpoint_group
9
+ from clearskies.di import Di
10
+ from clearskies.di.additional_config import AdditionalConfig
11
+ from clearskies.input_outputs import Programmatic
12
+
13
+ if TYPE_CHECKING:
14
+ from clearskies.input_outputs import InputOutput
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
+ """
19
+ Context: a flexible way to connect applications to hosting strategies.
20
+ """
21
+
22
+ di: Di = None # type: ignore
23
+
24
+ """
25
+ The application to execute.
26
+
27
+ This can be a callable, an endpoint, or an endpoint group. If passed a callable, the callable can request any
28
+ standard or defined dependencies and should return the desired response. It can also raise any exception from
29
+ clearskies.exceptions.
30
+ """
31
+ application: Callable | clearskies.endpoint.Endpoint | clearskies.endpoint_group.EndpointGroup = None # type: ignore
32
+
33
+ def __init__(
34
+ self,
35
+ application: Callable | clearskies.endpoint.Endpoint | clearskies.endpoint_group.EndpointGroup,
36
+ classes: type | list[type] = [],
37
+ modules: ModuleType | list[ModuleType] = [],
38
+ bindings: dict[str, Any] = {},
39
+ additional_configs: AdditionalConfig | list[AdditionalConfig] = [],
40
+ class_overrides: dict[type, Any] = {},
41
+ overrides: dict[str, type] = {},
42
+ now: datetime.datetime | None = None,
43
+ utcnow: datetime.datetime | None = None,
44
+ ):
45
+ self.di = Di(
46
+ classes=classes,
47
+ modules=modules,
48
+ bindings=bindings,
49
+ additional_configs=additional_configs,
50
+ class_overrides=class_overrides,
51
+ now=now,
52
+ utcnow=utcnow,
53
+ )
54
+ self.application = application
55
+
56
+ def execute_application(self, input_output: InputOutput):
57
+ if hasattr(self.application, "injectable_properties"):
58
+ self.application.injectable_properties(self.di)
59
+ return self.application(input_output)
60
+ elif callable(self.application):
61
+ try:
62
+ return input_output.respond(
63
+ self.di.call_function(self.application, **input_output.get_context_for_callables())
44
64
  )
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"
65
+ except clearskies.exceptions.ClientError as e:
66
+ return input_output.respond(str(e), 400)
67
+ except clearskies.exceptions.Authentication as e:
68
+ return input_output.respond(str(e), 401)
69
+ except clearskies.exceptions.Authorization as e:
70
+ return input_output.respond(str(e), 403)
71
+ except clearskies.exceptions.NotFound as e:
72
+ return input_output.respond(str(e), 404)
73
+ except clearskies.exceptions.MovedPermanently as e:
74
+ return input_output.respond(str(e), 302)
75
+ except clearskies.exceptions.MovedTemporarily as e:
76
+ return input_output.respond(str(e), 307)
77
+
78
+ def __call__(
79
+ self,
80
+ url: str = "",
81
+ request_method: str = "GET",
82
+ body: str | dict[str, Any] | list[Any] = "",
83
+ query_parameters: dict[str, str] = {},
84
+ request_headers: dict[str, str] = {},
85
+ ):
86
+ return self.execute_application(
87
+ Programmatic(
88
+ url=url,
89
+ request_method=request_method,
90
+ body=body,
91
+ query_parameters=query_parameters,
92
+ request_headers=request_headers,
93
+ )
59
94
  )
60
95
 
61
- def finalize_handler_config(self, config):
62
- return config
96
+ def build(self, thing: Any, cache: bool = False) -> Any:
97
+ return self.di.build(thing, cache=cache)
@@ -1,33 +1,20 @@
1
- from ..input_outputs import WSGI as WSGIInputOutput
2
- from .build_context import build_context
3
- from .context import Context
1
+ import datetime
2
+ from types import ModuleType
3
+ from typing import Any, Callable
4
+ from wsgiref.simple_server import make_server
5
+ from wsgiref.util import setup_testing_defaults
4
6
 
7
+ import clearskies.endpoint
8
+ import clearskies.endpoint_group
9
+ from clearskies.contexts.context import Context
10
+ from clearskies.di import AdditionalConfig
11
+ from clearskies.input_outputs import Wsgi as WsgiInputOutput
5
12
 
6
- class WSGI(Context):
7
- def __init__(self, di):
8
- super().__init__(di)
9
13
 
10
- def __call__(self, env, start_response):
11
- if self.handler is None:
12
- raise ValueError("Cannot execute WSGI context without first configuring it")
14
+ class Wsgi(Context):
15
+ """
16
+ Connect your application to a WSGI server.
17
+ """
13
18
 
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
- )
19
+ def __call__(self, env, start_response): # type: ignore
20
+ return self.execute_application(WsgiInputOutput(env, start_response))
@@ -0,0 +1,53 @@
1
+ import datetime
2
+ from types import ModuleType
3
+ from typing import Any, Callable
4
+ from wsgiref.simple_server import make_server
5
+ from wsgiref.util import setup_testing_defaults
6
+
7
+ import clearskies.endpoint
8
+ import clearskies.endpoint_group
9
+ from clearskies.contexts.context import Context
10
+ from clearskies.di import AdditionalConfig
11
+ from clearskies.input_outputs import Wsgi as WsgiInputOutput
12
+
13
+
14
+ class WsgiRef(Context):
15
+ """
16
+ Use a built in WSGI server (for development purposes only).
17
+ """
18
+
19
+ port: int = 8080
20
+
21
+ def __init__(
22
+ self,
23
+ application: Callable | clearskies.endpoint.Endpoint | clearskies.endpoint_group.EndpointGroup,
24
+ port: int = 8080,
25
+ classes: type | list[type] = [],
26
+ modules: ModuleType | list[ModuleType] = [],
27
+ bindings: dict[str, Any] = {},
28
+ additional_configs: AdditionalConfig | list[AdditionalConfig] = [],
29
+ class_overrides: dict[type, type] = {},
30
+ overrides: dict[str, type] = {},
31
+ now: datetime.datetime | None = None,
32
+ utcnow: datetime.datetime | None = None,
33
+ ):
34
+ super().__init__(
35
+ application,
36
+ classes=classes,
37
+ modules=modules,
38
+ bindings=bindings,
39
+ additional_configs=additional_configs,
40
+ class_overrides=class_overrides,
41
+ overrides=overrides,
42
+ now=now,
43
+ utcnow=utcnow,
44
+ )
45
+ self.port = port
46
+
47
+ def __call__(self): # type: ignore
48
+ with make_server("", self.port, self.handler) as httpd:
49
+ print(f"Starting WSGI server on port {self.port}. This is NOT intended for production usage.")
50
+ httpd.serve_forever()
51
+
52
+ def handler(self, environment, start_response):
53
+ return self.execute_application(WsgiInputOutput(environment, start_response))
clearskies/di/__init__.py CHANGED
@@ -1,11 +1,14 @@
1
- from .additional_config_auto_import import AdditionalConfigAutoImport
2
- from .di import DI
3
- from .standard_dependencies import StandardDependencies
4
- from .additional_config import AdditionalConfig
1
+ import clearskies.di.inject as inject
2
+ from clearskies.di.additional_config import AdditionalConfig
3
+ from clearskies.di.additional_config_auto_import import AdditionalConfigAutoImport
4
+ from clearskies.di.di import Di
5
+ from clearskies.di.injectable import Injectable
6
+ from clearskies.di.injectable_properties import InjectableProperties
5
7
 
6
8
  __all__ = [
7
- "AdditionalConfigAutoImport",
8
- "DI",
9
- "StandardDependencies",
10
9
  "AdditionalConfig",
10
+ "AdditionalConfigAutoImport",
11
+ "Di",
12
+ "InjectableProperties",
13
+ "injectInjectable",
11
14
  ]