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,33 @@
1
+ from clearskies.validators.after_column import AfterColumn
2
+ from clearskies.validators.before_column import BeforeColumn
3
+ from clearskies.validators.in_the_future import InTheFuture
4
+ from clearskies.validators.in_the_future_at_least import InTheFutureAtLeast
5
+ from clearskies.validators.in_the_future_at_most import InTheFutureAtMost
6
+ from clearskies.validators.in_the_past import InThePast
7
+ from clearskies.validators.in_the_past_at_least import InThePastAtLeast
8
+ from clearskies.validators.in_the_past_at_most import InThePastAtMost
9
+ from clearskies.validators.maximum_length import MaximumLength
10
+ from clearskies.validators.maximum_value import MaximumValue
11
+ from clearskies.validators.minimum_length import MinimumLength
12
+ from clearskies.validators.minimum_value import MinimumValue
13
+ from clearskies.validators.required import Required
14
+ from clearskies.validators.timedelta import Timedelta
15
+ from clearskies.validators.unique import Unique
16
+
17
+ __all__ = [
18
+ "AfterColumn",
19
+ "BeforeColumn",
20
+ "InTheFuture",
21
+ "InTheFutureAtLeast",
22
+ "InTheFutureAtMost",
23
+ "InThePast",
24
+ "InThePastAtLeast",
25
+ "InThePastAtMost",
26
+ "MaximumLength",
27
+ "MaximumValue",
28
+ "MinimumLength",
29
+ "MaximumLength",
30
+ "Required",
31
+ "Timedelta",
32
+ "Unique",
33
+ ]
@@ -0,0 +1,62 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import dateparser
7
+
8
+ import clearskies.configs
9
+ import clearskies.parameters_to_properties
10
+ from clearskies.validator import Validator
11
+
12
+ if TYPE_CHECKING:
13
+ import clearskies.model
14
+
15
+
16
+ class AfterColumn(Validator):
17
+ """The name of the other date column for comparison."""
18
+
19
+ other_column_name = clearskies.configs.String(default="", required=True)
20
+
21
+ """
22
+ If true, then this column is allowed to be eqaul to the other column.
23
+ """
24
+ allow_equal = clearskies.configs.Boolean(default=False)
25
+
26
+ @clearskies.parameters_to_properties.parameters_to_properties
27
+ def __init__(self, other_column_name: str, allow_equal: bool = False):
28
+ self.other_column_name = other_column_name
29
+ self.allow_equal = allow_equal
30
+ self.finalize_and_validate_configuration()
31
+
32
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
33
+ # we won't check anything for missing values (columns should be required if that is an issue)
34
+ if not data.get(column_name):
35
+ return ""
36
+ my_value = data[column_name]
37
+ other_value = data.get(self.other_column_name, getattr(model, self.other_column_name))
38
+ # again, no checks for non-values
39
+ if not other_value:
40
+ return ""
41
+
42
+ my_value = dateparser.parse(my_value) if isinstance(my_value, str) else my_value
43
+ if not my_value:
44
+ return f"'{column_name}' was not a valid date."
45
+
46
+ if type(other_value) != str and type(other_value) != datetime.datetime:
47
+ return f"'{self.other_column_name}' was not a valid date."
48
+ other_value = dateparser.parse(other_value) if isinstance(other_value, str) else other_value
49
+ if not other_value:
50
+ return f"'{self.other_column_name}' was not a valid date."
51
+
52
+ return self.date_comparison(my_value, other_value, column_name)
53
+
54
+ def date_comparison(
55
+ self, incoming_date: datetime.datetime, comparison_date: datetime.datetime, column_name: str
56
+ ) -> str:
57
+ if incoming_date == comparison_date:
58
+ return "" if self.allow_equal else f"'{column_name}' must be after '{self.other_column_name}'"
59
+
60
+ if incoming_date < comparison_date:
61
+ return f"'{column_name}' must be after '{self.other_column_name}'"
62
+ return ""
@@ -0,0 +1,13 @@
1
+ import datetime
2
+
3
+ from clearskies.validators.after_column import AfterColumn
4
+
5
+
6
+ class BeforeColumn(AfterColumn):
7
+ def date_comparison(self, incoming_date: datetime.datetime, comparison_date: datetime.datetime, column_name) -> str:
8
+ if incoming_date == comparison_date:
9
+ return "" if self.allow_equal else f"'{column_name}' must be before '{self.other_column_name}'"
10
+
11
+ if incoming_date > comparison_date:
12
+ return f"'{column_name}' must be before '{self.other_column_name}'"
13
+ return ""
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from collections import OrderedDict
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import dateparser
8
+
9
+ import clearskies.configs
10
+ import clearskies.di
11
+ import clearskies.parameters_to_properties
12
+ from clearskies.validator import Validator
13
+
14
+ if TYPE_CHECKING:
15
+ import clearskies.model
16
+
17
+
18
+ class InTheFuture(Validator, clearskies.di.InjectableProperties):
19
+ utcnow = clearskies.di.inject.Utcnow()
20
+
21
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
22
+ if not data.get(column_name):
23
+ return ""
24
+
25
+ as_date = dateparser.parse(data[column_name]) if isinstance(data[column_name], str) else data[column_name]
26
+ if not as_date:
27
+ return f"'{column_name}' was not a valid date"
28
+ if as_date.tzinfo == None:
29
+ as_date = as_date.replace(tzinfo=datetime.timezone.utc)
30
+ if as_date <= self.utcnow:
31
+ return f"'{column_name}' must be in the future"
32
+ return ""
@@ -0,0 +1,11 @@
1
+ import datetime
2
+
3
+ from clearskies.validators.timedelta import Timedelta
4
+
5
+
6
+ class InTheFutureAtLeast(Timedelta):
7
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
8
+ if as_date < self.utcnow + self.timedelta:
9
+ human_friendly = self.delta_human_friendly()
10
+ return f"'{column_name}' must be at least {human_friendly} in the future."
11
+ return ""
@@ -0,0 +1,10 @@
1
+ import datetime
2
+
3
+ from clearskies.validators.timedelta import Timedelta
4
+
5
+
6
+ class InTheFutureAtMost(Timedelta):
7
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
8
+ if as_date > self.utcnow + self.timedelta:
9
+ return f"'{column_name}' must be at most {self.delta_human_friendly()} in the future."
10
+ return ""
@@ -0,0 +1,32 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from collections import OrderedDict
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import dateparser
8
+
9
+ import clearskies.configs
10
+ import clearskies.di
11
+ import clearskies.parameters_to_properties
12
+ from clearskies.validator import Validator
13
+
14
+ if TYPE_CHECKING:
15
+ import clearskies.model
16
+
17
+
18
+ class InThePast(Validator, clearskies.di.InjectableProperties):
19
+ utcnow = clearskies.di.inject.Utcnow()
20
+
21
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
22
+ if not data.get(column_name):
23
+ return ""
24
+
25
+ as_date = dateparser.parse(data[column_name]) if isinstance(data[column_name], str) else data[column_name]
26
+ if not as_date:
27
+ return f"'{column_name}' was not a valid date"
28
+ if as_date.tzinfo == None:
29
+ as_date = as_date.replace(tzinfo=datetime.timezone.utc)
30
+ if as_date >= self.utcnow:
31
+ return f"'{column_name}' must be in the past"
32
+ return ""
@@ -0,0 +1,10 @@
1
+ import datetime
2
+
3
+ from clearskies.validators.timedelta import Timedelta
4
+
5
+
6
+ class InThePastAtLeast(Timedelta):
7
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
8
+ if as_date > self.utcnow - self.timedelta:
9
+ return f"'{column_name}' must be at least {self.delta_human_friendly()} in the past."
10
+ return ""
@@ -0,0 +1,10 @@
1
+ import datetime
2
+
3
+ from clearskies.validators.timedelta import Timedelta
4
+
5
+
6
+ class InThePastAtMost(Timedelta):
7
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
8
+ if as_date < self.utcnow - self.timedelta:
9
+ return f"'{column_name}' must be at most {self.delta_human_friendly()} in the past."
10
+ return ""
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import clearskies.configs
7
+ from clearskies.validator import Validator
8
+
9
+ if TYPE_CHECKING:
10
+ import clearskies.model
11
+
12
+
13
+ class MaximumLength(Validator):
14
+ maximum_length = clearskies.configs.Integer(required=True)
15
+
16
+ def __init__(self, maximum_length: int):
17
+ self.maximum_length = maximum_length
18
+ self.finalize_and_validate_configuration()
19
+
20
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
21
+ # we won't check anything for missing values (columns should be required if that is an issue)
22
+ if not data.get(column_name):
23
+ return ""
24
+ if len(data[column_name]) <= self.maximum_length:
25
+ return ""
26
+ return f"'{column_name}' must be at most {self.maximum_length} characters long."
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import clearskies.configs
7
+ from clearskies.validator import Validator
8
+
9
+ if TYPE_CHECKING:
10
+ import clearskies.model
11
+
12
+
13
+ class MaximumValue(Validator):
14
+ maximum_value = clearskies.configs.Integer(required=True)
15
+
16
+ def __init__(self, maximum_value: int):
17
+ self.maximum_value = maximum_value
18
+ self.finalize_and_validate_configuration()
19
+
20
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
21
+ if column_name not in data:
22
+ return ""
23
+ try:
24
+ value = float(data[column_name])
25
+ except ValueError:
26
+ return f"{column_name} must be an integer or float"
27
+ if float(value) <= self.maximum_value:
28
+ return ""
29
+ return f"'{column_name}' must be at most {self.maximum_value}."
@@ -0,0 +1,26 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import clearskies.configs
7
+ from clearskies.validator import Validator
8
+
9
+ if TYPE_CHECKING:
10
+ import clearskies.model
11
+
12
+
13
+ class MinimumLength(Validator):
14
+ minimum_length = clearskies.configs.Integer(required=True)
15
+
16
+ def __init__(self, minimum_length: int):
17
+ self.minimum_length = minimum_length
18
+ self.finalize_and_validate_configuration()
19
+
20
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
21
+ # we won't check anything for missing values (columns should be required if that is an issue)
22
+ if not data.get(column_name):
23
+ return ""
24
+ if len(data[column_name]) >= self.minimum_length:
25
+ return ""
26
+ return f"'{column_name}' must be at least {self.minimum_length} characters long."
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import clearskies.configs
7
+ from clearskies.validator import Validator
8
+
9
+ if TYPE_CHECKING:
10
+ import clearskies.model
11
+
12
+
13
+ class MinimumValue(Validator):
14
+ minimum_value = clearskies.configs.Integer(required=True)
15
+
16
+ def __init__(self, minimum_value: int):
17
+ self.minimum_value = minimum_value
18
+ self.finalize_and_validate_configuration()
19
+
20
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
21
+ if column_name not in data:
22
+ return ""
23
+ try:
24
+ value = float(data[column_name])
25
+ except ValueError:
26
+ return f"{column_name} must be an integer or float"
27
+ if float(value) >= self.minimum_value:
28
+ return ""
29
+ return f"'{column_name}' must be at least {self.minimum_value}."
@@ -0,0 +1,35 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import clearskies.configs
7
+ from clearskies import parameters_to_properties
8
+ from clearskies.validator import Validator
9
+
10
+ if TYPE_CHECKING:
11
+ import clearskies.model
12
+
13
+
14
+ class Required(Validator):
15
+ is_required = True
16
+
17
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
18
+ # you'd think that "required" is straight forward and we want an input error if it isn't found.
19
+ # this isn't strictly true though. If the model already exists, the column has a value in the model already,
20
+ # and the column is completely missing from the input data, then it is actually perfectly fine (because
21
+ # there will still be a value in the column after the save). However, if the model doesn't exist, then
22
+ # we must require the column in the data with an actual value.
23
+ has_value = False
24
+ has_some_value = False
25
+ if column_name in data:
26
+ has_some_value = True
27
+ if type(data[column_name]) == str:
28
+ has_value = bool(data[column_name].strip())
29
+ else:
30
+ has_value = bool(data[column_name])
31
+ if has_value:
32
+ return ""
33
+ if model and getattr(model, column_name) and not has_some_value:
34
+ return ""
35
+ return f"'{column_name}' is required."
@@ -0,0 +1,59 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from collections import OrderedDict
5
+ from typing import TYPE_CHECKING, Any
6
+
7
+ import dateparser
8
+
9
+ import clearskies.configs
10
+ import clearskies.di
11
+ import clearskies.parameters_to_properties
12
+ from clearskies.validator import Validator
13
+
14
+ if TYPE_CHECKING:
15
+ import clearskies.model
16
+
17
+
18
+ class Timedelta(Validator, clearskies.di.InjectableProperties):
19
+ timedelta = clearskies.configs.Timedelta(default=None)
20
+
21
+ utcnow = clearskies.di.inject.Utcnow()
22
+
23
+ @clearskies.parameters_to_properties.parameters_to_properties
24
+ def __init__(self, timedelta: datetime.timedelta):
25
+ self.finalize_and_validate_configuration()
26
+
27
+ def delta_human_friendly(self):
28
+ remainder = int(self.timedelta.total_seconds())
29
+ parts = []
30
+ conversion = OrderedDict(
31
+ [
32
+ ("year", 31536000),
33
+ ("day", 86400),
34
+ ("hour", 3600),
35
+ ("minute", 60),
36
+ ("second", 1),
37
+ ]
38
+ )
39
+ for name, num_seconds in conversion.items():
40
+ if num_seconds > remainder:
41
+ continue
42
+ amount = int(remainder / num_seconds)
43
+ remainder -= amount * num_seconds
44
+ parts.append(f"{amount} {name}" + ("s" if amount != 1 else ""))
45
+ return ", ".join(parts)
46
+
47
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
48
+ if not data.get(column_name):
49
+ return ""
50
+
51
+ as_date = dateparser.parse(data[column_name]) if isinstance(data[column_name], str) else data[column_name]
52
+ if not as_date:
53
+ return f"'{column_name}' was not a valid date"
54
+ if as_date.tzinfo == None:
55
+ as_date = as_date.replace(tzinfo=datetime.timezone.utc)
56
+ return self.check_timedelta(as_date, column_name)
57
+
58
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
59
+ return ""
@@ -0,0 +1,31 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import clearskies.configs
7
+ from clearskies import parameters_to_properties
8
+ from clearskies.validator import Validator
9
+
10
+ if TYPE_CHECKING:
11
+ import clearskies.model
12
+
13
+
14
+ class Unique(Validator):
15
+ is_unique = True
16
+
17
+ def check(self, model: clearskies.model.Model, column_name: str, data: dict[str, Any]) -> str:
18
+ # Unique is mildly tricky. We obviously want to search the backend for the new value,
19
+ # but we need to first skip this if our column is not being set, or if we're editing
20
+ # the model and nothing is changing.
21
+ if column_name not in data:
22
+ return ""
23
+ new_value = data[column_name]
24
+ if model and getattr(model, column_name) == new_value:
25
+ return ""
26
+
27
+ as_query = model.as_query()
28
+ matching_model = as_query.find(f"{column_name}={new_value}")
29
+ if matching_model:
30
+ return f"Invalid value for '{column_name}': the given value already exists, and must be unique."
31
+ return ""