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,71 @@
1
+ from .audit import Audit
2
+ from .belongs_to_id import BelongsToId
3
+ from .belongs_to_model import BelongsToModel
4
+ from .belongs_to_self import BelongsToSelf
5
+ from .boolean import Boolean
6
+ from .category_tree import CategoryTree
7
+ from .category_tree_ancestors import CategoryTreeAncestors
8
+ from .category_tree_children import CategoryTreeChildren
9
+ from .category_tree_descendants import CategoryTreeDescendants
10
+ from .created import Created
11
+ from .created_by_authorization_data import CreatedByAuthorizationData
12
+ from .created_by_header import CreatedByHeader
13
+ from .created_by_ip import CreatedByIp
14
+ from .created_by_routing_data import CreatedByRoutingData
15
+ from .created_by_user_agent import CreatedByUserAgent
16
+ from .date import Date
17
+ from .datetime import Datetime
18
+ from .email import Email
19
+ from .float import Float
20
+ from .has_many import HasMany
21
+ from .has_many_self import HasManySelf
22
+ from .has_one import HasOne
23
+ from .integer import Integer
24
+ from .json import Json
25
+ from .many_to_many_ids import ManyToManyIds
26
+ from .many_to_many_ids_with_data import ManyToManyIdsWithData
27
+ from .many_to_many_models import ManyToManyModels
28
+ from .many_to_many_pivots import ManyToManyPivots
29
+ from .phone import Phone
30
+ from .select import Select
31
+ from .string import String
32
+ from .timestamp import Timestamp
33
+ from .updated import Updated
34
+ from .uuid import Uuid
35
+
36
+ __all__ = [
37
+ "Audit",
38
+ "BelongsToId",
39
+ "BelongsToModel",
40
+ "BelongsToSelf",
41
+ "Boolean",
42
+ "CategoryTree",
43
+ "CategoryTreeAncestors",
44
+ "CategoryTreeChildren",
45
+ "CategoryTreeDescendants",
46
+ "Created",
47
+ "CreatedByAuthorizationData",
48
+ "CreatedByHeader",
49
+ "CreatedByIp",
50
+ "CreatedByRoutingData",
51
+ "CreatedByUserAgent",
52
+ "Date",
53
+ "Datetime",
54
+ "Email",
55
+ "Float",
56
+ "HasMany",
57
+ "HasManySelf",
58
+ "HasOne",
59
+ "Integer",
60
+ "Json",
61
+ "ManyToManyIds",
62
+ "ManyToManyIdsWithData",
63
+ "ManyToManyModels",
64
+ "ManyToManyPivots",
65
+ "Phone",
66
+ "Select",
67
+ "String",
68
+ "Timestamp",
69
+ "Updated",
70
+ "Uuid",
71
+ ]
@@ -0,0 +1,306 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs, decorators
6
+ from clearskies.column import Column
7
+ from clearskies.columns.has_many import HasMany
8
+
9
+ if TYPE_CHECKING:
10
+ from clearskies import Model, typing
11
+
12
+
13
+ class Audit(HasMany):
14
+ """
15
+ Enables auditing for a model.
16
+
17
+ Specify the audit class to use and attach this column to your model. Everytime the model is created/updated/deleted,
18
+ the audit class will record the action and the changes. Your audit model must have the following columns:
19
+
20
+ | Name | type |
21
+ |-------------|----------|
22
+ | class_name | str |
23
+ | resource_id | str |
24
+ | action | str |
25
+ | data | json |
26
+ | created_at | created |
27
+
28
+ The names are not currently adjustable.
29
+
30
+ 1. Class is a string that records the name of the class that the action happened for. This allows you to use
31
+ the same audit class for multiple, different, resources.
32
+ 2. resource_id is the id of the record which the audit entry is for.
33
+ 3. Action is the actual action taken (create/update/delete)
34
+ 4. Data is a serialized record of what columns in the record were changed (both their previous and new values)
35
+ 5. The time the audit record was created
36
+
37
+ Here's an example:
38
+
39
+ ```
40
+ #!/usr/bin/env python
41
+
42
+ import clearskies
43
+
44
+
45
+ class PersonHistory(clearskies.Model):
46
+ id_column_name = "id"
47
+ backend = clearskies.backends.MemoryBackend()
48
+
49
+ id = clearskies.columns.Uuid()
50
+ class_name = clearskies.columns.String()
51
+ resource_id = clearskies.columns.String()
52
+ action = clearskies.columns.String()
53
+ data = clearskies.columns.Json()
54
+ created_at = clearskies.columns.Created(date_format="%Y-%m-%d %H:%M:%S.%f")
55
+
56
+
57
+ class Person(clearskies.Model):
58
+ id_column_name = "id"
59
+ backend = clearskies.backends.MemoryBackend()
60
+
61
+ id = clearskies.columns.Uuid()
62
+ name = clearskies.columns.String()
63
+ age = clearskies.columns.Integer()
64
+ history = clearskies.columns.Audit(audit_model_class=PersonHistory)
65
+
66
+
67
+ def test_audit(persons: Person):
68
+ bob = persons.create({"name": "Bob", "age": 30})
69
+ bob.save({"age": 31})
70
+ bob.save({"age": 32})
71
+ bob.delete()
72
+
73
+ return bob.history.sort_by("created_at", "asc")
74
+
75
+
76
+ cli = clearskies.contexts.Cli(
77
+ clearskies.endpoints.Callable(
78
+ test_audit,
79
+ model_class=PersonHistory,
80
+ return_records=True,
81
+ readable_column_names=["id", "action", "data", "created_at"],
82
+ ),
83
+ classes=[Person, PersonHistory],
84
+ )
85
+
86
+ if __name__ == "__main__":
87
+ cli()
88
+ ```
89
+
90
+ And if you invoke this you will get back:
91
+
92
+ ```
93
+ {
94
+ "status": "success",
95
+ "error": "",
96
+ "data": [
97
+ {
98
+ "id": "25eae3d9-d64b-4819-9e31-70e1d4d34945",
99
+ "action": "create",
100
+ "data": {"name": "Bob", "age": 30, "id": "145c7cf2-3fc6-41c8-b3b2-f68eb2b08b03"},
101
+ "created_at": "2025-12-04T12:14:42.540108+00:00",
102
+ },
103
+ {
104
+ "id": "c8dea383-ae1d-4e25-a58e-d4ebdf2fb4f9",
105
+ "action": "update",
106
+ "data": {"from": {"age": 30}, "to": {"age": 31}},
107
+ "created_at": "2025-12-04T12:14:42.540384+00:00",
108
+ },
109
+ {
110
+ "id": "5e1f3067-a45a-4463-8a66-3b92d47a8863",
111
+ "action": "update",
112
+ "data": {"from": {"age": 31}, "to": {"age": 32}},
113
+ "created_at": "2025-12-04T12:14:42.540595+00:00",
114
+ },
115
+ {
116
+ "id": "44179d35-9abb-4117-803c-ec87bb58adb5",
117
+ "action": "delete",
118
+ "data": {"name": "Bob", "age": 32, "id": "145c7cf2-3fc6-41c8-b3b2-f68eb2b08b03"},
119
+ "created_at": "2025-12-04T12:14:42.540747+00:00",
120
+ },
121
+ ],
122
+ "pagination": {"number_results": 4, "limit": 0, "next_page": {}},
123
+ "input_errors": {},
124
+ }
125
+ ```
126
+
127
+ """
128
+
129
+ """ The model class for the destination that will store the audit data. """
130
+ audit_model_class = configs.ModelClass(required=True)
131
+
132
+ """
133
+ A list of columns that shouldn't be copied into the audit record.
134
+
135
+ To be clear, these are columns from the model class that the audit column is attached to.
136
+ If only excluded columns are updated then no audit record will be created.
137
+ """
138
+ exclude_columns = configs.ModelColumns(default=[])
139
+
140
+ """
141
+ A list of columns that should be masked when copied into the audit record.
142
+
143
+ With masked columns a generic value is placed in the audit record (e.g. XXXXX) which denotes that
144
+ the column was changed, but it does not record either old or new values.
145
+ """
146
+ mask_columns = configs.ModelColumns(default=[])
147
+
148
+ """ Columns from the child table that should be included when converting this column to JSON. """
149
+ readable_child_column_names = configs.ReadableModelColumns(
150
+ "audit_model_class", default=["resource_id", "action", "data", "created_at"]
151
+ )
152
+
153
+ """
154
+ Since this column is always populated automatically, it is never directly writeable.
155
+ """
156
+ is_writeable = configs.Boolean(default=False)
157
+ is_searchable = configs.Boolean(default=False)
158
+ _descriptor_config_map = None
159
+ _parent_columns: dict[str, Column] | None
160
+
161
+ @decorators.parameters_to_properties
162
+ def __init__(
163
+ self,
164
+ audit_model_class,
165
+ exclude_columns: list[str] = [],
166
+ mask_columns: list[str] = [],
167
+ foreign_column_name: str | None = None,
168
+ readable_child_column_names: list[str] = [],
169
+ where: typing.condition | list[typing.condition] = [],
170
+ default: str | None = None,
171
+ is_readable: bool = True,
172
+ is_temporary: bool = False,
173
+ on_change_pre_save: typing.action | list[typing.action] = [],
174
+ on_change_post_save: typing.action | list[typing.action] = [],
175
+ on_change_save_finished: typing.action | list[typing.action] = [],
176
+ ):
177
+ self.child_model_class = self.audit_model_class
178
+ self.foreign_column_name = "resource_id"
179
+
180
+ def save_finished(self, model: Model):
181
+ super().save_finished(model)
182
+ old_data: dict[str, Any] = model._previous_data
183
+ new_data: dict[str, Any] = model.get_raw_data()
184
+ exclude_columns = self.exclude_columns
185
+ mask_columns = self.mask_columns
186
+ model_columns = self.get_model_columns()
187
+
188
+ if not old_data:
189
+ create_data: dict[str, Any] = {}
190
+ for key in new_data.keys():
191
+ if key in self.exclude_columns or key == self.name:
192
+ continue
193
+ if key in model_columns:
194
+ column_data = model_columns[key].to_json(model)
195
+ else:
196
+ column_data = {key: new_data[key]}
197
+
198
+ create_data = {
199
+ **create_data,
200
+ **column_data,
201
+ }
202
+ if key in mask_columns and key in create_data:
203
+ create_data[key] = "****"
204
+ self.record(model, "create", data=create_data)
205
+ return
206
+
207
+ # note that this is fairly simple logic to get started. It's not going to detect changes that happen
208
+ # in other "tables". For instance, disconnecting a record by deleting an entry in a many-to-many relationship
209
+ # won't be picked up by this.
210
+ old_model = model.empty()
211
+ old_model._data = old_data
212
+ from_data: dict[str, Any] = {}
213
+ to_data: dict[str, Any] = {}
214
+ for column, new_value in new_data.items():
215
+ if column in exclude_columns or column not in old_data or column == self.name:
216
+ continue
217
+ if old_data[column] == new_value:
218
+ continue
219
+ from_data = {
220
+ **from_data,
221
+ **(
222
+ model_columns[column].to_json(old_model)
223
+ if column in model_columns
224
+ else {column: old_data.get(column)}
225
+ ),
226
+ }
227
+ to_data = {
228
+ **to_data,
229
+ **(
230
+ model_columns[column].to_json(model)
231
+ if column in model_columns
232
+ else {column: model._data.get(column)}
233
+ ),
234
+ }
235
+ if column in mask_columns and column in to_data:
236
+ to_data[column] = "****"
237
+ from_data[column] = "****"
238
+ if not from_data and not to_data:
239
+ return
240
+
241
+ self.record(
242
+ model,
243
+ "update",
244
+ data={
245
+ "from": from_data,
246
+ "to": to_data,
247
+ },
248
+ )
249
+
250
+ def post_delete(self, model: Model) -> None:
251
+ super().post_delete(model)
252
+ exclude_columns = self.exclude_columns
253
+ model_columns = self.get_model_columns()
254
+ mask_columns = self.mask_columns
255
+
256
+ final_data: dict[str, Any] = {}
257
+ for key in model._data.keys():
258
+ if key in exclude_columns or key == self.name:
259
+ continue
260
+ final_data = {
261
+ **final_data,
262
+ **(model_columns[key].to_json(model) if key in model_columns else {key: model.get_raw_data().get(key)}),
263
+ }
264
+
265
+ for key in mask_columns:
266
+ if key not in final_data:
267
+ continue
268
+ final_data[key] = "****"
269
+
270
+ self.child_model.create(
271
+ {
272
+ "class_name": self.model_class.__name__,
273
+ "resource_id": getattr(model, self.model_class.id_column_name),
274
+ "action": "delete",
275
+ "data": final_data,
276
+ }
277
+ )
278
+
279
+ @property
280
+ def parent_columns(self) -> dict[str, Column]:
281
+ if self._parent_columns == None:
282
+ self._parent_columns = self.di.build(self.model_class, cache=True).columns()
283
+ return self._parent_columns # type: ignore[return-value]
284
+
285
+ def record(self, model, action, data=None, record_data=None):
286
+ audit_data = {
287
+ "class_name": self.model_class.__name__,
288
+ "resource_id": getattr(model, self.model_class.id_column_name),
289
+ "action": action,
290
+ }
291
+ if data is not None:
292
+ audit_data["data"] = data
293
+ if record_data is not None:
294
+ audit_data = {
295
+ **audit_data,
296
+ **record_data,
297
+ }
298
+
299
+ self.child_model.create(audit_data)
300
+
301
+ def __get__(self, model, cls):
302
+ if model is None:
303
+ self.model_class = cls
304
+ return self # type: ignore
305
+
306
+ return super().__get__(model, cls).where(f"class_name={self.model_class.__name__}")