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
@@ -1,373 +0,0 @@
1
- from abc import ABC
2
- import re
3
- from ..autodoc.schema import String as AutoDocString
4
- from .. import input_requirements
5
- from .. import binding_config
6
- import inspect
7
-
8
-
9
- class Column(ABC):
10
- _auto_doc_class = AutoDocString
11
- _is_unique = None
12
- _is_required = None
13
- configuration = None
14
- common_configs = [
15
- "input_requirements",
16
- "class",
17
- "is_writeable",
18
- "is_temporary",
19
- "on_change",
20
- "default",
21
- "setable",
22
- "created_by_source_type",
23
- "created_by_source_key",
24
- ]
25
-
26
- def __init__(self, di):
27
- self.di = di
28
-
29
- my_configs = []
30
- required_configs = []
31
-
32
- @property
33
- def is_writeable(self):
34
- is_writeable = self.config("is_writeable", True)
35
- return True if (is_writeable or is_writeable is None) else False
36
-
37
- @property
38
- def is_readable(self):
39
- return True
40
-
41
- @property
42
- def is_unique(self):
43
- if self._is_unique is None:
44
- requirements = self.config("input_requirements")
45
- self._is_unique = False
46
- for requirement in requirements:
47
- if isinstance(requirement, input_requirements.Unique):
48
- self._is_unique = True
49
- return self._is_unique
50
-
51
- @property
52
- def is_temporary(self):
53
- return bool(self.config("is_temporary", silent=True))
54
-
55
- @property
56
- def is_required(self):
57
- if self._is_required is None:
58
- requirements = self.config("input_requirements")
59
- self._is_required = False
60
- for requirement in requirements:
61
- if isinstance(requirement, input_requirements.Required):
62
- self._is_required = True
63
- return self._is_required
64
-
65
- def model_column_configurations(self):
66
- nargs = len(inspect.getfullargspec(self.model_class.__init__).args) - 1
67
- fake_model = self.model_class(*([""] * nargs))
68
- return fake_model.all_columns()
69
-
70
- def configure(self, name, configuration, model_class):
71
- if not name:
72
- raise ValueError(f"Missing name for column in '{model_class.__name__}'")
73
- self.model_class = model_class
74
- self.name = name
75
- self._check_configuration(configuration)
76
- configuration = self._finalize_configuration(configuration)
77
- self.configuration = configuration
78
-
79
- def _check_configuration(self, configuration):
80
- """Check the configuration and throw exceptions as needed"""
81
- for key in self.required_configs:
82
- if key not in configuration:
83
- raise KeyError(
84
- f"Missing required configuration '{key}' for column '{self.name}' in '{self.model_class.__name__}'"
85
- )
86
- for key in configuration.keys():
87
- if key not in self.common_configs and key not in self.my_configs and key not in self.required_configs:
88
- raise KeyError(
89
- f"Configuration '{key}' not allowed for column '{self.name}' in '{self.model_class.__name__}'"
90
- )
91
- if "is_writeable" in configuration and type(configuration["is_writeable"]) != bool:
92
- raise ValueError("'is_writeable' must be a boolean")
93
- if configuration.get("on_change"):
94
- self._check_actions(configuration.get("on_change"), "on_change")
95
-
96
- self._check_created_by_source(configuration)
97
-
98
- def _finalize_configuration(self, configuration):
99
- """Make any changes to the configuration/fill in defaults"""
100
- if not "input_requirements" in configuration:
101
- configuration["input_requirements"] = []
102
- return configuration
103
-
104
- def _check_created_by_source(self, configuration):
105
- source_type = configuration.get("created_by_source_type")
106
- source_key = configuration.get("created_by_source_key")
107
- if not source_type and not source_key:
108
- return
109
-
110
- error_prefix = f"Misconfiguration for column '{self.name}' in '{self.model_class.__name__}': "
111
- if not source_type or not source_key:
112
- raise ValueError(
113
- f"{error_prefix} must provide both 'created_by_source_type' and 'created_by_source_key' but only one was provided."
114
- )
115
-
116
- if not isinstance(source_type, str):
117
- raise ValueError(
118
- f"{error_prefix} 'created_by_source_type' must be a string but is a '"
119
- + source_type.__class__.__name__
120
- + "'"
121
- )
122
- if not isinstance(source_key, str):
123
- raise ValueError(
124
- f"{error_prefix} 'created_by_source_key' must be a string but is a '"
125
- + source_key.__class__.__name__
126
- + "'"
127
- )
128
-
129
- allowed_types = ["authorization_data"]
130
- if source_type not in allowed_types:
131
- raise ValueError(
132
- f"{error_prefix} 'created_by_source_type' must be one of '" + "', '".join(allowed_types) + "'"
133
- )
134
- if configuration.get("setable"):
135
- raise ValueError(f"{error_prefix} you cannot set both 'setable' and 'created_by_source_type'")
136
-
137
- def _check_actions(self, actions, trigger_name):
138
- """Check that the given actions are actually understandable by the system"""
139
- if type(actions) != list:
140
- raise ValueError(
141
- "The actions provided to a trigger should be a list of callables/binding configs, but something "
142
- + f"else was provided for the '{trigger_name}' trigger in '{self.model_class.__name__}'"
143
- )
144
- for index, action in enumerate(actions):
145
- # if it's callable we're good. This includes functions, lambdas, callable objects,
146
- # and classes that will be callable when instantiated
147
- if callable(action):
148
- continue
149
- # the above pretty much covers everything. The only thing that we support otherwise
150
- # is a binding config containing a callable class.
151
- if type(action) == binding_config.BindingConfig:
152
- if callable(action.object_class):
153
- continue
154
-
155
- raise ValueError(
156
- f"Invalid action: action #{index+1} for trigger '{trigger_name} in '{self.model_class.__name__}'"
157
- )
158
-
159
- def config(self, key, silent=False):
160
- if not key in self.configuration:
161
- if silent:
162
- return None
163
- raise KeyError(f"column '{self.__class__.__name__}' does not have a configuration named '{key}'")
164
-
165
- return self.configuration[key]
166
-
167
- def additional_write_columns(self, is_create=False):
168
- additional_write_columns = {}
169
- for requirement in self.config("input_requirements"):
170
- additional_write_columns = {
171
- **additional_write_columns,
172
- **requirement.additional_write_columns(is_create=is_create),
173
- }
174
- return additional_write_columns
175
-
176
- def from_backend(self, value):
177
- """
178
- Takes the database representation and returns a python representation
179
-
180
- For instance, for an SQL date field, this will return a Python DateTime object
181
- """
182
- return value
183
-
184
- def to_backend(self, data):
185
- """
186
- Makes any changes needed to save the data to the backend.
187
-
188
- This typically means formatting changes - converting DateTime objects to database
189
- date strings, etc...
190
- """
191
- return data
192
-
193
- def to_json(self, model):
194
- """
195
- Grabs the column out of the model and converts it into a representation that can be turned into JSON
196
- """
197
- return {self.name: model.get(self.name, silent=True)}
198
-
199
- def input_errors(self, model, data):
200
- error = self.check_input(model, data)
201
- if error:
202
- return {self.name: error}
203
-
204
- for requirement in self.config("input_requirements"):
205
- error = requirement.check(model, data)
206
- if error:
207
- return {self.name: error}
208
-
209
- return {}
210
-
211
- def check_input(self, model, data):
212
- if self.name not in data or not data[self.name]:
213
- return ""
214
- return self.input_error_for_value(data[self.name])
215
-
216
- def pre_save(self, data, model):
217
- """
218
- Make any changes needed to the data before starting the save process
219
-
220
- The difference between this and transform_for_database is that transform_for_database only affects
221
- the data as it is going into the database, while this affects the data that will get persisted
222
- in the object as well. So for instance, for a "created" field, pre_save may fill in the current
223
- date with a Python DateTime object when the record is being saved, and then transform_for_database may
224
- turn that into an SQL-compatible date string.
225
-
226
- The difference between this and post_save is that this happens before the database is updated.
227
- As a result, if you need the model id to make your changes, it has to happen in post_save, not pre_save
228
- """
229
- if not model.exists:
230
- source_type = self.configuration.get("created_by_source_type")
231
- if source_type:
232
- if source_type == "authorization_data":
233
- authorization_data = self.di.build("input_output", cache=True).get_authorization_data()
234
- data[self.name] = authorization_data.get(self.config("created_by_source_key"), "N/A")
235
- if "setable" in self.configuration:
236
- setable = self.configuration["setable"]
237
- if callable(setable):
238
- data[self.name] = self.di.call_function(setable, data=data, model=model)
239
- else:
240
- data[self.name] = setable
241
- if not model.exists and "default" in self.configuration and self.name not in data:
242
- data[self.name] = self.configuration["default"]
243
- return data
244
-
245
- def post_save(self, data, model, id):
246
- """
247
- Make any changes needed after saving to the database
248
-
249
- data is the data being saved and id is the id of the record. Note that while the database is updated
250
- before this is called, the model isn't, so there will be a difference between what is in the database
251
- and what is in the object.
252
- """
253
- return data
254
-
255
- def save_finished(self, model):
256
- """
257
- Make any necessary changes needed after a save has completely finished.
258
-
259
- This is typically used for configurable triggers set by the developer. Column-specific behavior
260
- that needs to always happen is placed in pre_save or post_save because those affect the save
261
- process itself.
262
- """
263
- on_change_actions = self.config("on_change", silent=True)
264
- if on_change_actions and model.was_changed(self.name):
265
- self.execute_actions(on_change_actions, model)
266
-
267
- def values_match(self, value_1, value_2):
268
- """
269
- Compares two values to see if they are the same
270
- """
271
- return value_1 == value_2
272
-
273
- def pre_delete(self, model):
274
- """
275
- Make any changes needed to the data before starting the delete process
276
- """
277
- pass
278
-
279
- def post_delete(self, model):
280
- """
281
- Make any changes needed to the data before finishing the delete process
282
- """
283
- pass
284
-
285
- def can_provide(self, column_name):
286
- """
287
- This works together with self.provide to load ancillary data
288
-
289
- For instance, a foreign key will have an "id" column such as `user_id` but it can also load up
290
- the user model, which you expect to happen by requesting `model.user`. If a model receives a
291
- request for a column name that it doesn't recognize, it will loop through all the columns and
292
- call `can_provide` with the column name. We then have to return True or False to denote whether
293
- or not we can provide the thing being requested. If we return True then the model will then
294
- call `column.provide` with the data from the model and the requested column name
295
- """
296
- return False
297
-
298
- def provide(self, data, column_name):
299
- """
300
- This is called if the column declares that it can provide something, and should return the value
301
-
302
- See can_provide for more details on the flow here
303
- """
304
- pass
305
-
306
- def execute_actions(self, actions, model):
307
- for action in actions:
308
- if type(action) == binding_config.BindingConfig:
309
- action = self.di.build(action)
310
- elif inspect.isclass(action):
311
- action = self.di.build(action)
312
- if hasattr(action, "__call__"):
313
- self.di.call_function(action.__call__, model=model)
314
- else:
315
- self.di.call_function(action.__call__, model=model)
316
-
317
- def add_search(self, models, value, operator=None, relationship_reference=None):
318
- return models.where(self.build_condition(value, operator=operator))
319
-
320
- def build_condition(self, value, operator=None, column_prefix=""):
321
- """
322
- This is called by the read (and related) handlers to turn user input into a condition.
323
-
324
- Note that this may look like it is vulnerable to SQLi, but it isn't. These conditions aren't passed directly
325
- into a query. Rather, they are parsed by the condition parser before being sent into the backend.
326
- The condition parser can safely reconstruct the original pieces, and the backend can then use the data
327
- safely (and remember, the backend may not be an SQL anyway)
328
-
329
- As a result, this is perfectly safe for any user input, assuming normal system flow.
330
- """
331
- return f"{column_prefix}{self.name}={value}"
332
-
333
- def is_allowed_operator(self, operator, relationship_reference=None):
334
- """
335
- This is called when processing user data to decide if the end-user is specifying an allowed operator
336
- """
337
- return operator == "="
338
-
339
- def configure_n_plus_one(self, models):
340
- return models
341
-
342
- def check_search_value(self, value, operator=None, relationship_reference=None):
343
- return self.input_error_for_value(value, operator=operator)
344
-
345
- def input_error_for_value(self, value, operator=None):
346
- return ""
347
-
348
- def where_for_request(self, models, routing_data, authorization_data, input_output):
349
- """
350
- A hook to automatically apply filtering whenever the column makes an appearance in a get/update/list/search handler.
351
- """
352
- return models
353
-
354
- def validate_models_class(self, models_class, config_name="parent_models_class"):
355
- if not hasattr(models_class, "model_class"):
356
- if hasattr(models_class, "columns_configuration"):
357
- raise ValueError(
358
- f"'{config_name}' in configuration for column '{self.name}' in model class "
359
- + f"'{self.model_class.__name__}' appears to be a Model class, but it should be a Models class"
360
- )
361
- else:
362
- raise ValueError(
363
- f"'{config_name}' in configuration for column '{self.name}' should be a Models class, "
364
- + f"but it appears to be something unknown."
365
- )
366
-
367
- def camel_to_nice(self, string):
368
- string = re.sub("(.)([A-Z][a-z]+)", r"\1 \2", string)
369
- string = re.sub("([a-z0-9])([A-Z])", r"\1 \2", string).lower()
370
- return string
371
-
372
- def documentation(self, name=None, example=None, value=None):
373
- return self._auto_doc_class(name if name is not None else self.name, example=example, value=value)
@@ -1,26 +0,0 @@
1
- from .datetime import DateTime
2
-
3
-
4
- class Created(DateTime):
5
- my_configs = [
6
- "date_format",
7
- "default_date",
8
- "utc",
9
- ]
10
-
11
- def __init__(self, di, datetime, timezone):
12
- super().__init__(di, timezone)
13
- self.datetime = datetime
14
-
15
- @property
16
- def is_writeable(self):
17
- return False
18
-
19
- def pre_save(self, data, model):
20
- if model.exists:
21
- return data
22
- if self.config("utc", silent=True):
23
- now = self.datetime.datetime.now(self.datetime.timezone.utc)
24
- else:
25
- now = self.datetime.datetime.now(self._timezone)
26
- return {**data, self.name: now}
@@ -1,26 +0,0 @@
1
- from .string import String
2
-
3
-
4
- class CreatedByAuthorizationData(String):
5
- required_configs = [
6
- "authorization_data_key_name",
7
- ]
8
-
9
- def __init__(self, di):
10
- super().__init__(di)
11
-
12
- @property
13
- def is_writeable(self):
14
- return False
15
-
16
- def pre_save(self, data, model):
17
- if model.exists:
18
- return data
19
-
20
- authorization_data = self.di.build("input_output", cache=True).get_authorization_data()
21
- # data comes last so that it can override the info in the authorization data. This seems counter-intuitive,
22
- # but is important. You would think that you *don't* want the data from the authorization data to be
23
- # overridden (since this is mainly used for logging), but the trouble is that there are a variety of use-cases
24
- # where the application must provide the audit data. Examples include registration and login. In these
25
- # cases, authorization data will be empty, and must be provided by the applicaiton.
26
- return {self.name: authorization_data.get(self.config("authorization_data_key_name"), "N/A"), **data}
@@ -1,24 +0,0 @@
1
- from .string import String
2
-
3
-
4
- class CreatedByHeader(String):
5
- required_configs = [
6
- "header_name",
7
- ]
8
-
9
- def __init__(self, di):
10
- super().__init__(di)
11
-
12
- @property
13
- def is_writeable(self):
14
- return False
15
-
16
- def pre_save(self, data, model):
17
- if model.exists:
18
- return data
19
-
20
- input_output = self.di.build("input_output", cache=True)
21
- return {
22
- **data,
23
- self.name: input_output.get_request_header(self.config("header_name")),
24
- }
@@ -1,17 +0,0 @@
1
- from .string import String
2
-
3
-
4
- class CreatedByIp(String):
5
- def __init__(self, di):
6
- super().__init__(di)
7
-
8
- @property
9
- def is_writeable(self):
10
- return False
11
-
12
- def pre_save(self, data, model):
13
- if model.exists:
14
- return data
15
-
16
- input_output = self.di.build("input_output", cache=True)
17
- return {**data, self.name: input_output.get_client_ip()}
@@ -1,25 +0,0 @@
1
- from .string import String
2
-
3
-
4
- class CreatedByRoutingData(String):
5
- required_configs = [
6
- "routing_data_name",
7
- ]
8
-
9
- def __init__(self, di):
10
- super().__init__(di)
11
-
12
- @property
13
- def is_writeable(self):
14
- return False
15
-
16
- def pre_save(self, data, model):
17
- if model.exists:
18
- return data
19
-
20
- input_output = self.di.build("input_output", cache=True)
21
- routing_data = input_output.routing_data()
22
- return {
23
- **data,
24
- self.name: routing_data[self.config("routing_data_name")],
25
- }
@@ -1,17 +0,0 @@
1
- from .string import String
2
-
3
-
4
- class CreatedByUserAgent(String):
5
- def __init__(self, di):
6
- super().__init__(di)
7
-
8
- @property
9
- def is_writeable(self):
10
- return False
11
-
12
- def pre_save(self, data, model):
13
- if model.exists:
14
- return data
15
-
16
- input_output = self.di.build("input_output", cache=True)
17
- return {**data, self.name: input_output.get_request_header("user-agent")}
@@ -1,26 +0,0 @@
1
- from .datetime_micro import DateTimeMicro
2
-
3
-
4
- class CreatedMicro(DateTimeMicro):
5
- my_configs = [
6
- "date_format",
7
- "default_date",
8
- "utc",
9
- ]
10
-
11
- def __init__(self, di, datetime, timezone):
12
- super().__init__(di, timezone)
13
- self.datetime = datetime
14
-
15
- @property
16
- def is_writeable(self):
17
- return False
18
-
19
- def pre_save(self, data, model):
20
- if model.exists:
21
- return data
22
- if self.config("utc", silent=True):
23
- now = self.datetime.datetime.now(self.datetime.timezone.utc)
24
- else:
25
- now = self.datetime.datetime.now(self._timezone)
26
- return {**data, self.name: now}
@@ -1,109 +0,0 @@
1
- from .column import Column
2
- from datetime import datetime, timezone
3
- import dateparser
4
- from ..autodoc.schema import DateTime as AutoDocDateTime
5
-
6
-
7
- class DateTime(Column):
8
- _auto_doc_class = AutoDocDateTime
9
- _date_format = "%Y-%m-%d %H:%M:%S"
10
- _default_date = "0000-00-00 00:00:00"
11
-
12
- my_configs = [
13
- "date_format",
14
- "default_date",
15
- ]
16
-
17
- def __init__(self, di, timezone: datetime.tzinfo):
18
- super().__init__(di)
19
- self._timezone = timezone
20
-
21
- def _finalize_configuration(self, configuration):
22
- return {
23
- **{
24
- "date_format": self._date_format,
25
- "default_date": self._default_date,
26
- },
27
- **super()._finalize_configuration(configuration),
28
- }
29
-
30
- def from_backend(self, value):
31
- if not value or value == self.config("default_date"):
32
- date = None
33
- elif type(value) == str:
34
- date = dateparser.parse(value)
35
- else:
36
- date = value
37
- return date.replace(tzinfo=self._timezone) if date else None
38
-
39
- def to_backend(self, data):
40
- if not self.name in data or type(data[self.name]) == str or data[self.name] == None:
41
- return data
42
-
43
- # hopefully this is a Python datetime object in UTC timezone...
44
- return {**data, **{self.name: data[self.name].strftime(self.config("date_format"))}}
45
-
46
- def to_json(self, model):
47
- datetime = model.get(self.name, silent=True)
48
- return {self.name: datetime.isoformat() if datetime else None}
49
-
50
- def build_condition(self, value, operator=None, column_prefix=""):
51
- date = dateparser.parse(value).astimezone(self._timezone).strftime(self.config("date_format"))
52
- if not operator:
53
- operator = "="
54
- return f"{column_prefix}{self.name}{operator}{date}"
55
-
56
- def is_allowed_operator(self, operator, relationship_reference=None):
57
- """
58
- This is called when processing user data to decide if the end-user is specifying an allowed operator
59
- """
60
- return operator in ["=", "<", ">", "<=", ">="]
61
-
62
- def input_error_for_value(self, value, operator=None):
63
- value = dateparser.parse(value)
64
- if not value:
65
- return "given value did not appear to be a valid date"
66
- if not value.tzinfo:
67
- return "date is missing timezone information"
68
- return ""
69
-
70
- def values_match(self, value_1, value_2):
71
- """
72
- Compares two values to see if they are the same
73
- """
74
- # in this function we deal with data directly out of the backend, so our date is likely
75
- # to be string-ified and we want to look for default (e.g. null) values in string form.
76
- if type(value_1) == str and "0000-00-00" in value_1:
77
- value_1 = None
78
- if type(value_2) == str and "0000-00-00" in value_2:
79
- value_2 = None
80
- number_values = 0
81
- if value_1:
82
- number_values += 1
83
- if value_2:
84
- number_values += 1
85
- if number_values == 0:
86
- return True
87
- if number_values == 1:
88
- return False
89
-
90
- if type(value_1) == str:
91
- value_1 = dateparser.parse(value_1)
92
- if type(value_2) == str:
93
- value_2 = dateparser.parse(value_2)
94
-
95
- # we need to make sure we're comparing in the same timezones. For our purposes, a difference in timezone
96
- # is fine as long as they represent the same time (e.g. 16:00EST == 20:00UTC). For python, same time in different
97
- # timezones is treated as different datetime objects.
98
- if value_1.tzinfo is not None and value_2.tzinfo is not None:
99
- value_1 = value_1.astimezone(value_2.tzinfo)
100
-
101
- # two times can be the same but if one is datetime-aware and one is not, python will treat them as not equal.
102
- # we want to treat such times as being the same. Therefore, check for equality but ignore the timezone.
103
- for to_check in ["year", "month", "day", "hour", "minute", "second", "microsecond"]:
104
- if getattr(value_1, to_check) != getattr(value_2, to_check):
105
- return False
106
-
107
- # and since we already converted the timezones to match (or one has a timezone and one doesn't), we're good to go.
108
- # if we passed the above loop then the times are the same.
109
- return True
@@ -1,13 +0,0 @@
1
- from re import T
2
- from .datetime import DateTime
3
- from datetime import datetime, timezone
4
- import dateparser
5
- from ..autodoc.schema import DateTime as AutoDocDateTime
6
-
7
-
8
- class DateTimeMicro(DateTime):
9
- _date_format = "%Y-%m-%d %H:%M:%S.%f"
10
- _default_date = "0000-00-00 00:00:00.000000"
11
-
12
- def __init__(self, di, timezone: datetime.tzinfo):
13
- super().__init__(di, timezone)
@@ -1,18 +0,0 @@
1
- from .string import String
2
- import re
3
-
4
-
5
- class Email(String):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- def input_error_for_value(self, value, operator=None):
10
- if type(value) != str:
11
- return f"Value must be a string for {self.name}"
12
- if operator and operator.lower() == "like":
13
- # don't check for an email if doing a fuzzy search, since we may be searching
14
- # for a partial email
15
- return ""
16
- if re.search(r"^[^@\s]+@[^@]+\.[^@]+$", value):
17
- return ""
18
- return "Invalid email address"