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,218 +0,0 @@
1
- import urllib.parse
2
- from .routing import Routing
3
- from .create import Create
4
- from .update import Update
5
- from .delete import Delete
6
- from .get import Get
7
- from .list import List
8
- from .advanced_search import AdvancedSearch
9
- from .. import autodoc
10
-
11
-
12
- class InvalidUrl(Exception):
13
- pass
14
-
15
-
16
- class RestfulAPI(Routing):
17
- _cached_handlers = None
18
-
19
- _configuration_defaults = {
20
- "base_url": "",
21
- "allow_create": True,
22
- "allow_delete": True,
23
- "allow_get": True,
24
- "allow_list": True,
25
- "allow_search": True,
26
- "allow_update": True,
27
- "create_handler": Create,
28
- "delete_handler": Delete,
29
- "get_handler": Get,
30
- "list_handler": List,
31
- "search_handler": AdvancedSearch,
32
- "update_handler": Update,
33
- "read_only": False,
34
- "create_request_method": "POST",
35
- "delete_request_method": "DELETE",
36
- "get_request_method": "GET",
37
- "list_request_method": "GET",
38
- "search_request_method": ["GET", "POST"],
39
- "update_request_method": "PUT",
40
- }
41
-
42
- _resource_id = None
43
- _is_search = False
44
-
45
- def __init__(self, di):
46
- super().__init__(di)
47
- self._cached_handlers = {}
48
-
49
- def handler_classes(self, configuration):
50
- classes = []
51
- for action in ["create", "delete", "get", "list", "search", "update"]:
52
- allow_key = f"allow_{action}"
53
- handler_key = f"{action}_handler"
54
- if allow_key in configuration and not configuration[allow_key]:
55
- continue
56
- classes.append(
57
- configuration[handler_key]
58
- if handler_key in configuration
59
- else self._configuration_defaults[handler_key]
60
- )
61
- return classes
62
-
63
- def configure(self, configuration):
64
- # if we have read only set then we can't allow any write methods
65
- if configuration.get("read_only"):
66
- for action in ["update", "delete", "create"]:
67
- if configuration.get(f"allow_{action}"):
68
- raise ValueError(
69
- f"Contradictory configuration for handler '{self.__class__.__name__}': "
70
- + f"'read_only' and 'allow_{action} are both set to True"
71
- )
72
- configuration[f"allow_{action}"] = False
73
-
74
- super().configure(configuration)
75
-
76
- def _finalize_configuration(self, configuration):
77
- search_request_method = configuration.get("search_request_method")
78
- if type(search_request_method) == str:
79
- configuration["search_request_method"] = [search_request_method]
80
- configuration["search_request_method"] = [method.upper() for method in configuration["search_request_method"]]
81
-
82
- request_method_keys = [
83
- "create_request_method",
84
- "delete_request_method",
85
- "get_request_method",
86
- "list_request_method",
87
- "update_request_method",
88
- ]
89
- for key in request_method_keys:
90
- configuration[key] = configuration[key].upper()
91
-
92
- return super()._finalize_configuration(configuration)
93
-
94
- def handle(self, input_output):
95
- [resource_id, handler_class] = self._get_handler_class_for_route(input_output)
96
- if handler_class is None:
97
- return self.error(input_output, "Not Found", 404)
98
- handler = self.fetch_cached_handler(handler_class)
99
- if resource_id is not None:
100
- input_output.add_routing_data({"id": resource_id})
101
- return handler(input_output)
102
-
103
- def cors(self, input_output):
104
- cors = self._cors_header
105
- if not cors:
106
- return self.error(input_output, "not found", 404)
107
- authentication = self._configuration.get("authentication")
108
- if authentication:
109
- authentication.set_headers_for_cors(cors)
110
- methods = {}
111
- for action in ["create", "delete", "list", "search", "update"]:
112
- if self.configuration(f"allow_{action}"):
113
- route_methods = self.configuration(f"{action}_request_method")
114
- if type(route_methods) != list:
115
- route_methods = [route_methods]
116
- for route_method in route_methods:
117
- methods[route_method] = True
118
- for method in methods.keys():
119
- cors.add_method(method)
120
- cors.set_headers_for_input_output(input_output)
121
- return input_output.respond("", 200)
122
-
123
- def fetch_cached_handler(self, handler_class):
124
- cache_key = handler_class.__name__
125
- if cache_key not in self._cached_handlers:
126
- self._cached_handlers[cache_key] = self.build_handler(handler_class)
127
- return self._cached_handlers[cache_key]
128
-
129
- def build_handler(self, handler_class, configuration=None):
130
- if not configuration and handler_class == self.configuration("update_handler"):
131
- configuration = self._configuration
132
- configuration["include_id_in_path"] = True
133
- return super().build_handler(handler_class, configuration=configuration)
134
-
135
- def _get_handler_class_for_route(self, input_output):
136
- try:
137
- [is_search, resource_id] = self._parse_url(input_output)
138
- except InvalidUrl:
139
- return [None, None]
140
- request_method = input_output.get_request_method().upper()
141
- if is_search:
142
- if request_method not in self.configuration("search_request_method"):
143
- return [None, None]
144
- return [resource_id, self.configuration("search_handler") if self.configuration("allow_search") else None]
145
- if resource_id:
146
- if request_method == self.configuration("update_request_method"):
147
- return [
148
- resource_id,
149
- self.configuration("update_handler") if self.configuration("allow_update") else None,
150
- ]
151
- elif request_method == self.configuration("delete_request_method"):
152
- return [
153
- resource_id,
154
- self.configuration("delete_handler") if self.configuration("allow_delete") else None,
155
- ]
156
- if request_method != self.configuration("get_request_method"):
157
- return [None, None]
158
- return [resource_id, self.configuration("get_handler") if self.configuration("allow_get") else None]
159
- if request_method == self.configuration("create_request_method"):
160
- return [resource_id, self.configuration("create_handler") if self.configuration("allow_create") else None]
161
- if request_method == self.configuration("list_request_method"):
162
- return [resource_id, self.configuration("list_handler") if self.configuration("allow_list") else None]
163
- return [None, None]
164
-
165
- def _parse_url(self, input_output):
166
- resource_id = None
167
- is_search = False
168
- full_path = input_output.get_full_path().strip("/")
169
- base_url = self.configuration("base_url").strip("/")
170
- for key, value in input_output.routing_data().items():
171
- base_url = base_url.replace("{" + key + "}", value)
172
- if base_url and full_path[: len(base_url)] != base_url:
173
- raise InvalidUrl()
174
- url = full_path[len(base_url) :].strip("/")
175
- if url:
176
- if url == "search" and self.configuration("allow_search"):
177
- is_search = True
178
- else:
179
- resource_id = urllib.parse.unquote(url)
180
- return [is_search, resource_id]
181
-
182
- def documentation(self):
183
- docs = []
184
- for name in ["list", "get", "search", "create", "update", "delete"]:
185
- if not self.configuration(f"allow_{name}"):
186
- continue
187
- handler = self.build_handler(self.configuration(f"{name}_handler"))
188
- action_docs = handler.documentation()
189
- for doc in action_docs:
190
- request_methods = self.configuration(f"{name}_request_method")
191
-
192
- # for search, filter down the docs to only the allowed request methods.
193
- # for the rest, set the request method to match the allowed one.
194
- # this helps with the fact that the searching has different input methods depending
195
- # on the request method, and helps with the fact that search has different rules
196
- # for different methods.
197
- if name == "search":
198
- if not set(request_methods).intersection(set(doc.request_methods)):
199
- continue
200
- else:
201
- doc.set_request_methods(request_methods if type(request_methods) == list else [request_methods])
202
-
203
- if name == "search":
204
- doc.append_relative_path("search")
205
-
206
- docs.append(doc)
207
-
208
- return docs
209
-
210
- def documentation_models(self):
211
- # read and write use the same model, so we just need one
212
- read_handler = self.build_handler(self.configuration("get_handler"))
213
- return read_handler.documentation_models()
214
-
215
- def documentation_security_schemes(self):
216
- # read and write use the same model, so we just need one
217
- read_handler = self.build_handler(self.configuration("get_handler"))
218
- return read_handler.documentation_security_schemes()
@@ -1,62 +0,0 @@
1
- from .base import Base
2
- from abc import abstractmethod
3
-
4
-
5
- class Routing(Base):
6
- def __init__(self, di):
7
- super().__init__(di)
8
-
9
- @abstractmethod
10
- def handler_classes(self, configuration):
11
- pass
12
-
13
- @abstractmethod
14
- def handle(self, input_output):
15
- pass
16
-
17
- def build_handler(self, handler_class, configuration=None):
18
- if configuration is None:
19
- configuration = self._configuration
20
- handler = self._di.build(handler_class, cache=False)
21
- handler_configuration = {}
22
- for key in handler._configuration_defaults.keys():
23
- if key in configuration:
24
- handler_configuration[key] = configuration[key]
25
- for key in handler._global_configuration_defaults.keys():
26
- if key in configuration:
27
- handler_configuration[key] = configuration[key]
28
- handler.configure(self._finalize_configuration_for_sub_handler(handler_configuration, handler_class))
29
- return handler
30
-
31
- def _finalize_configuration_for_sub_handler(self, configuration, handler_class):
32
- return configuration
33
-
34
- def configure(self, configuration):
35
- # we need to completely clobber the base configuration process because it expects to have
36
- # the list of all allowed configurations. We don't know what that list is - rather, we
37
- # just need to fulfill the requirements of the handlers we'll be routing to.
38
- # We also want to make it possible for handlers that extend this to still define their
39
- # own possible configuration values. Therefore, we'll loop over all of the handlers
40
- # which we might route to, make them, have them check the configs, and let them throw exceptions
41
- # as needed. Finally we'll figure out what configs may not have been "used" by a child handler
42
- # and see if those are in our own configuration - if not, we'll throw an "Unknown config" exception
43
-
44
- # First, let's check the configuration for the handlers, which is just a matter of building
45
- # the handlers (they willl automatically throw exceptions for invalid configurations as part
46
- # of this process)
47
- used_configs = list(self._global_configuration_defaults.keys())
48
- used_configs.extend(self._configuration_defaults.keys())
49
- for handler_class in self.handler_classes(configuration):
50
- handler = self.build_handler(handler_class, configuration=configuration)
51
- used_configs.extend(handler._configuration_defaults.keys())
52
-
53
- for key in configuration.keys():
54
- if key not in used_configs and key not in self._global_configuration_defaults:
55
- class_name = self.__class__.__name__
56
- raise KeyError(f"Attempt to set unknown configuration setting '{key}' for handler '{class_name}'")
57
-
58
- self._check_configuration(configuration)
59
- self._configuration = self._finalize_configuration(self.apply_default_configuration(configuration))
60
-
61
- def _check_configuration(self, configuration):
62
- super()._check_configuration(configuration)
@@ -1,128 +0,0 @@
1
- from collections import OrderedDict
2
- from abc import ABC
3
- from ..functional import validations
4
-
5
-
6
- class FakeModel:
7
- def __getattr__(self, key):
8
- return None
9
-
10
-
11
- class SchemaHelper(ABC):
12
- """
13
- A helper for handlers that want to accept arbitrary schemas as configuration inputs.
14
-
15
- To use this, the schema should be stored in a configuration called `schema`. You can then check
16
- some data against the schema by doing something like this:
17
-
18
- ```
19
- input_errors = {
20
- **self._extra_column_errors(request_data),
21
- **self._find_input_errors(request_data),
22
- }
23
- ```
24
- """
25
-
26
- def _check_schema(self, schema, writeable_columns, error_prefix):
27
- """
28
- Validates that the schema provided in the configuration is valid.
29
-
30
- The schema is allowed to be one of 3 things:
31
-
32
- 1. A list of column definitions.
33
- 2. A model class.
34
- 3. A model.
35
-
36
- An example of option #1 would be:
37
-
38
- ```
39
- {
40
- 'schema': [
41
- clearskies.column_types.string('name', input_requirements=[clearskies.input_requirements.required()]),
42
- clearskies.column_types.integer('age'),
43
- ],
44
- }
45
- ```
46
- """
47
- is_valid_schema = False
48
- if validations.is_model_or_class(schema):
49
- is_valid_schema = True
50
- else:
51
- if not hasattr(schema, "__iter__") or type(schema) == str:
52
- raise ValueError(
53
- f"{error_prefix} 'schema' should be a list of column definitions, but was instead a " + type(schema)
54
- )
55
- for column in schema:
56
- if type(column) != tuple:
57
- raise ValueError(
58
- f"{error_prefix} 'schema' should be a list of column definitions, but one of the entries was not a column definition"
59
- )
60
- is_valid_schema = True
61
- if not is_valid_schema:
62
- raise ValueError(
63
- f"{error_prefix} 'schema' should be a model, model class, or list of column definitions, but was instead a "
64
- + type(schema)
65
- )
66
-
67
- if not writeable_columns and writeable_columns is not None:
68
- raise ValueError(
69
- f"{error_prefix} 'writeable_columns' can't be an empty list. It can be 'None', but otherwise I don't know how to handle empty values"
70
- )
71
-
72
- if writeable_columns:
73
- if not hasattr(writeable_columns, "__iter__") or type(writeable_columns) == str:
74
- raise ValueError(
75
- f"{error_prefix} 'writeable_columns' should be a list of column names, but was instead a "
76
- + type(writeable_columns)
77
- )
78
- columns = self._schema_to_columns(schema)
79
- for column in writeable_columns:
80
- if type(column) != str:
81
- raise ValueError(
82
- f"{error_prefix} 'writeable_columns' should be a list of column names, but one of the entries was not a string"
83
- )
84
- if column not in columns:
85
- raise ValueError(
86
- f"{error_prefix} 'writeable_columns' references a column named '{column}' but this column does not exist in the schema"
87
- )
88
-
89
- def _schema_to_columns(self, schema, columns_to_keep=None):
90
- """
91
- Converts the schema from the developer to a columns object
92
- """
93
- # the schema can be a model, model class, or list of column configs.
94
- # Each requires a different conversion method
95
- if validations.is_model(schema):
96
- columns = schema.columns()
97
- elif validations.is_model_class(schema):
98
- columns = self._di.build(schema).columns()
99
- else:
100
- columns = self._di.build("columns").configure(OrderedDict(schema), self.__class__)
101
-
102
- # if we don't have a list of columns to keep, then we're done
103
- if not columns_to_keep:
104
- return columns
105
-
106
- # only keep things that we're allowed to keep
107
- return OrderedDict([(key, value) for (key, value) in columns.items() if key in columns_to_keep])
108
-
109
- def _find_input_errors(self, input_data, schema=None):
110
- if not schema:
111
- schema = self.configuration("schema")
112
- input_errors = {}
113
- fake_model = FakeModel()
114
- for column in schema.values():
115
- input_errors = {
116
- **input_errors,
117
- **column.input_errors(fake_model, input_data),
118
- }
119
- return input_errors
120
-
121
- def _extra_column_errors(self, input_data, schema=None):
122
- if not schema:
123
- schema = self.configuration("schema")
124
- input_errors = {}
125
- for column_name in input_data.keys():
126
- if column_name not in schema:
127
- input_errors[column_name] = f"Input column '{column_name}' is not an allowed column"
128
- return input_errors
@@ -1,206 +0,0 @@
1
- from .base import Base
2
- from abc import abstractmethod
3
- from .simple_routing_route import SimpleRoutingRoute
4
- from . import callable as callable_handler
5
- from ..functional import string
6
- from .. import autodoc
7
-
8
-
9
- class SimpleRouting(Base):
10
- _routes = None
11
-
12
- _configuration_defaults = {
13
- "base_url": "",
14
- "authentication": None,
15
- "routes": [],
16
- "schema_route": "",
17
- "schema_configuration": {},
18
- "schema_format": autodoc.formats.oai3_json.OAI3JSON,
19
- "schema_authentication": None,
20
- }
21
-
22
- def __init__(self, di):
23
- super().__init__(di)
24
-
25
- def top_level_authentication_and_authorization(self, input_output, authentication=None):
26
- # Check for separate authentication on the schema route
27
- schema_authentication = self.configuration("schema_authentication")
28
- if schema_authentication:
29
- request_method = input_output.get_request_method()
30
- full_path = input_output.get_full_path().strip("/")
31
- if self.configuration("schema_route") and self.configuration("schema_route") == full_path:
32
- return super().top_level_authentication_and_authorization(input_output, schema_authentication)
33
- return super().top_level_authentication_and_authorization(input_output)
34
-
35
- def can_handle(self, full_path, request_method, is_cors=False):
36
- for route in self._routes:
37
- route_data = route.matches(full_path, request_method, is_cors=is_cors)
38
- if route_data is not None:
39
- return route_data
40
- return None
41
-
42
- def handle(self, input_output):
43
- request_method = input_output.get_request_method()
44
- full_path = input_output.get_full_path().strip("/")
45
- if self.configuration("schema_route") and self.configuration("schema_route") == full_path:
46
- return self.hosted_schema(input_output)
47
-
48
- if request_method == "OPTIONS":
49
- return self.cors(input_output)
50
-
51
- for route in self._routes:
52
- route_data = route.matches(full_path, request_method)
53
- if route_data is None:
54
- continue
55
- input_output.add_routing_data(route_data)
56
-
57
- return route(input_output)
58
-
59
- return self.error(input_output, "Page not found", 404)
60
-
61
- def cors(self, input_output):
62
- if not self._cors_header:
63
- return self.error(input_output, "not found", 404)
64
- request_method = input_output.get_request_method()
65
- full_path = input_output.get_full_path().strip("/")
66
- for route in self._routes:
67
- route_data = route.matches(full_path, request_method, is_cors=True)
68
- if route_data is None:
69
- continue
70
-
71
- return route.cors(input_output)
72
- return self.error(input_output, "Page not found", 404)
73
-
74
- def _check_configuration(self, configuration):
75
- super()._check_configuration(configuration)
76
-
77
- if not configuration.get("routes"):
78
- raise ValueError(f"'routes' must be a list of routes for the {self.__class__.__name__} handler")
79
- if not hasattr(configuration["routes"], "__iter__"):
80
- raise ValueError(
81
- f"'routes' must be a list of routes for the {self.__class__.__name__} handler, "
82
- + "but a non-iterable was provided instead"
83
- )
84
- if isinstance(configuration["routes"], str):
85
- raise ValueError(
86
- f"'routes' must be a list of routes for the {self.__class__.__name__} handler, "
87
- + "but a string was provided instead"
88
- )
89
-
90
- # we're actually going to build our routes, which will implicitly check the configuration too
91
- base_url = configuration.get("base_url")
92
- self._build_routes(
93
- configuration["routes"],
94
- base_url if base_url else "/",
95
- authentication=configuration.get("authentication"),
96
- response_headers=configuration.get("response_headers"),
97
- security_headers=configuration.get("security_headers"),
98
- )
99
-
100
- def _finalize_configuration(self, configuration):
101
- configuration = super()._finalize_configuration(configuration)
102
- if configuration.get("schema_route"):
103
- base_url = configuration.get("base_url")
104
- configuration["schema_route"] = (
105
- base_url.strip("/") + "/" + configuration["schema_route"].strip("/")
106
- ).strip("/")
107
- if configuration.get("schema_authentication") is not None:
108
- configuration["schema_authentication"] = self._di.build(configuration["schema_authentication"])
109
- return configuration
110
-
111
- def _build_routes(self, routes, base_url, authentication=None, response_headers=None, security_headers=None):
112
- self._routes = []
113
- if base_url is None:
114
- base_url = ""
115
- for i, route_config in enumerate(routes):
116
- # in general the route should be a dictionary with the route configuration,
117
- # but there are two exceptions. The first is a "plain" callable. In that case,
118
- # wrap it in a callable handler and define the path from the name
119
- if type(route_config) != dict:
120
- if callable(route_config):
121
- route_config = {
122
- "path": route_config.__name__,
123
- "handler_class": callable_handler.Callable,
124
- "handler_config": {"callable": route_config},
125
- }
126
- # the other option is an application with another simple routing handler, in which
127
- # case just skip the path (which is optional anyway)
128
- elif (
129
- hasattr(route_config, "handler_class")
130
- and hasattr(route_config, "handler_config")
131
- and issubclass(route_config.handler_class, SimpleRouting)
132
- ):
133
- route_config = {
134
- "path": "",
135
- "application": route_config,
136
- }
137
- if type(route_config) != dict:
138
- raise ValueError(
139
- f"Routing config expected a dictionary with route information but found something else for route #{i+1}"
140
- )
141
- path = route_config.get("path")
142
- if path is None:
143
- path = ""
144
- if route_config.get("application"):
145
- application = route_config.get("application")
146
- if not hasattr(application, "handler_config") or not hasattr(application, "handler_class"):
147
- raise ValueError(f"A non application was passed in the 'application' key of route #{i+1}")
148
- route_config["handler_class"] = application.handler_class
149
- route_config["handler_config"] = application.handler_config
150
- if not route_config.get("handler_class"):
151
- raise ValueError(
152
- "Each route must specify a handler class via 'handler_class' key, "
153
- + f"but 'handler_class' was missing for route #{i+1}"
154
- )
155
- if route_config.get("handler_config") is None:
156
- raise ValueError(
157
- "Each route must specify the handler configuration via 'handler_config' key, "
158
- + f"but 'handler_config' was missing for route #{i+1}"
159
- )
160
- if route_config.get("authentication"):
161
- authentication = self._di.build(route_config.get("authentication"), cache=True)
162
- route = SimpleRoutingRoute(self._di)
163
- route.configure(
164
- route_config["handler_class"],
165
- route_config["handler_config"],
166
- path=base_url.rstrip("/") + "/" + path.lstrip("/"),
167
- methods=route_config.get("methods"),
168
- path_parameter_with_slashes=route_config.get("path_parameter_with_slashes"),
169
- authentication=authentication,
170
- response_headers=response_headers,
171
- security_headers=security_headers,
172
- has_sub_paths=route_config.get("has_sub_paths", True),
173
- )
174
- self._routes.append(route)
175
-
176
- def documentation(self):
177
- docs = []
178
- for route in self._routes:
179
- docs.extend(route.documentation())
180
- return docs
181
-
182
- def documentation_security_schemes(self):
183
- schemes = {}
184
- for route in self._routes:
185
- schemes = {**schemes, **route.documentation_security_schemes()}
186
- return schemes
187
-
188
- def documentation_models(self):
189
- models = {}
190
- for route in self._routes:
191
- models = {**models, **route.documentation_models()}
192
- return models
193
-
194
- def hosted_schema(self, input_output):
195
- schema = self._di.build(self.configuration("schema_format"))
196
- schema.set_requests(self.documentation())
197
- schema.set_components(self.documentation_components())
198
- extra_schema_config = self.configuration("schema_configuration")
199
- if "info" not in extra_schema_config:
200
- extra_schema_config["info"] = {"title": "Auto generated by clearskies", "version": "1.0"}
201
- response_headers = self.configuration("response_headers")
202
- if response_headers:
203
- input_output.set_headers(response_headers)
204
- for security_header in self.configuration("security_headers"):
205
- security_header.set_headers_for_input_output(input_output)
206
- return input_output.respond(schema.pretty(root_properties=extra_schema_config), 200)