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,417 @@
1
+ from __future__ import annotations
2
+
3
+ import inspect
4
+ from typing import TYPE_CHECKING, Any, Callable
5
+
6
+ from clearskies import configs, decorators
7
+ from clearskies.authentication import Authentication, Authorization, Public
8
+ from clearskies.endpoint_group import EndpointGroup
9
+ from clearskies.endpoints.create import Create
10
+ from clearskies.endpoints.delete import Delete
11
+ from clearskies.endpoints.get import Get
12
+ from clearskies.endpoints.simple_search import SimpleSearch
13
+ from clearskies.endpoints.update import Update
14
+
15
+ if TYPE_CHECKING:
16
+ from clearskies import Column, Endpoint, Model, Schema, SecurityHeader
17
+
18
+
19
+ class RestfulApi(EndpointGroup):
20
+ """
21
+ Full CRUD operations for a model.
22
+
23
+ This endpoint group sets up all five standard endpoints to manage a model:
24
+
25
+ 1. Create
26
+ 2. Update
27
+ 3. Delete
28
+ 4. Get
29
+ 5. List
30
+
31
+ As such, you can set any option for all of the above endpoints. All five endpoints are enabled by default
32
+ but can be turned off individually. It's important to understand that the actual API behavior is controlled
33
+ by other endoints. This endpoint group creates them and routes requests to them. So, to fully understand
34
+ the behavior of the subsequent Restful API, you have to consult the documentation for the endpoints themselves.
35
+
36
+ For routing purposes, the create and list endpoints are reachable via the URL specified for this endpoint group
37
+ and are separated by request method (POST for create by default, GET for list). The update, delete, and get
38
+ endoints all expect the id to be appended to the base URL, and then are separated by request method
39
+ (PATCH for update, DELETE for delete, and GET for get). See the example app and calls below:
40
+
41
+ ```python
42
+ import clearskies
43
+ from clearskies.validators import Required, Unique
44
+ from clearskies import columns
45
+
46
+
47
+ class User(clearskies.Model):
48
+ id_column_name = "id"
49
+ backend = clearskies.backends.MemoryBackend()
50
+
51
+ id = columns.Uuid()
52
+ name = columns.String(validators=[Required()])
53
+ username = columns.String(
54
+ validators=[
55
+ Required(),
56
+ Unique(),
57
+ ]
58
+ )
59
+ age = columns.Integer(validators=[Required()])
60
+ created_at = columns.Created()
61
+ updated_at = columns.Updated()
62
+
63
+
64
+ wsgi = clearskies.contexts.WsgiRef(
65
+ clearskies.endpoints.RestfulApi(
66
+ url="users",
67
+ model_class=User,
68
+ readable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
69
+ writeable_column_names=["name", "username", "age"],
70
+ sortable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
71
+ searchable_column_names=["id", "name", "username", "age", "created_at", "updated_at"],
72
+ default_sort_column_name="name",
73
+ )
74
+ )
75
+ wsgi()
76
+ ```
77
+
78
+ Which spins up a fully functional API. In the below usage examples we create two users, fetch
79
+ one of them, update a user, delete the other, and then list all users.
80
+
81
+ ```bash
82
+ $ curl 'http://localhost:8080/users' -d '{"name":"Bob", "username": "bob", "age": 25}' | jq
83
+ {
84
+ "status": "success",
85
+ "error": "",
86
+ "data": {
87
+ "id": "8bd9c03f-bb0c-41bd-afbc-f9526ded88f4",
88
+ "name": "Bob",
89
+ "username": "bob",
90
+ "age": 25,
91
+ "created_at": "2025-06-10T12:39:35+00:00",
92
+ "updated_at": "2025-06-10T12:39:35+00:00"
93
+ },
94
+ "pagination": {},
95
+ "input_errors": {}
96
+ }
97
+
98
+ $ curl 'http://localhost:8080/users' -d '{"name":"Alice", "username": "alice", "age": 22}' | jq
99
+ {
100
+ "status": "success",
101
+ "error": "",
102
+ "data": {
103
+ "id": "16d483c6-0eb1-4104-b07b-32f3d736223f",
104
+ "name": "Alice",
105
+ "username": "alice",
106
+ "age": 22,
107
+ "created_at": "2025-06-10T12:42:59+00:00",
108
+ "updated_at": "2025-06-10T12:42:59+00:00"
109
+ },
110
+ "pagination": {},
111
+ "input_errors": {}
112
+ }
113
+
114
+ $ curl 'http://localhost:8080/users/8bd9c03f-bb0c-41bd-afbc-f9526ded88f4' | jq
115
+ {
116
+ "status": "success",
117
+ "error": "",
118
+ "data": {
119
+ "id": "8bd9c03f-bb0c-41bd-afbc-f9526ded88f4",
120
+ "name": "Bob",
121
+ "username": "bob",
122
+ "age": 25,
123
+ "created_at": "2025-06-10T12:39:35+00:00",
124
+ "updated_at": "2025-06-10T12:39:35+00:00"
125
+ },
126
+ "pagination": {},
127
+ "input_errors": {}
128
+ }
129
+
130
+ $ curl 'http://localhost:8080/users/16d483c6-0eb1-4104-b07b-32f3d736223f' -d '{"name":"Alice Smith", "age": 23}' -X PATCH | jq
131
+ {
132
+ "status": "success",
133
+ "error": "",
134
+ "data": {
135
+ "id": "16d483c6-0eb1-4104-b07b-32f3d736223f",
136
+ "name": "Alice Smith",
137
+ "username": "alice",
138
+ "age": 23,
139
+ "created_at": "2025-06-10T12:42:59+00:00",
140
+ "updated_at": "2025-06-10T12:45:01+00:00"
141
+ },
142
+ "pagination": {},
143
+ "input_errors": {}
144
+ }
145
+
146
+ $ curl 'http://localhost:8080/users/8bd9c03f-bb0c-41bd-afbc-f9526ded88f4' -X DELETE | jq
147
+ {
148
+ "status": "success",
149
+ "error": "",
150
+ "data": {},
151
+ "pagination": {},
152
+ "input_errors": {}
153
+ }
154
+
155
+ $ curl 'http://localhost:8080/users/' | jq
156
+ {
157
+ "status": "success",
158
+ "error": "",
159
+ "data": [
160
+ {
161
+ "id": "16d483c6-0eb1-4104-b07b-32f3d736223f",
162
+ "name": "Alice Smith",
163
+ "username": "alice",
164
+ "age": 23,
165
+ "created_at": "2025-06-10T12:42:59+00:00",
166
+ "updated_at": "2025-06-10T12:45:01+00:00"
167
+ }
168
+ ],
169
+ "pagination": {
170
+ "number_results": 1,
171
+ "limit": 50,
172
+ "next_page": {}
173
+ },
174
+ "input_errors": {}
175
+ }
176
+ ```
177
+ """
178
+
179
+ """
180
+ The endpoint class to use for managing the create operation.
181
+
182
+ This defaults to `clearskies.endpoints.Create`. To disable the create operation all together,
183
+ set this to None.
184
+ """
185
+ create_endpoint = configs.Endpoint(default=Create)
186
+
187
+ """
188
+ The endpoint class to use for managing the delete operation.
189
+
190
+ This defaults to `clearskies.endpoints.Delete`. To disable the delete operation all together,
191
+ set this to None.
192
+ """
193
+ delete_endpoint = configs.Endpoint(default=Delete)
194
+
195
+ """
196
+ The endpoint class to use for managing the update operation.
197
+
198
+ This defaults to `clearskies.endpoints.Update`. To disable the update operation all together,
199
+ set this to None.
200
+ """
201
+ update_endpoint = configs.Endpoint(default=Update)
202
+
203
+ """
204
+ The endpoint class to use to fetch individual records.
205
+
206
+ This defaults to `clearskies.endpoints.Get`. To disable the get operation all together,
207
+ set this to None.
208
+ """
209
+ get_endpoint = configs.Endpoint(default=Get)
210
+
211
+ """
212
+ The endpoint class to use to list records.
213
+
214
+ This defaults to `clearskies.endpoints.SimpleSearch`. To disable the list operation all together,
215
+ set this to None.
216
+ """
217
+ list_endpoint = configs.Endpoint(default=SimpleSearch)
218
+
219
+ """
220
+ The request method(s) to use to route to the create operation. Default is ["POST"].
221
+ """
222
+ create_request_methods = configs.SelectList(
223
+ allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["POST"]
224
+ )
225
+
226
+ """
227
+ The request method(s) to use to route to the update operation. Default is ["PATCH"].
228
+ """
229
+ update_request_methods = configs.SelectList(
230
+ allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["PATCH"]
231
+ )
232
+
233
+ """
234
+ The request method(s) to use to route to the delete operation. Default is ["DELETE"].
235
+ """
236
+ delete_request_methods = configs.SelectList(
237
+ allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["DELETE"]
238
+ )
239
+
240
+ """
241
+ The request method(s) to use to route to the get operation. Default is ["GET"].
242
+ """
243
+ get_request_methods = configs.SelectList(allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["GET"])
244
+
245
+ """
246
+ The request method(s) to use to route to the create operation. Default is ["GET"].
247
+ """
248
+ list_request_methods = configs.SelectList(
249
+ allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH", "QUERY"], default=["GET", "POST", "QUERY"]
250
+ )
251
+
252
+ """
253
+ The request method(s) to use to route to the create operation. Default is ["POST"].
254
+ """
255
+ id_column_name = configs.ModelColumn("model_class", default=None)
256
+
257
+ """
258
+ The base URL to be used for all the endpoints.
259
+ """
260
+ url = configs.String(default="")
261
+
262
+ authentication = configs.Authentication(default=Public())
263
+ authorization = configs.Authorization(default=Authorization())
264
+ output_map = configs.Callable(default=None)
265
+ output_schema = configs.Schema(default=None)
266
+ model_class = configs.ModelClass(default=None)
267
+ readable_column_names = configs.ReadableModelColumns("model_class", default=[])
268
+ writeable_column_names = configs.WriteableModelColumns("model_class", default=[])
269
+ searchable_column_names = configs.SearchableModelColumns("model_class", default=[])
270
+ sortable_column_names = configs.ReadableModelColumns("model_class", default=[])
271
+ default_sort_column_name = configs.ModelColumn("model_class", required=True)
272
+ default_sort_direction = configs.Select(["ASC", "DESC"], default="ASC")
273
+ default_limit = configs.Integer(default=50)
274
+ maximum_limit = configs.Integer(default=200)
275
+ group_by_column_name = configs.ModelColumn("model_class")
276
+ input_validation_callable = configs.Callable(default=None)
277
+ include_routing_data_in_request_data = configs.Boolean(default=False)
278
+ column_overrides = configs.Columns(default={})
279
+ internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
280
+ external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
281
+ security_headers = configs.SecurityHeaders(default=[])
282
+ description = configs.String(default="")
283
+ where = configs.Conditions(default=[])
284
+ _descriptor_config_map = None
285
+
286
+ @decorators.parameters_to_properties
287
+ def __init__(
288
+ self,
289
+ model_class: type[Model],
290
+ writeable_column_names: list[str],
291
+ readable_column_names: list[str],
292
+ searchable_column_names: list[str],
293
+ sortable_column_names: list[str],
294
+ default_sort_column_name: str,
295
+ read_only: bool = False,
296
+ create_endpoint: type[Endpoint] | None = Create,
297
+ delete_endpoint: type[Endpoint] | None = Delete,
298
+ update_endpoint: type[Endpoint] | None = Update,
299
+ get_endpoint: type[Endpoint] | None = Get,
300
+ list_endpoint: type[Endpoint] | None = SimpleSearch,
301
+ create_request_methods: list[str] = ["POST"],
302
+ update_request_methods: list[str] = ["PATCH"],
303
+ delete_request_methods: list[str] = ["DELETE"],
304
+ get_request_methods: list[str] = ["GET"],
305
+ list_request_methods: list[str] = ["GET"],
306
+ id_column_name: str = "",
307
+ group_by_column_name: str = "",
308
+ input_validation_callable: Callable | None = None,
309
+ include_routing_data_in_request_data: bool = False,
310
+ url: str = "",
311
+ default_sort_direction: str = "ASC",
312
+ default_limit: int = 50,
313
+ maximum_limit: int = 200,
314
+ request_methods: list[str] = ["POST"],
315
+ response_headers: list[str | Callable[..., list[str]]] = [],
316
+ output_map: Callable[..., dict[str, Any]] | None = None,
317
+ output_schema: Schema | None = None,
318
+ column_overrides: dict[str, Column] = {},
319
+ internal_casing: str = "snake_case",
320
+ external_casing: str = "snake_case",
321
+ security_headers: list[SecurityHeader] = [],
322
+ description: str = "",
323
+ authentication: Authentication = Public(),
324
+ authorization: Authorization = Authorization(),
325
+ ):
326
+ self.finalize_and_validate_configuration()
327
+
328
+ id_column_name = id_column_name if id_column_name else model_class.id_column_name
329
+
330
+ # figure out which endpoints we actually need
331
+ endpoints_to_build = []
332
+ if not read_only:
333
+ if create_endpoint:
334
+ endpoints_to_build.append(
335
+ {
336
+ "class": create_endpoint,
337
+ "request_methods": create_request_methods,
338
+ }
339
+ )
340
+ if delete_endpoint:
341
+ endpoints_to_build.append(
342
+ {
343
+ "class": delete_endpoint,
344
+ "request_methods": delete_request_methods,
345
+ "url_suffix": f"/:{id_column_name}",
346
+ }
347
+ )
348
+ if update_endpoint:
349
+ endpoints_to_build.append(
350
+ {
351
+ "class": update_endpoint,
352
+ "request_methods": update_request_methods,
353
+ "url_suffix": f"/:{id_column_name}",
354
+ }
355
+ )
356
+ if get_endpoint:
357
+ endpoints_to_build.append(
358
+ {
359
+ "class": get_endpoint,
360
+ "request_methods": get_request_methods,
361
+ "url_suffix": f"/:{id_column_name}",
362
+ }
363
+ )
364
+ if list_endpoint:
365
+ endpoints_to_build.append(
366
+ {
367
+ "class": list_endpoint,
368
+ "request_methods": list_request_methods,
369
+ }
370
+ )
371
+
372
+ # and now build them! Pass along our own kwargs to the endoints when we build them. Now, technically, I
373
+ # know what the kwargs are for each endpoint. However, it would be a lot of duplication to manually
374
+ # instantiate each endpoint and pass along all the kwargs. So, fetch the list of kwargs from our own
375
+ # __init__ and then compare that with the kwargs of the __init__ for each endpoint and map everything
376
+ # automatically like that. Then add in the individual config from above.
377
+
378
+ # these lines take all of the arguments we were initialized with and dumps it into a dict. It's the
379
+ # equivalent of combining both *args and **kwargs without using either
380
+ my_args = inspect.getfullargspec(self.__class__)
381
+ local_variables = inspect.currentframe().f_locals # type: ignore
382
+ available_args = {arg: local_variables[arg] for arg in my_args.args[1:]}
383
+
384
+ # we handle this one manually
385
+ del available_args["url"]
386
+
387
+ # now loop through the list of endpoints
388
+ endpoints = []
389
+ for endpoint_to_build in endpoints_to_build:
390
+ # grab our class and any pre-defined configs
391
+ endpoint_class = endpoint_to_build["class"]
392
+ url_suffix = endpoint_to_build.get("url_suffix")
393
+
394
+ # now get the allowed args out of the init and fill them out with our own.
395
+ endpoint_args = inspect.getfullargspec(endpoint_class)
396
+ nendpoint_args = len(endpoint_args.args)
397
+ nendpoint_kwargs = len(endpoint_args.defaults) if endpoint_args.defaults else 0
398
+ final_args: list[str] = []
399
+ final_kwargs: dict[str, Any] = {}
400
+ for arg in endpoint_args.args[1:]:
401
+ if not available_args.get(arg):
402
+ continue
403
+ final_kwargs[arg] = available_args[arg]
404
+
405
+ if url_suffix:
406
+ final_kwargs["url"] = url_suffix
407
+ final_kwargs["request_methods"] = endpoint_to_build["request_methods"]
408
+ endpoints.append(endpoint_class(*final_args, **final_kwargs)) # type: ignore
409
+
410
+ super().__init__(
411
+ endpoints,
412
+ url=url,
413
+ response_headers=response_headers,
414
+ security_headers=security_headers,
415
+ authentication=authentication,
416
+ authorization=authorization,
417
+ )
@@ -0,0 +1,185 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING, Any, Callable
4
+
5
+ from clearskies import authentication, autodoc, configs, decorators
6
+ from clearskies.endpoint import Endpoint
7
+
8
+ if TYPE_CHECKING:
9
+ from clearskies import SecurityHeader
10
+ from clearskies.authentication import Authentication
11
+ from clearskies.input_outputs import InputOutput
12
+
13
+
14
+ class Schema(Endpoint):
15
+ """
16
+ An endpoint that automatically creates a swagger doc for the application.
17
+
18
+ The schema endpoint must always be attached to an endpoint group. It will document all endpoints
19
+ attached to its parent endpoint group.
20
+
21
+ Keep in mind that the routing in the endpoint group is greedy and goes from top-down. As a result,
22
+ since the schema endpoint (typically) has a specific URL, it's usually best for it to be at the top
23
+ of your endpoint list. The following example builds an application with two endpoint groups, each
24
+ of which has a schema endpoint:
25
+
26
+ ```
27
+ import clearskies
28
+ from clearskies.validators import Required, Unique
29
+ from clearskies import columns
30
+
31
+
32
+ class User(clearskies.Model):
33
+ id_column_name = "id"
34
+ backend = clearskies.backends.MemoryBackend()
35
+
36
+ id = columns.Uuid()
37
+ name = columns.String(validators=[Required()])
38
+ username = columns.String(
39
+ validators=[
40
+ Required(),
41
+ Unique(),
42
+ ]
43
+ )
44
+ age = columns.Integer(validators=[Required()])
45
+ company_name = columns.String()
46
+ created_at = columns.Created()
47
+ updated_at = columns.Updated()
48
+
49
+
50
+ readable_column_names = [
51
+ "id",
52
+ "name",
53
+ "username",
54
+ "age",
55
+ "company_name",
56
+ "created_at",
57
+ "updated_at",
58
+ ]
59
+ writeable_user_column_names = ["name", "username", "age", "company_name"]
60
+ users_api = clearskies.EndpointGroup(
61
+ [
62
+ clearskies.endpoints.Schema(url="schema"),
63
+ clearskies.endpoints.RestfulApi(
64
+ url="users",
65
+ model_class=User,
66
+ readable_column_names=readable_column_names,
67
+ writeable_column_names=writeable_user_column_names,
68
+ sortable_column_names=readable_column_names,
69
+ searchable_column_names=readable_column_names,
70
+ default_sort_column_name="name",
71
+ ),
72
+ ],
73
+ url="/users",
74
+ )
75
+
76
+
77
+ class SomeThing(clearskies.Model):
78
+ id_column_name = "id"
79
+ backend = clearskies.backends.MemoryBackend()
80
+
81
+ id = clearskies.columns.Uuid()
82
+ thing_1 = clearskies.columns.String(validators=[Required()])
83
+ thing_2 = clearskies.columns.String(validators=[Unique()])
84
+
85
+
86
+ more_endpoints = clearskies.EndpointGroup(
87
+ [
88
+ clearskies.endpoints.HealthCheck(url="health"),
89
+ clearskies.endpoints.Schema(url="schema"),
90
+ clearskies.endpoints.Callable(
91
+ lambda request_data, some_things: some_things.create(request_data),
92
+ model_class=SomeThing,
93
+ readable_column_names=["id", "thing_1", "thing_2"],
94
+ writeable_column_names=["thing_1", "thing_2"],
95
+ request_methods=["POST"],
96
+ url="some_thing",
97
+ ),
98
+ users_api,
99
+ ]
100
+ )
101
+
102
+ wsgi = clearskies.contexts.WsgiRef(more_endpoints)
103
+ wsgi()
104
+ ```
105
+
106
+ We attach the `more_endpoints` endpoint group to our context, and this contains 4 endpoints:
107
+
108
+ 1. A healthcheck
109
+ 2. A schema endpoint
110
+ 3. A callable endpoint
111
+ 4. The `users_api` endpoint group.
112
+
113
+ The `users_api` endpoint group then contains it's own schema endpoint and a restful api endpoint
114
+ with all our standard user CRUD operations. As a result, we can fetch two different schema endpoints:
115
+
116
+ ```
117
+ curl 'http://localhost/schema'
118
+
119
+ curl 'http://localhost/users/schema'
120
+ ```
121
+
122
+ The former documents all endpoints in the system. The latter only documents the endpoints under the `/users`
123
+ path provided by the `users_api` endpoint group.
124
+ """
125
+
126
+ """
127
+ The doc builder class/format to use
128
+ """
129
+ schema_format = configs.Any(default=autodoc.formats.oai3_json.Oai3Json)
130
+
131
+ """
132
+ Addiional data to inject into the schema doc.
133
+
134
+ This is typically used for setting info/server settings in the resultant swagger doc. Anything
135
+ in this dictionary is injected into the "root" of the generated documentation file.
136
+ """
137
+ schema_configuration = configs.AnyDict(default={})
138
+
139
+ @decorators.parameters_to_properties
140
+ def __init__(
141
+ self,
142
+ url: str,
143
+ schema_format=autodoc.formats.oai3_json.Oai3Json,
144
+ request_methods: list[str] = ["GET"],
145
+ response_headers: list[str | Callable[..., list[str]]] = [],
146
+ security_headers: list[SecurityHeader] = [],
147
+ schema_configuration: dict[str, Any] = {},
148
+ authentication: Authentication = authentication.Public(),
149
+ ):
150
+ # we need to call the parent but don't have to pass along any of our kwargs. They are all optional in our parent, and our parent class
151
+ # just stores them in parameters, which we have already done. However, the parent does do some extra initialization stuff that we need,
152
+ # which is why we have to call the parent.
153
+ super().__init__()
154
+
155
+ def handle(self, input_output: InputOutput) -> Any:
156
+ current_endpoint_groups = self.di.build_from_name("endpoint_groups", cache=True)
157
+ if not current_endpoint_groups:
158
+ raise ValueError(
159
+ f"{self.__class__.__name__} endpoint was attached directly to the context, but it must be attached to an endpoint group (otherwise it has no application to document)."
160
+ )
161
+
162
+ # the endpoint group at the end of the list is the one that invoked us. Let's grab it
163
+ # if we don't hvae any endpoint groups then we've been attached directly to a context,
164
+ # which is pointless - there's nothing for us to document. So, treat it as an error.
165
+ endpoint_group = current_endpoint_groups[-1]
166
+ requests: list[Any] = []
167
+ models: dict[str, Any] = {}
168
+ security_schemes: dict[str, Any] = {}
169
+ for endpoint in endpoint_group.all_endpoints():
170
+ requests.extend(endpoint.documentation())
171
+ models = {**models, **endpoint.documentation_models()}
172
+ # if "user" in models:
173
+ # print(models["user"].children)
174
+ # print(endpoint.__class__.__name__)
175
+ security_schemes = {**security_schemes, **endpoint.documentation_security_schemes()}
176
+ # print(models["user"].children)
177
+
178
+ schema = self.di.build(self.schema_format)
179
+ schema.set_requests(requests)
180
+ schema.set_components({"models": models, "securitySchemes": security_schemes})
181
+ extra_schema_config = {**self.schema_configuration}
182
+ if "info" not in extra_schema_config:
183
+ extra_schema_config["info"] = {"title": "Auto generated by clearskies", "version": "1.0"}
184
+ self.add_response_headers(input_output)
185
+ return input_output.respond(schema.pretty(root_properties=extra_schema_config), 200)