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