clear-skies 1.19.22__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 (362) 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.19.22.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 +9 -38
  7. clearskies/authentication/authentication.py +44 -0
  8. clearskies/authentication/authorization.py +14 -8
  9. clearskies/authentication/authorization_pass_through.py +22 -0
  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 +56 -17
  41. clearskies/backends/api_backend.py +1128 -166
  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 +117 -3
  154. clearskies/di/additional_config_auto_import.py +12 -0
  155. clearskies/di/di.py +717 -126
  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 -152
  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 +1894 -199
  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.19.22.dist-info/METADATA +0 -46
  243. clear_skies-1.19.22.dist-info/RECORD +0 -206
  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 -39
  248. clearskies/backends/example_backend.py +0 -43
  249. clearskies/backends/file_backend.py +0 -48
  250. clearskies/backends/json_backend.py +0 -7
  251. clearskies/backends/restful_api_advanced_search_backend.py +0 -138
  252. clearskies/binding_config.py +0 -16
  253. clearskies/column_types/__init__.py +0 -184
  254. clearskies/column_types/audit.py +0 -235
  255. clearskies/column_types/belongs_to.py +0 -250
  256. clearskies/column_types/boolean.py +0 -60
  257. clearskies/column_types/category_tree.py +0 -226
  258. clearskies/column_types/column.py +0 -373
  259. clearskies/column_types/created.py +0 -26
  260. clearskies/column_types/created_by_authorization_data.py +0 -26
  261. clearskies/column_types/created_by_header.py +0 -24
  262. clearskies/column_types/created_by_ip.py +0 -17
  263. clearskies/column_types/created_by_routing_data.py +0 -25
  264. clearskies/column_types/created_by_user_agent.py +0 -17
  265. clearskies/column_types/created_micro.py +0 -26
  266. clearskies/column_types/datetime.py +0 -108
  267. clearskies/column_types/datetime_micro.py +0 -12
  268. clearskies/column_types/email.py +0 -18
  269. clearskies/column_types/float.py +0 -43
  270. clearskies/column_types/has_many.py +0 -139
  271. clearskies/column_types/integer.py +0 -41
  272. clearskies/column_types/json.py +0 -25
  273. clearskies/column_types/many_to_many.py +0 -278
  274. clearskies/column_types/many_to_many_with_data.py +0 -162
  275. clearskies/column_types/select.py +0 -11
  276. clearskies/column_types/string.py +0 -24
  277. clearskies/column_types/updated.py +0 -24
  278. clearskies/column_types/updated_micro.py +0 -24
  279. clearskies/column_types/uuid.py +0 -25
  280. clearskies/columns.py +0 -123
  281. clearskies/condition_parser.py +0 -172
  282. clearskies/contexts/build_context.py +0 -54
  283. clearskies/contexts/convert_to_application.py +0 -190
  284. clearskies/contexts/extract_handler.py +0 -37
  285. clearskies/contexts/test.py +0 -94
  286. clearskies/decorators/__init__.py +0 -39
  287. clearskies/decorators/auth0_jwks.py +0 -22
  288. clearskies/decorators/authorization.py +0 -10
  289. clearskies/decorators/binding_classes.py +0 -9
  290. clearskies/decorators/binding_modules.py +0 -9
  291. clearskies/decorators/bindings.py +0 -9
  292. clearskies/decorators/create.py +0 -10
  293. clearskies/decorators/delete.py +0 -10
  294. clearskies/decorators/docs.py +0 -14
  295. clearskies/decorators/get.py +0 -10
  296. clearskies/decorators/jwks.py +0 -26
  297. clearskies/decorators/merge.py +0 -124
  298. clearskies/decorators/patch.py +0 -10
  299. clearskies/decorators/post.py +0 -10
  300. clearskies/decorators/public.py +0 -11
  301. clearskies/decorators/response_headers.py +0 -10
  302. clearskies/decorators/return_raw_response.py +0 -9
  303. clearskies/decorators/schema.py +0 -10
  304. clearskies/decorators/secret_bearer.py +0 -24
  305. clearskies/decorators/security_headers.py +0 -10
  306. clearskies/di/standard_dependencies.py +0 -140
  307. clearskies/di/test_module/__init__.py +0 -6
  308. clearskies/di/test_module/another_module/__init__.py +0 -2
  309. clearskies/di/test_module/module_class.py +0 -5
  310. clearskies/handlers/__init__.py +0 -41
  311. clearskies/handlers/advanced_search.py +0 -271
  312. clearskies/handlers/base.py +0 -473
  313. clearskies/handlers/callable.py +0 -189
  314. clearskies/handlers/create.py +0 -35
  315. clearskies/handlers/crud_by_method.py +0 -18
  316. clearskies/handlers/database_connector.py +0 -32
  317. clearskies/handlers/delete.py +0 -61
  318. clearskies/handlers/exceptions/__init__.py +0 -5
  319. clearskies/handlers/exceptions/not_found.py +0 -3
  320. clearskies/handlers/get.py +0 -156
  321. clearskies/handlers/health_check.py +0 -59
  322. clearskies/handlers/input_processing.py +0 -79
  323. clearskies/handlers/list.py +0 -530
  324. clearskies/handlers/mygrations.py +0 -82
  325. clearskies/handlers/request_method_routing.py +0 -47
  326. clearskies/handlers/restful_api.py +0 -218
  327. clearskies/handlers/routing.py +0 -62
  328. clearskies/handlers/schema_helper.py +0 -128
  329. clearskies/handlers/simple_routing.py +0 -204
  330. clearskies/handlers/simple_routing_route.py +0 -192
  331. clearskies/handlers/simple_search.py +0 -136
  332. clearskies/handlers/update.py +0 -96
  333. clearskies/handlers/write.py +0 -193
  334. clearskies/input_requirements/__init__.py +0 -68
  335. clearskies/input_requirements/after.py +0 -36
  336. clearskies/input_requirements/before.py +0 -36
  337. clearskies/input_requirements/in_the_future_at_least.py +0 -19
  338. clearskies/input_requirements/in_the_future_at_most.py +0 -19
  339. clearskies/input_requirements/in_the_past_at_least.py +0 -19
  340. clearskies/input_requirements/in_the_past_at_most.py +0 -19
  341. clearskies/input_requirements/maximum_length.py +0 -19
  342. clearskies/input_requirements/minimum_length.py +0 -22
  343. clearskies/input_requirements/requirement.py +0 -25
  344. clearskies/input_requirements/time_delta.py +0 -38
  345. clearskies/input_requirements/unique.py +0 -18
  346. clearskies/mocks/__init__.py +0 -7
  347. clearskies/mocks/input_output.py +0 -124
  348. clearskies/mocks/models.py +0 -142
  349. clearskies/models.py +0 -345
  350. clearskies/security_headers/base.py +0 -12
  351. clearskies/tests/simple_api/models/__init__.py +0 -2
  352. clearskies/tests/simple_api/models/status.py +0 -23
  353. clearskies/tests/simple_api/models/user.py +0 -21
  354. clearskies/tests/simple_api/users_api.py +0 -64
  355. {clear_skies-1.19.22.dist-info → clear_skies-2.0.23.dist-info/licenses}/LICENSE +0 -0
  356. /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
  357. /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
  358. /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
  359. /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
  360. /clearskies/{secrets/exceptions → exceptions}/not_found.py +0 -0
  361. /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
  362. /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
@@ -0,0 +1,333 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Callable, Self
4
+
5
+ from clearskies import configs, configurable, decorators, di, end
6
+ from clearskies.authentication import Authentication, Authorization, Public
7
+ from clearskies.endpoint import Endpoint
8
+ from clearskies.functional import routing
9
+ from clearskies.input_outputs import InputOutput
10
+
11
+ if TYPE_CHECKING:
12
+ from clearskies import SecurityHeader
13
+
14
+
15
+ class EndpointGroup(
16
+ end.End, # type: ignore
17
+ configurable.Configurable,
18
+ di.InjectableProperties,
19
+ ):
20
+ """
21
+ An endpoint group brings endpoints together: it basically handles routing.
22
+
23
+ The endpoint group accepts a list of endpoints/endpoint groups and routes requests to them. You can set a URL for
24
+ the endpoint group, and this becomes a URL prefix for all of the endpoints under it. Note that all routing is
25
+ greedy, which means you want to put endpoints with more specific URLs first. Here's an example of how
26
+ you can use them to build a fully functional API that manages both users and companies. Each individual
27
+ endpoint is defined for the purpose of the example, but note that in practice you could accomplish this same
28
+ thing with much less code by using the RestfulApi endpoint:
29
+
30
+ ```python
31
+ import clearskies
32
+ from clearskies.validators import Required, Unique
33
+ from clearskies import columns
34
+
35
+
36
+ class Company(clearskies.Model):
37
+ id_column_name = "id"
38
+ backend = clearskies.backends.MemoryBackend()
39
+
40
+ id = columns.Uuid()
41
+ name = columns.String(
42
+ validators=[
43
+ Required(),
44
+ Unique(),
45
+ ]
46
+ )
47
+
48
+
49
+ class User(clearskies.Model):
50
+ id_column_name = "id"
51
+ backend = clearskies.backends.MemoryBackend()
52
+
53
+ id = columns.Uuid()
54
+ name = columns.String(validators=[Required()])
55
+ username = columns.String(
56
+ validators=[
57
+ Required(),
58
+ Unique(),
59
+ ]
60
+ )
61
+ age = columns.Integer(validators=[Required()])
62
+ created_at = columns.Created()
63
+ updated_at = columns.Updated()
64
+ company_id = columns.BelongsToId(
65
+ Company,
66
+ readable_parent_columns=["id", "name"],
67
+ validators=[Required()],
68
+ )
69
+ company = columns.BelongsToModel("company_id")
70
+
71
+
72
+ readable_user_column_names = [
73
+ "id",
74
+ "name",
75
+ "username",
76
+ "age",
77
+ "created_at",
78
+ "updated_at",
79
+ "company",
80
+ ]
81
+ writeable_user_column_names = ["name", "username", "age", "company_id"]
82
+ users_api = clearskies.EndpointGroup(
83
+ [
84
+ clearskies.endpoints.Update(
85
+ model_class=User,
86
+ url="/:id",
87
+ readable_column_names=readable_user_column_names,
88
+ writeable_column_names=writeable_user_column_names,
89
+ ),
90
+ clearskies.endpoints.Delete(
91
+ model_class=User,
92
+ url="/:id",
93
+ ),
94
+ clearskies.endpoints.Get(
95
+ model_class=User,
96
+ url="/:id",
97
+ readable_column_names=readable_user_column_names,
98
+ ),
99
+ clearskies.endpoints.Create(
100
+ model_class=User,
101
+ readable_column_names=readable_user_column_names,
102
+ writeable_column_names=writeable_user_column_names,
103
+ ),
104
+ clearskies.endpoints.SimpleSearch(
105
+ model_class=User,
106
+ readable_column_names=readable_user_column_names,
107
+ sortable_column_names=readable_user_column_names,
108
+ searchable_column_names=readable_user_column_names,
109
+ default_sort_column_name="name",
110
+ ),
111
+ ],
112
+ url="users",
113
+ )
114
+
115
+ readable_company_column_names = ["id", "name"]
116
+ writeable_company_column_names = ["name"]
117
+ companies_api = clearskies.EndpointGroup(
118
+ [
119
+ clearskies.endpoints.Update(
120
+ model_class=Company,
121
+ url="/:id",
122
+ readable_column_names=readable_company_column_names,
123
+ writeable_column_names=writeable_company_column_names,
124
+ ),
125
+ clearskies.endpoints.Delete(
126
+ model_class=Company,
127
+ url="/:id",
128
+ ),
129
+ clearskies.endpoints.Get(
130
+ model_class=Company,
131
+ url="/:id",
132
+ readable_column_names=readable_company_column_names,
133
+ ),
134
+ clearskies.endpoints.Create(
135
+ model_class=Company,
136
+ readable_column_names=readable_company_column_names,
137
+ writeable_column_names=writeable_company_column_names,
138
+ ),
139
+ clearskies.endpoints.SimpleSearch(
140
+ model_class=Company,
141
+ readable_column_names=readable_company_column_names,
142
+ sortable_column_names=readable_company_column_names,
143
+ searchable_column_names=readable_company_column_names,
144
+ default_sort_column_name="name",
145
+ ),
146
+ ],
147
+ url="companies",
148
+ )
149
+
150
+ wsgi = clearskies.contexts.WsgiRef(clearskies.EndpointGroup([users_api, companies_api]))
151
+ wsgi()
152
+ ```
153
+
154
+ Usage then works exactly as expected:
155
+
156
+ ```bash
157
+ $ curl 'http://localhost:8080/companies' -d '{"name": "Box Store"}' | jq
158
+ {
159
+ "status": "success",
160
+ "error": "",
161
+ "data": {
162
+ "id": "f073ee4d-318d-4e0b-a796-f450c40aa771",
163
+ "name": "Box Store"
164
+ },
165
+ "pagination": {},
166
+ "input_errors": {}
167
+ }
168
+
169
+ curl 'http://localhost:8080/users' -d '{"name": "Bob Brown", "username": "bobbrown", "age": 25, "company_id": "f073ee4d-318d-4e0b-a796-f450c40aa771"}'
170
+ curl 'http://localhost:8080/users' -d '{"name": "Jane Doe", "username": "janedoe", "age": 32, "company_id": "f073ee4d-318d-4e0b-a796-f450c40aa771"}'
171
+
172
+ $ curl 'http://localhost:8080/users' | jq
173
+ {
174
+ "status": "success",
175
+ "error": "",
176
+ "data": [
177
+ {
178
+ "id": "68cbb9e9-689a-4ae0-af77-d60e4cb344f1",
179
+ "name": "Bob Brown",
180
+ "username": "bobbrown",
181
+ "age": 25,
182
+ "created_at": "2025-06-08T10:40:37+00:00",
183
+ "updated_at": "2025-06-08T10:40:37+00:00",
184
+ "company": {
185
+ "id": "f073ee4d-318d-4e0b-a796-f450c40aa771",
186
+ "name": "Box Store"
187
+ }
188
+ },
189
+ {
190
+ "id": "e69c4ebf-38b1-40d2-b523-5d58f5befc7b",
191
+ "name": "Jane Doe",
192
+ "username": "janedoe",
193
+ "age": 32,
194
+ "created_at": "2025-06-08T10:41:04+00:00",
195
+ "updated_at": "2025-06-08T10:41:04+00:00",
196
+ "company": {
197
+ "id": "f073ee4d-318d-4e0b-a796-f450c40aa771",
198
+ "name": "Box Store"
199
+ }
200
+ }
201
+ ],
202
+ "pagination": {
203
+ "number_results": 2,
204
+ "limit": 50,
205
+ "next_page": {}
206
+ },
207
+ "input_errors": {}
208
+ }
209
+
210
+ ```
211
+ """
212
+
213
+ """
214
+ The dependency injection container
215
+ """
216
+ di = di.inject.Di()
217
+
218
+ """
219
+ The base URL for the endpoint group.
220
+
221
+ This URL is added as a prefix to all endpoints attached to the group. This includes any named URL parameters:
222
+ """
223
+ url = configs.String(default="")
224
+
225
+ """
226
+ The list of endpoints connected to this endpoint group
227
+ """
228
+ endpoints = configs.EndpointList()
229
+
230
+ internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
231
+ external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
232
+ response_headers = configs.StringListOrCallable(default=[])
233
+ authentication = configs.Authentication(default=Public())
234
+ authorization = configs.Authorization(default=Authorization())
235
+ security_headers = configs.SecurityHeaders(default=[])
236
+
237
+ cors_header: SecurityHeader = None # type: ignore
238
+ has_cors: bool = False
239
+ endpoints_initialized = False
240
+
241
+ @decorators.parameters_to_properties
242
+ def __init__(
243
+ self,
244
+ endpoints: list[Endpoint | Self],
245
+ url: str = "",
246
+ response_headers: list[str | Callable[..., list[str]]] = [],
247
+ security_headers: list[SecurityHeader] = [],
248
+ internal_casing: str = "snake_case",
249
+ external_casing: str = "snake_case",
250
+ authentication: Authentication = Public(),
251
+ authorization: Authorization = Authorization(),
252
+ ):
253
+ self.finalize_and_validate_configuration()
254
+ for security_header in self.security_headers:
255
+ if not security_header.is_cors:
256
+ continue
257
+ self.cors_header = security_header
258
+ self.has_cors = True
259
+ break
260
+
261
+ if not endpoints:
262
+ raise ValueError(
263
+ "An endpoint group must receive a list of endpoints/endpoint groups, but my list of endpoints is empty."
264
+ )
265
+ if not isinstance(endpoints, list):
266
+ raise ValueError(
267
+ f"An endpoint group must receive a list of endpoints/endpoint groups, but instead of a list I found an object of type '{endpoints.__class__.__name__}'"
268
+ )
269
+ for index, endpoint in enumerate(endpoints):
270
+ if not isinstance(endpoint, Endpoint) and not isinstance(endpoint, self.__class__):
271
+ raise ValueError(
272
+ f"An endpoint group must receive a list of endpoints/endpoint groups, but item #{index + 1} was neither an endpoint nor an endpoint group, but an object of type '{endpoints.__class__.__name__}'"
273
+ )
274
+ if self.url.strip("/"):
275
+ endpoint.add_url_prefix(self.url)
276
+
277
+ def add_url_prefix(self, prefix: str) -> None:
278
+ self.url = (prefix.rstrip("/") + "/" + self.url.lstrip("/")).lstrip("/")
279
+ for endpoint in self.endpoints:
280
+ endpoint.add_url_prefix(self.url)
281
+
282
+ def matches_request(self, input_output: InputOutput, allow_partial=True) -> bool:
283
+ """Whether or not we can handle an incoming request based on URL and request method."""
284
+ expected_url = self.url.strip("/")
285
+ incoming_url = input_output.get_full_path().strip("/")
286
+ if not expected_url and not incoming_url:
287
+ return True
288
+ (matches, routing_data) = routing.match_route(expected_url, incoming_url, allow_partial=allow_partial)
289
+ return matches
290
+
291
+ def populate_routing_data(self, input_output: InputOutput) -> Any:
292
+ # only endpoints (not the endpoint group) can handle this because the endpoint group doesn't have the full url
293
+ return None
294
+
295
+ def handle(self, input_output):
296
+ if not self.endpoints_initialized:
297
+ self.endpoints_initialized = True
298
+ for endpoint in self.endpoints:
299
+ endpoint.injectable_properties(self.di)
300
+
301
+ has_match = False
302
+ for endpoint in self.endpoints:
303
+ if not endpoint.matches_request(input_output):
304
+ continue
305
+ has_match = True
306
+ break
307
+
308
+ if not has_match:
309
+ return self.error(input_output, "Not Found", 404)
310
+
311
+ self.add_response_headers(input_output)
312
+
313
+ # "register" ourself with the DI system
314
+ current_endpoint_groups = self.di.build_from_name("endpoint_groups", cache=True)
315
+ current_endpoint_groups.append(self)
316
+ self.di.add_binding("endpoint_groups", current_endpoint_groups)
317
+
318
+ return endpoint(input_output)
319
+
320
+ def error(self, input_output: InputOutput, message: str, status_code: int) -> Any:
321
+ """Return a client-side error (e.g. 400)."""
322
+ return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
323
+
324
+ def all_endpoints(self) -> list[Endpoint]:
325
+ """Return the full (recursive) list of all endpoints associated with this endpoint group."""
326
+ all_endpoints: list[Endpoint] = []
327
+ for endpoint in self.endpoints:
328
+ if hasattr(endpoint, "all_endpoints"):
329
+ all_endpoints = [*all_endpoints, *endpoint.all_endpoints()]
330
+ else:
331
+ all_endpoints.append(endpoint)
332
+
333
+ return all_endpoints
@@ -0,0 +1,25 @@
1
+ from clearskies.endpoints.advanced_search import AdvancedSearch
2
+ from clearskies.endpoints.callable import Callable
3
+ from clearskies.endpoints.create import Create
4
+ from clearskies.endpoints.delete import Delete
5
+ from clearskies.endpoints.get import Get
6
+ from clearskies.endpoints.health_check import HealthCheck
7
+ from clearskies.endpoints.list import List
8
+ from clearskies.endpoints.restful_api import RestfulApi
9
+ from clearskies.endpoints.schema import Schema
10
+ from clearskies.endpoints.simple_search import SimpleSearch
11
+ from clearskies.endpoints.update import Update
12
+
13
+ __all__ = [
14
+ "AdvancedSearch",
15
+ "Callable",
16
+ "Create",
17
+ "Delete",
18
+ "Get",
19
+ "HealthCheck",
20
+ "List",
21
+ "RestfulApi",
22
+ "Schema",
23
+ "SimpleSearch",
24
+ "Update",
25
+ ]