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
@@ -1,271 +0,0 @@
1
- from .simple_search import SimpleSearch
2
- from .. import autodoc
3
- from .. import condition_parser
4
-
5
-
6
- class AdvancedSearch(SimpleSearch):
7
- expected_request_methods = "POST"
8
-
9
- @property
10
- def allowed_request_keys(self):
11
- return ["sort", "where", "limit"]
12
-
13
- def configure_models_from_request_data(self, models, request_data, query_parameters, pagination_data):
14
- limit = int(
15
- self._from_either(request_data, query_parameters, "limit", default=self.configuration("default_limit"))
16
- )
17
- models = models.limit(limit)
18
- if pagination_data:
19
- models = models.pagination(**pagination_data)
20
- if "sort" in request_data:
21
- primary_column = request_data["sort"][0]["column"]
22
- primary_direction = request_data["sort"][0]["direction"]
23
- secondary_column = None
24
- secondary_direction = None
25
- secondary_table = None
26
- if len(request_data["sort"]) == 2:
27
- secondary_column = request_data["sort"][1]["column"]
28
- secondary_direction = request_data["sort"][1]["direction"]
29
- # call _add_join first because it's expecting something like table.column_name, and
30
- # _resolve_references_for_query will break that up.
31
- models = self._add_join(secondary_column, models)
32
- [secondary_column, secondary_table] = self._resolve_references_for_query(secondary_column)
33
-
34
- # call _add_join first because it's expecting column_name to be something like 'table.column_name', and
35
- # _resolve_references_for_query will break up the table and column name reference.
36
- models = self._add_join(primary_column, models)
37
- [primary_column, primary_table] = self._resolve_references_for_query(primary_column)
38
- models = models.sort_by(
39
- primary_column,
40
- primary_direction,
41
- primary_table=primary_table,
42
- secondary_column=secondary_column,
43
- secondary_direction=secondary_direction,
44
- secondary_table=secondary_table,
45
- )
46
- if "where" in request_data:
47
- for where in request_data["where"]:
48
- column_name = where["column"]
49
- if column_name == "id":
50
- column_name = self.id_column_name
51
- models = self._add_join(column_name, models)
52
- [column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
53
- column = self._columns[column_name]
54
- models = column.add_search(
55
- models,
56
- where["value"],
57
- operator=where["operator"].lower() if "operator" in where else None,
58
- relationship_reference=relationship_reference,
59
- )
60
-
61
- return [models, limit]
62
-
63
- def check_request_data(self, request_data, query_parameters, pagination_data):
64
- # first, check the pagination data
65
- if pagination_data:
66
- error = self._model.validate_pagination_kwargs(pagination_data, self.auto_case_internal_column_name)
67
- if error:
68
- return error
69
- # next, check that they didn't provide something unexpected in the rest of the request
70
- allowed_request_keys = self.allowed_request_keys
71
- for key in request_data.keys():
72
- if key not in allowed_request_keys:
73
- return f"Invalid request parameter found in request body: '{key}'"
74
- # and ensure that the data we expect is not in the query parameters. This is not as strict
75
- # of a check as ensuring that *nothing* is in the query parameters, but query parameters get
76
- # used for a lot of things, so that could backfire
77
- for key in allowed_request_keys:
78
- if key in query_parameters:
79
- return f"Invalid request: key '{key}' was found in a URL parameter but should only be in the JSON body"
80
- limit = request_data.get("limit", None)
81
- if limit is not None and type(limit) != int and type(limit) != float and type(limit) != str:
82
- return "Invalid request: 'limit' should be an integer"
83
- if limit:
84
- try:
85
- limit = int(limit)
86
- except ValueError:
87
- return "Invalid request: 'limit' should be an integer"
88
- if limit and limit > self.configuration("max_limit"):
89
- return f"Invalid request: 'limit' must be at most {self.configuration('max_limit')}"
90
- allowed_sort_columns = self.configuration("sortable_columns")
91
- if not allowed_sort_columns:
92
- allowed_sort_columns = self._columns
93
- if "sort" in request_data:
94
- if type(request_data["sort"]) != list:
95
- return (
96
- "Invalid request: if provided, 'sort' must be a list of "
97
- + "objects with 'column' and 'direction' keys"
98
- )
99
- if len(request_data["sort"]) > 2:
100
- return "Invalid request: at most 2 sort directives may be specified"
101
- for index, sort in enumerate(request_data["sort"]):
102
- error_prefix = (
103
- "Invalid request: 'sort' must be a list of objects with 'column' and 'direction'"
104
- + f" keys, but entry #{index+1}"
105
- )
106
- if type(sort) != dict:
107
- return f"{error_prefix} was not an object"
108
- if "column" not in sort or "direction" not in sort or not sort["column"] or not sort["direction"]:
109
- return f"{error_prefix} did not declare both 'column' and 'direction'"
110
- if len(sort) != 2:
111
- return f"{error_prefix} had extra keys present"
112
- if sort["direction"].lower() not in ["asc", "desc"]:
113
- return (
114
- "Invalid request: sort direction must be 'asc' or 'desc'"
115
- + f" but found something else in sort entry #{index+1}"
116
- )
117
- if sort["column"] not in allowed_sort_columns:
118
- return f"Invalid request: invalid sort column specified in sort entry #{index+1}"
119
- return self.check_search_in_request_data(request_data, query_parameters)
120
-
121
- def check_search_in_request_data(self, request_data, query_parameters):
122
- if "where" in request_data:
123
- if type(request_data["where"]) != list:
124
- return "Invalid request: if provided, 'where' must be a list of objects"
125
- for index, where in enumerate(request_data["where"]):
126
- if type(where) != dict:
127
- return f"Invalid request: 'where' must be a list of objects, entry #{index+1} was not an object"
128
- if "column" not in where or not where["column"]:
129
- return f"Invalid request: 'column' missing in 'where' entry #{index+1}"
130
- column_name = where["column"]
131
- if column_name not in self.configuration("searchable_columns"):
132
- return f"Invalid request: invalid search column specified in where entry #{index+1}"
133
- [column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
134
- if column_name == "id":
135
- column_name = self.id_column_name
136
- if "value" not in where:
137
- return f"Invalid request: 'value' missing in 'where' entry #{index+1}"
138
- operator = None
139
- if "operator" in where:
140
- if type(where["operator"]) != str:
141
- return f"Invalid request: operator must be a string in 'where' entry #{index+1}"
142
- if not self._columns[column_name].is_allowed_operator(
143
- where["operator"], relationship_reference=relationship_reference
144
- ):
145
- return f"Invalid request: given operator is not allowed for column in 'where' entry #{index+1}"
146
- operator = where["operator"].lower()
147
- value_error = self._columns[column_name].check_search_value(
148
- where["value"], operator, relationship_reference=relationship_reference
149
- )
150
- if value_error:
151
- return f"Invalid request: {value_error} for 'where' entry #{index+1}"
152
- # similarly, query parameters mean search conditions
153
- for column_name, value in query_parameters.items():
154
- if column_name not in self.configuration("searchable_columns"):
155
- return f"Invalid request: invalid search column: '{column_name}'"
156
- value_error = self._columns[column_name].check_search_value(value)
157
- if value_error:
158
- return f"Invalid request: {value_error} for search column '{column_name}'"
159
-
160
- return None
161
-
162
- def map_input_to_internal_names(self, input):
163
- input = super().map_input_to_internal_names(input)
164
-
165
- # the base will take care of most of this, but it won't handle the data inside of
166
- # input['where']. Therefore, we need to handle that
167
- if "where" not in input or type(input["where"]) != list:
168
- return input
169
-
170
- mapped_wheres = []
171
-
172
- # a map between the internal column name and the external column name: easier to have before hand
173
- search_column_map = {}
174
- for internal_name in self.configuration("searchable_columns"):
175
- external_name = self.auto_case_column_name(internal_name, True)
176
- search_column_map[external_name] = internal_name
177
-
178
- # it's important that as we do this we only change things that we know we can change.
179
- # we don't want to remove things that don't belong or things that seem wrong, otherwise
180
- # the input checking won't be able to return meaningful error messages.
181
- for where in input["where"]:
182
- if type(where) != dict:
183
- mapped_wheres.append(where)
184
- continue
185
- mapped_where = {**where}
186
- for internal_name in ["column", "operator", "value"]:
187
- external_name = self.auto_case_internal_column_name(internal_name)
188
- if external_name != internal_name and external_name in mapped_where:
189
- mapped_where[internal_name] = mapped_where[external_name]
190
- del mapped_where[external_name]
191
- if "column" in mapped_where and type(mapped_where["column"]) == str:
192
- if mapped_where["column"] in search_column_map:
193
- mapped_where["column"] = search_column_map[mapped_where["column"]]
194
-
195
- mapped_wheres.append(mapped_where)
196
-
197
- input["where"] = mapped_wheres
198
- return input
199
-
200
- def documentation(self):
201
- return [
202
- self._documentation_request(
203
- "POST",
204
- [
205
- *self.documentation_request_parameters(),
206
- ],
207
- ),
208
- ]
209
-
210
- def documentation_request_parameters(self):
211
- return [
212
- *self.documentation_json_parameters(),
213
- ]
214
-
215
- def documentation_json_parameters(self):
216
- # named 'where' in the request
217
- where_condition = autodoc.schema.Object(
218
- self.auto_case_internal_column_name("condition"),
219
- [
220
- autodoc.schema.Enum(
221
- self.auto_case_internal_column_name("column"),
222
- [
223
- self.auto_case_column_name(column.name, True)
224
- for column in self._get_searchable_columns().values()
225
- ],
226
- autodoc.schema.String(self.auto_case_column_name("column_name", True)),
227
- example="name",
228
- ),
229
- autodoc.schema.Enum(
230
- self.auto_case_internal_column_name("operator"),
231
- condition_parser.ConditionParser.operators,
232
- autodoc.schema.String(self.auto_case_internal_column_name("operator")),
233
- example="=",
234
- ),
235
- autodoc.schema.String(self.auto_case_internal_column_name("value"), example="Jane"),
236
- ],
237
- )
238
-
239
- allowed_sort_columns = self.configuration("sortable_columns")
240
- if not allowed_sort_columns:
241
- allowed_sort_columns = [self.auto_case_column_name(key, True) for key in self._columns.keys()]
242
-
243
- sort_item = autodoc.schema.Object(
244
- self.auto_case_internal_column_name("sort"),
245
- [
246
- autodoc.schema.Enum(
247
- self.auto_case_internal_column_name("column"),
248
- allowed_sort_columns,
249
- autodoc.schema.String(self.auto_case_internal_column_name("column")),
250
- example=self.auto_case_internal_column_name("name"),
251
- ),
252
- autodoc.schema.Enum(
253
- self.auto_case_internal_column_name("direction"),
254
- ["asc", "desc"],
255
- autodoc.schema.String(self.auto_case_internal_column_name("direction")),
256
- example="asc",
257
- ),
258
- ],
259
- )
260
-
261
- return [
262
- autodoc.request.JSONBody(
263
- autodoc.schema.Array(self.auto_case_internal_column_name("where"), where_condition),
264
- description="List of search conditions",
265
- ),
266
- autodoc.request.JSONBody(
267
- autodoc.schema.Array(self.auto_case_internal_column_name("sort"), sort_item),
268
- description="List of sort directives (max 2)",
269
- ),
270
- *self.documentation_json_pagination_parameters(),
271
- ]