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,24 +1,23 @@
1
- from .base import Base
2
- from ..binding_config import BindingConfig
1
+ from __future__ import annotations
3
2
 
3
+ from clearskies import configs, decorators
4
+ from clearskies.security_header import SecurityHeader
4
5
 
5
- class HSTS(Base):
6
- max_age = None
7
- include_sub_domains = None
8
6
 
9
- def __init__(self, environment):
10
- super().__init__(environment)
7
+ class Hsts(SecurityHeader):
8
+ max_age = configs.Integer(default=31536000)
9
+ include_sub_domains = configs.Boolean()
11
10
 
12
- def configure(self, max_age=31536000, include_sub_domains=False):
13
- self.max_age = max_age
14
- self.include_sub_domains = include_sub_domains
11
+ @decorators.parameters_to_properties
12
+ def __init__(
13
+ self,
14
+ max_age: int = 31536000,
15
+ include_sub_domains: bool = False,
16
+ ):
17
+ self.finalize_and_validate_configuration()
15
18
 
16
19
  def set_headers_for_input_output(self, input_output):
17
20
  value = f"max-age={self.max_age} ;"
18
21
  if self.include_sub_domains:
19
22
  value += " includeSubDomains"
20
- input_output.set_header("strict-transport-security", value)
21
-
22
-
23
- def hsts(max_age=31536000, include_sub_domains=False):
24
- return BindingConfig(HSTS, max_age=max_age, include_sub_domains=include_sub_domains)
23
+ input_output.response_headers.add("strict-transport-security", value)
clearskies/typing.py ADDED
@@ -0,0 +1,11 @@
1
+ from typing import Any, Callable
2
+
3
+ from clearskies.action import Action
4
+ from clearskies.query import Condition
5
+ from clearskies.validator import Validator
6
+
7
+ action = Callable[..., dict[str, Any]] | Action
8
+ condition = str | Callable | Condition
9
+ join = str | Callable[..., str]
10
+ validator = Callable[..., str] | Validator
11
+ response = str | bytes | dict[str, Any] | list[Any]
@@ -0,0 +1,36 @@
1
+ from __future__ import annotations
2
+
3
+ from abc import ABC, abstractmethod
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ from clearskies import configurable
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import Column, Model
10
+
11
+
12
+ class Validator(ABC, configurable.Configurable):
13
+ """
14
+ Attach input validation rules to columns.
15
+
16
+ The validators provide a way to attach input validation logic to columns. The columns themselves already
17
+ provide basic validation (making sure strings are strings, integers are integers, etc...) but these classes
18
+ allow for more detailed rules.
19
+
20
+ It's important to understand that validators only apply to client input, which means that input validation
21
+ is only enforced by appropriate endpoints. If you inject a model into a function of your own and execute
22
+ a save operation with it, validators will **NOT** be checked.
23
+ """
24
+
25
+ is_unique = False
26
+ is_required = False
27
+
28
+ def __call__(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
29
+ return self.check(model, column_name, data)
30
+
31
+ @abstractmethod
32
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
33
+ pass
34
+
35
+ def additional_write_columns(self, is_create=False) -> dict[str, Column]:
36
+ return {}
@@ -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
+ "MinimumValue",
29
+ "MinimumLength",
30
+ "Required",
31
+ "Timedelta",
32
+ "Unique",
33
+ ]
@@ -0,0 +1,61 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import dateparser
7
+
8
+ from clearskies import configs, decorators
9
+ from clearskies.validator import Validator
10
+
11
+ if TYPE_CHECKING:
12
+ from clearskies import Model
13
+
14
+
15
+ class AfterColumn(Validator):
16
+ """The name of the other date column for comparison."""
17
+
18
+ other_column_name = configs.String(default="", required=True)
19
+
20
+ """
21
+ If true, then this column is allowed to be eqaul to the other column.
22
+ """
23
+ allow_equal = configs.Boolean(default=False)
24
+
25
+ @decorators.parameters_to_properties
26
+ def __init__(self, other_column_name: str, allow_equal: bool = False):
27
+ self.other_column_name = other_column_name
28
+ self.allow_equal = allow_equal
29
+ self.finalize_and_validate_configuration()
30
+
31
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
32
+ # we won't check anything for missing values (columns should be required if that is an issue)
33
+ if not data.get(column_name):
34
+ return ""
35
+ my_value = data[column_name]
36
+ other_value = data.get(self.other_column_name, getattr(model, self.other_column_name))
37
+ # again, no checks for non-values
38
+ if not other_value:
39
+ return ""
40
+
41
+ my_value = dateparser.parse(my_value) if isinstance(my_value, str) else my_value
42
+ if not my_value:
43
+ return f"'{column_name}' was not a valid date."
44
+
45
+ if type(other_value) != str and type(other_value) != datetime.datetime:
46
+ return f"'{self.other_column_name}' was not a valid date."
47
+ other_value = dateparser.parse(other_value) if isinstance(other_value, str) else other_value
48
+ if not other_value:
49
+ return f"'{self.other_column_name}' was not a valid date."
50
+
51
+ return self.date_comparison(my_value, other_value, column_name)
52
+
53
+ def date_comparison(
54
+ self, incoming_date: datetime.datetime, comparison_date: datetime.datetime, column_name: str
55
+ ) -> str:
56
+ if incoming_date == comparison_date:
57
+ return "" if self.allow_equal else f"'{column_name}' must be after '{self.other_column_name}'"
58
+
59
+ if incoming_date < comparison_date:
60
+ return f"'{column_name}' must be after '{self.other_column_name}'"
61
+ return ""
@@ -0,0 +1,15 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.validators.after_column import AfterColumn
6
+
7
+
8
+ class BeforeColumn(AfterColumn):
9
+ def date_comparison(self, incoming_date: datetime.datetime, comparison_date: datetime.datetime, column_name) -> str:
10
+ if incoming_date == comparison_date:
11
+ return "" if self.allow_equal else f"'{column_name}' must be before '{self.other_column_name}'"
12
+
13
+ if incoming_date > comparison_date:
14
+ return f"'{column_name}' must be before '{self.other_column_name}'"
15
+ return ""
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import dateparser
7
+
8
+ from clearskies.di import InjectableProperties, inject
9
+ from clearskies.validator import Validator
10
+
11
+ if TYPE_CHECKING:
12
+ from clearskies import Model
13
+
14
+
15
+ class InTheFuture(Validator, InjectableProperties):
16
+ utcnow = inject.Utcnow()
17
+
18
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
19
+ if not data.get(column_name):
20
+ return ""
21
+
22
+ as_date = dateparser.parse(data[column_name]) if isinstance(data[column_name], str) else data[column_name]
23
+ if not as_date:
24
+ return f"'{column_name}' was not a valid date"
25
+ if as_date.tzinfo == None:
26
+ as_date = as_date.replace(tzinfo=datetime.timezone.utc)
27
+ if as_date <= self.utcnow:
28
+ return f"'{column_name}' must be in the future"
29
+ return ""
@@ -0,0 +1,13 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.validators.timedelta import Timedelta
6
+
7
+
8
+ class InTheFutureAtLeast(Timedelta):
9
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
10
+ if as_date < self.utcnow + self.timedelta:
11
+ human_friendly = self.delta_human_friendly()
12
+ return f"'{column_name}' must be at least {human_friendly} in the future."
13
+ return ""
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.validators.timedelta import Timedelta
6
+
7
+
8
+ class InTheFutureAtMost(Timedelta):
9
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
10
+ if as_date > self.utcnow + self.timedelta:
11
+ return f"'{column_name}' must be at most {self.delta_human_friendly()} in the future."
12
+ return ""
@@ -0,0 +1,29 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+ from typing import TYPE_CHECKING, Any
5
+
6
+ import dateparser
7
+
8
+ from clearskies.di import InjectableProperties, inject
9
+ from clearskies.validator import Validator
10
+
11
+ if TYPE_CHECKING:
12
+ from clearskies import Model
13
+
14
+
15
+ class InThePast(Validator, InjectableProperties):
16
+ utcnow = inject.Utcnow()
17
+
18
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
19
+ if not data.get(column_name):
20
+ return ""
21
+
22
+ as_date = dateparser.parse(data[column_name]) if isinstance(data[column_name], str) else data[column_name]
23
+ if not as_date:
24
+ return f"'{column_name}' was not a valid date"
25
+ if as_date.tzinfo == None:
26
+ as_date = as_date.replace(tzinfo=datetime.timezone.utc)
27
+ if as_date >= self.utcnow:
28
+ return f"'{column_name}' must be in the past"
29
+ return ""
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.validators.timedelta import Timedelta
6
+
7
+
8
+ class InThePastAtLeast(Timedelta):
9
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
10
+ if as_date > self.utcnow - self.timedelta:
11
+ return f"'{column_name}' must be at least {self.delta_human_friendly()} in the past."
12
+ return ""
@@ -0,0 +1,12 @@
1
+ from __future__ import annotations
2
+
3
+ import datetime
4
+
5
+ from clearskies.validators.timedelta import Timedelta
6
+
7
+
8
+ class InThePastAtMost(Timedelta):
9
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
10
+ if as_date < self.utcnow - self.timedelta:
11
+ return f"'{column_name}' must be at most {self.delta_human_friendly()} in the past."
12
+ return ""
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs
6
+ from clearskies.validator import Validator
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import Model
10
+
11
+
12
+ class MaximumLength(Validator):
13
+ maximum_length = configs.Integer(required=True)
14
+
15
+ def __init__(self, maximum_length: int):
16
+ self.maximum_length = maximum_length
17
+ self.finalize_and_validate_configuration()
18
+
19
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
20
+ # we won't check anything for missing values (columns should be required if that is an issue)
21
+ if not data.get(column_name):
22
+ return ""
23
+ if len(data[column_name]) <= self.maximum_length:
24
+ return ""
25
+ return f"'{column_name}' must be at most {self.maximum_length} characters long."
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs
6
+ from clearskies.validator import Validator
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import Model
10
+
11
+
12
+ class MaximumValue(Validator):
13
+ maximum_value = configs.Integer(required=True)
14
+
15
+ def __init__(self, maximum_value: int):
16
+ self.maximum_value = maximum_value
17
+ self.finalize_and_validate_configuration()
18
+
19
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
20
+ if column_name not in data:
21
+ return ""
22
+ try:
23
+ value = float(data[column_name])
24
+ except ValueError:
25
+ return f"{column_name} must be an integer or float"
26
+ if float(value) <= self.maximum_value:
27
+ return ""
28
+ return f"'{column_name}' must be at most {self.maximum_value}."
@@ -0,0 +1,25 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs
6
+ from clearskies.validator import Validator
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import Model
10
+
11
+
12
+ class MinimumLength(Validator):
13
+ minimum_length = configs.Integer(required=True)
14
+
15
+ def __init__(self, minimum_length: int):
16
+ self.minimum_length = minimum_length
17
+ self.finalize_and_validate_configuration()
18
+
19
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
20
+ # we won't check anything for missing values (columns should be required if that is an issue)
21
+ if not data.get(column_name):
22
+ return ""
23
+ if len(data[column_name]) >= self.minimum_length:
24
+ return ""
25
+ return f"'{column_name}' must be at least {self.minimum_length} characters long."
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies import configs
6
+ from clearskies.validator import Validator
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import Model
10
+
11
+
12
+ class MinimumValue(Validator):
13
+ minimum_value = configs.Integer(required=True)
14
+
15
+ def __init__(self, minimum_value: int):
16
+ self.minimum_value = minimum_value
17
+ self.finalize_and_validate_configuration()
18
+
19
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
20
+ if column_name not in data:
21
+ return ""
22
+ try:
23
+ value = float(data[column_name])
24
+ except ValueError:
25
+ return f"{column_name} must be an integer or float"
26
+ if float(value) >= self.minimum_value:
27
+ return ""
28
+ return f"'{column_name}' must be at least {self.minimum_value}."
@@ -1,8 +1,17 @@
1
- from .requirement import Requirement
1
+ from __future__ import annotations
2
2
 
3
+ from typing import TYPE_CHECKING, Any
3
4
 
4
- class Required(Requirement):
5
- def check(self, model, data):
5
+ from clearskies.validator import Validator
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies import Model
9
+
10
+
11
+ class Required(Validator):
12
+ is_required = True
13
+
14
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
6
15
  # you'd think that "required" is straight forward and we want an input error if it isn't found.
7
16
  # this isn't strictly true though. If the model already exists, the column has a value in the model already,
8
17
  # and the column is completely missing from the input data, then it is actually perfectly fine (because
@@ -10,14 +19,14 @@ class Required(Requirement):
10
19
  # we must require the column in the data with an actual value.
11
20
  has_value = False
12
21
  has_some_value = False
13
- if self.column_name in data:
22
+ if column_name in data:
14
23
  has_some_value = True
15
- if type(data[self.column_name]) == str:
16
- has_value = bool(data[self.column_name].strip())
24
+ if type(data[column_name]) == str:
25
+ has_value = bool(data[column_name].strip())
17
26
  else:
18
- has_value = bool(data[self.column_name])
27
+ has_value = bool(data[column_name])
19
28
  if has_value:
20
29
  return ""
21
- if model.exists and model[self.column_name] and not has_some_value:
30
+ if model and getattr(model, column_name) and not has_some_value:
22
31
  return ""
23
- return f"'{self.column_name}' is required."
32
+ return f"'{column_name}' is required."
@@ -0,0 +1,58 @@
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
+ from clearskies import configs, decorators
10
+ from clearskies.di import InjectableProperties, inject
11
+ from clearskies.validator import Validator
12
+
13
+ if TYPE_CHECKING:
14
+ from clearskies import Model
15
+
16
+
17
+ class Timedelta(Validator, InjectableProperties):
18
+ timedelta = configs.Timedelta(default=None)
19
+
20
+ utcnow = inject.Utcnow()
21
+
22
+ @decorators.parameters_to_properties
23
+ def __init__(self, timedelta: datetime.timedelta):
24
+ self.finalize_and_validate_configuration()
25
+
26
+ def delta_human_friendly(self):
27
+ remainder = int(self.timedelta.total_seconds())
28
+ parts = []
29
+ conversion = OrderedDict(
30
+ [
31
+ ("year", 31536000),
32
+ ("day", 86400),
33
+ ("hour", 3600),
34
+ ("minute", 60),
35
+ ("second", 1),
36
+ ]
37
+ )
38
+ for name, num_seconds in conversion.items():
39
+ if num_seconds > remainder:
40
+ continue
41
+ amount = int(remainder / num_seconds)
42
+ remainder -= amount * num_seconds
43
+ parts.append(f"{amount} {name}" + ("s" if amount != 1 else ""))
44
+ return ", ".join(parts)
45
+
46
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
47
+ if not data.get(column_name):
48
+ return ""
49
+
50
+ as_date = dateparser.parse(data[column_name]) if isinstance(data[column_name], str) else data[column_name]
51
+ if not as_date:
52
+ return f"'{column_name}' was not a valid date"
53
+ if as_date.tzinfo == None:
54
+ as_date = as_date.replace(tzinfo=datetime.timezone.utc)
55
+ return self.check_timedelta(as_date, column_name)
56
+
57
+ def check_timedelta(self, as_date: datetime.datetime, column_name: str) -> str:
58
+ return ""
@@ -0,0 +1,28 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any
4
+
5
+ from clearskies.validator import Validator
6
+
7
+ if TYPE_CHECKING:
8
+ from clearskies import Model
9
+
10
+
11
+ class Unique(Validator):
12
+ is_unique = True
13
+
14
+ def check(self, model: Model, column_name: str, data: dict[str, Any]) -> str:
15
+ # Unique is mildly tricky. We obviously want to search the backend for the new value,
16
+ # but we need to first skip this if our column is not being set, or if we're editing
17
+ # the model and nothing is changing.
18
+ if column_name not in data:
19
+ return ""
20
+ new_value = data[column_name]
21
+ if model and getattr(model, column_name) == new_value:
22
+ return ""
23
+
24
+ as_query = model.as_query()
25
+ matching_model = as_query.find(f"{column_name}={new_value}")
26
+ if matching_model:
27
+ return f"Invalid value for '{column_name}': the given value already exists, and must be unique."
28
+ return ""
@@ -1,47 +0,0 @@
1
- Metadata-Version: 2.1
2
- Name: clear-skies
3
- Version: 1.22.10
4
- Summary: A framework for building backends in the cloud
5
- Home-page: https://github.com/cmancone/clearskies
6
- License: MIT
7
- Author: Conor Mancone
8
- Author-email: cmancone@gmail.com
9
- Requires-Python: >=3.10,<4.0
10
- Classifier: Development Status :: 5 - Production/Stable
11
- Classifier: Intended Audience :: Developers
12
- Classifier: License :: OSI Approved :: MIT License
13
- Classifier: Programming Language :: Python :: 3
14
- Classifier: Programming Language :: Python :: 3.10
15
- Classifier: Programming Language :: Python :: 3.11
16
- Classifier: Topic :: Software Development :: Libraries :: Application Frameworks
17
- Provides-Extra: jose
18
- Provides-Extra: jwcrypto
19
- Provides-Extra: mysql
20
- Provides-Extra: secrets
21
- Requires-Dist: akeyless (>=4.0.0,<5.0.0) ; extra == "secrets"
22
- Requires-Dist: akeyless-cloud-id (>=0.2.3,<0.3.0) ; extra == "secrets"
23
- Requires-Dist: dateparser (>=1.1.8,<2.0.0)
24
- Requires-Dist: jose (>=1.0.0,<2.0.0) ; extra == "jose"
25
- Requires-Dist: jwcrypto (>=1.5.6,<2.0.0) ; extra == "jwcrypto"
26
- Requires-Dist: pymysql (>=1.1.0,<2.0.0) ; extra == "mysql"
27
- Requires-Dist: requests (>=2.31.0,<3.0.0)
28
- Requires-Dist: typing-extensions (>=4.12.0,<5.0.0) ; python_version >= "3.10" and python_version < "3.11"
29
- Project-URL: Repository, https://github.com/cmancone/clearskies
30
- Description-Content-Type: text/markdown
31
-
32
- # clearskies
33
-
34
- clearskies is a very opinionated Python framework intended for developing microservices in the cloud via declarative programming principles. It is mainly intended for backend services and so is designed for RESTful API endpoints, queue listeners, scheduled tasks, and the like.
35
-
36
- # Installation, Documentation, and Usage
37
-
38
- To install:
39
-
40
- ```
41
- pip3 install clear-skies
42
- ```
43
-
44
- Documentation is under construction here:
45
-
46
- [https://clearskies.info](https://clearskies.info)
47
-