clear-skies 2.0.3__py3-none-any.whl → 2.0.5__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 (251) hide show
  1. clear_skies-2.0.5.dist-info/METADATA +74 -0
  2. clear_skies-2.0.5.dist-info/RECORD +4 -0
  3. {clear_skies-2.0.3.dist-info → clear_skies-2.0.5.dist-info}/WHEEL +1 -1
  4. clear_skies-2.0.3.dist-info/METADATA +0 -46
  5. clear_skies-2.0.3.dist-info/RECORD +0 -249
  6. clearskies/__init__.py +0 -59
  7. clearskies/action.py +0 -7
  8. clearskies/authentication/__init__.py +0 -15
  9. clearskies/authentication/authentication.py +0 -46
  10. clearskies/authentication/authorization.py +0 -16
  11. clearskies/authentication/authorization_pass_through.py +0 -20
  12. clearskies/authentication/jwks.py +0 -163
  13. clearskies/authentication/public.py +0 -5
  14. clearskies/authentication/secret_bearer.py +0 -553
  15. clearskies/autodoc/__init__.py +0 -8
  16. clearskies/autodoc/formats/__init__.py +0 -5
  17. clearskies/autodoc/formats/oai3_json/__init__.py +0 -7
  18. clearskies/autodoc/formats/oai3_json/oai3_json.py +0 -87
  19. clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +0 -15
  20. clearskies/autodoc/formats/oai3_json/parameter.py +0 -35
  21. clearskies/autodoc/formats/oai3_json/request.py +0 -68
  22. clearskies/autodoc/formats/oai3_json/response.py +0 -28
  23. clearskies/autodoc/formats/oai3_json/schema/__init__.py +0 -11
  24. clearskies/autodoc/formats/oai3_json/schema/array.py +0 -9
  25. clearskies/autodoc/formats/oai3_json/schema/default.py +0 -13
  26. clearskies/autodoc/formats/oai3_json/schema/enum.py +0 -7
  27. clearskies/autodoc/formats/oai3_json/schema/object.py +0 -29
  28. clearskies/autodoc/formats/oai3_json/test.json +0 -1985
  29. clearskies/autodoc/py.typed +0 -0
  30. clearskies/autodoc/request/__init__.py +0 -15
  31. clearskies/autodoc/request/header.py +0 -6
  32. clearskies/autodoc/request/json_body.py +0 -6
  33. clearskies/autodoc/request/parameter.py +0 -8
  34. clearskies/autodoc/request/request.py +0 -38
  35. clearskies/autodoc/request/url_parameter.py +0 -6
  36. clearskies/autodoc/request/url_path.py +0 -6
  37. clearskies/autodoc/response/__init__.py +0 -5
  38. clearskies/autodoc/response/response.py +0 -9
  39. clearskies/autodoc/schema/__init__.py +0 -31
  40. clearskies/autodoc/schema/array.py +0 -10
  41. clearskies/autodoc/schema/base64.py +0 -8
  42. clearskies/autodoc/schema/boolean.py +0 -5
  43. clearskies/autodoc/schema/date.py +0 -5
  44. clearskies/autodoc/schema/datetime.py +0 -5
  45. clearskies/autodoc/schema/double.py +0 -5
  46. clearskies/autodoc/schema/enum.py +0 -17
  47. clearskies/autodoc/schema/integer.py +0 -6
  48. clearskies/autodoc/schema/long.py +0 -5
  49. clearskies/autodoc/schema/number.py +0 -6
  50. clearskies/autodoc/schema/object.py +0 -13
  51. clearskies/autodoc/schema/password.py +0 -5
  52. clearskies/autodoc/schema/schema.py +0 -11
  53. clearskies/autodoc/schema/string.py +0 -5
  54. clearskies/backends/__init__.py +0 -65
  55. clearskies/backends/api_backend.py +0 -1178
  56. clearskies/backends/backend.py +0 -136
  57. clearskies/backends/cursor_backend.py +0 -335
  58. clearskies/backends/memory_backend.py +0 -797
  59. clearskies/backends/secrets_backend.py +0 -106
  60. clearskies/column.py +0 -1233
  61. clearskies/columns/__init__.py +0 -71
  62. clearskies/columns/audit.py +0 -206
  63. clearskies/columns/belongs_to_id.py +0 -483
  64. clearskies/columns/belongs_to_model.py +0 -132
  65. clearskies/columns/belongs_to_self.py +0 -105
  66. clearskies/columns/boolean.py +0 -113
  67. clearskies/columns/category_tree.py +0 -275
  68. clearskies/columns/category_tree_ancestors.py +0 -51
  69. clearskies/columns/category_tree_children.py +0 -127
  70. clearskies/columns/category_tree_descendants.py +0 -48
  71. clearskies/columns/created.py +0 -95
  72. clearskies/columns/created_by_authorization_data.py +0 -116
  73. clearskies/columns/created_by_header.py +0 -99
  74. clearskies/columns/created_by_ip.py +0 -92
  75. clearskies/columns/created_by_routing_data.py +0 -97
  76. clearskies/columns/created_by_user_agent.py +0 -92
  77. clearskies/columns/date.py +0 -234
  78. clearskies/columns/datetime.py +0 -282
  79. clearskies/columns/email.py +0 -76
  80. clearskies/columns/float.py +0 -153
  81. clearskies/columns/has_many.py +0 -505
  82. clearskies/columns/has_many_self.py +0 -56
  83. clearskies/columns/has_one.py +0 -14
  84. clearskies/columns/integer.py +0 -160
  85. clearskies/columns/json.py +0 -126
  86. clearskies/columns/many_to_many_ids.py +0 -337
  87. clearskies/columns/many_to_many_ids_with_data.py +0 -274
  88. clearskies/columns/many_to_many_models.py +0 -158
  89. clearskies/columns/many_to_many_pivots.py +0 -134
  90. clearskies/columns/phone.py +0 -159
  91. clearskies/columns/select.py +0 -92
  92. clearskies/columns/string.py +0 -102
  93. clearskies/columns/timestamp.py +0 -164
  94. clearskies/columns/updated.py +0 -110
  95. clearskies/columns/uuid.py +0 -86
  96. clearskies/configs/README.md +0 -105
  97. clearskies/configs/__init__.py +0 -162
  98. clearskies/configs/actions.py +0 -43
  99. clearskies/configs/any.py +0 -13
  100. clearskies/configs/any_dict.py +0 -22
  101. clearskies/configs/any_dict_or_callable.py +0 -23
  102. clearskies/configs/authentication.py +0 -23
  103. clearskies/configs/authorization.py +0 -23
  104. clearskies/configs/boolean.py +0 -16
  105. clearskies/configs/boolean_or_callable.py +0 -18
  106. clearskies/configs/callable_config.py +0 -18
  107. clearskies/configs/columns.py +0 -34
  108. clearskies/configs/conditions.py +0 -30
  109. clearskies/configs/config.py +0 -24
  110. clearskies/configs/datetime.py +0 -18
  111. clearskies/configs/datetime_or_callable.py +0 -19
  112. clearskies/configs/endpoint.py +0 -23
  113. clearskies/configs/endpoint_list.py +0 -28
  114. clearskies/configs/float.py +0 -16
  115. clearskies/configs/float_or_callable.py +0 -18
  116. clearskies/configs/integer.py +0 -16
  117. clearskies/configs/integer_or_callable.py +0 -18
  118. clearskies/configs/joins.py +0 -30
  119. clearskies/configs/list_any_dict.py +0 -30
  120. clearskies/configs/list_any_dict_or_callable.py +0 -31
  121. clearskies/configs/model_class.py +0 -35
  122. clearskies/configs/model_column.py +0 -65
  123. clearskies/configs/model_columns.py +0 -56
  124. clearskies/configs/model_destination_name.py +0 -25
  125. clearskies/configs/model_to_id_column.py +0 -43
  126. clearskies/configs/readable_model_column.py +0 -9
  127. clearskies/configs/readable_model_columns.py +0 -9
  128. clearskies/configs/schema.py +0 -23
  129. clearskies/configs/searchable_model_columns.py +0 -9
  130. clearskies/configs/security_headers.py +0 -39
  131. clearskies/configs/select.py +0 -26
  132. clearskies/configs/select_list.py +0 -47
  133. clearskies/configs/string.py +0 -29
  134. clearskies/configs/string_dict.py +0 -32
  135. clearskies/configs/string_list.py +0 -32
  136. clearskies/configs/string_list_or_callable.py +0 -35
  137. clearskies/configs/string_or_callable.py +0 -18
  138. clearskies/configs/timedelta.py +0 -18
  139. clearskies/configs/timezone.py +0 -18
  140. clearskies/configs/url.py +0 -23
  141. clearskies/configs/validators.py +0 -45
  142. clearskies/configs/writeable_model_column.py +0 -9
  143. clearskies/configs/writeable_model_columns.py +0 -9
  144. clearskies/configurable.py +0 -76
  145. clearskies/contexts/__init__.py +0 -11
  146. clearskies/contexts/cli.py +0 -117
  147. clearskies/contexts/context.py +0 -98
  148. clearskies/contexts/wsgi.py +0 -76
  149. clearskies/contexts/wsgi_ref.py +0 -82
  150. clearskies/decorators.py +0 -33
  151. clearskies/di/__init__.py +0 -14
  152. clearskies/di/additional_config.py +0 -130
  153. clearskies/di/additional_config_auto_import.py +0 -17
  154. clearskies/di/di.py +0 -968
  155. clearskies/di/inject/__init__.py +0 -23
  156. clearskies/di/inject/by_class.py +0 -21
  157. clearskies/di/inject/by_name.py +0 -18
  158. clearskies/di/inject/di.py +0 -13
  159. clearskies/di/inject/environment.py +0 -14
  160. clearskies/di/inject/input_output.py +0 -20
  161. clearskies/di/inject/now.py +0 -13
  162. clearskies/di/inject/requests.py +0 -13
  163. clearskies/di/inject/secrets.py +0 -14
  164. clearskies/di/inject/utcnow.py +0 -13
  165. clearskies/di/inject/uuid.py +0 -15
  166. clearskies/di/injectable.py +0 -29
  167. clearskies/di/injectable_properties.py +0 -131
  168. clearskies/di/test_module/__init__.py +0 -6
  169. clearskies/di/test_module/another_module/__init__.py +0 -2
  170. clearskies/di/test_module/module_class.py +0 -5
  171. clearskies/end.py +0 -183
  172. clearskies/endpoint.py +0 -1310
  173. clearskies/endpoint_group.py +0 -310
  174. clearskies/endpoints/__init__.py +0 -23
  175. clearskies/endpoints/advanced_search.py +0 -526
  176. clearskies/endpoints/callable.py +0 -388
  177. clearskies/endpoints/create.py +0 -202
  178. clearskies/endpoints/delete.py +0 -139
  179. clearskies/endpoints/get.py +0 -275
  180. clearskies/endpoints/health_check.py +0 -181
  181. clearskies/endpoints/list.py +0 -573
  182. clearskies/endpoints/restful_api.py +0 -427
  183. clearskies/endpoints/simple_search.py +0 -286
  184. clearskies/endpoints/update.py +0 -190
  185. clearskies/environment.py +0 -104
  186. clearskies/exceptions/__init__.py +0 -17
  187. clearskies/exceptions/authentication.py +0 -2
  188. clearskies/exceptions/authorization.py +0 -2
  189. clearskies/exceptions/client_error.py +0 -2
  190. clearskies/exceptions/input_errors.py +0 -4
  191. clearskies/exceptions/moved_permanently.py +0 -3
  192. clearskies/exceptions/moved_temporarily.py +0 -3
  193. clearskies/exceptions/not_found.py +0 -2
  194. clearskies/functional/__init__.py +0 -7
  195. clearskies/functional/routing.py +0 -92
  196. clearskies/functional/string.py +0 -112
  197. clearskies/functional/validations.py +0 -76
  198. clearskies/input_outputs/__init__.py +0 -13
  199. clearskies/input_outputs/cli.py +0 -171
  200. clearskies/input_outputs/exceptions/__init__.py +0 -2
  201. clearskies/input_outputs/exceptions/cli_input_error.py +0 -2
  202. clearskies/input_outputs/exceptions/cli_not_found.py +0 -2
  203. clearskies/input_outputs/headers.py +0 -45
  204. clearskies/input_outputs/input_output.py +0 -138
  205. clearskies/input_outputs/programmatic.py +0 -69
  206. clearskies/input_outputs/py.typed +0 -0
  207. clearskies/input_outputs/wsgi.py +0 -77
  208. clearskies/model.py +0 -1922
  209. clearskies/py.typed +0 -0
  210. clearskies/query/__init__.py +0 -12
  211. clearskies/query/condition.py +0 -223
  212. clearskies/query/join.py +0 -136
  213. clearskies/query/query.py +0 -196
  214. clearskies/query/sort.py +0 -27
  215. clearskies/schema.py +0 -82
  216. clearskies/secrets/__init__.py +0 -6
  217. clearskies/secrets/additional_configs/__init__.py +0 -32
  218. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +0 -61
  219. clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +0 -160
  220. clearskies/secrets/akeyless.py +0 -182
  221. clearskies/secrets/exceptions/__init__.py +0 -1
  222. clearskies/secrets/exceptions/not_found.py +0 -2
  223. clearskies/secrets/secrets.py +0 -38
  224. clearskies/security_header.py +0 -15
  225. clearskies/security_headers/__init__.py +0 -11
  226. clearskies/security_headers/cache_control.py +0 -67
  227. clearskies/security_headers/cors.py +0 -50
  228. clearskies/security_headers/csp.py +0 -94
  229. clearskies/security_headers/hsts.py +0 -22
  230. clearskies/security_headers/x_content_type_options.py +0 -0
  231. clearskies/security_headers/x_frame_options.py +0 -0
  232. clearskies/test_base.py +0 -8
  233. clearskies/typing.py +0 -11
  234. clearskies/validator.py +0 -37
  235. clearskies/validators/__init__.py +0 -33
  236. clearskies/validators/after_column.py +0 -62
  237. clearskies/validators/before_column.py +0 -13
  238. clearskies/validators/in_the_future.py +0 -32
  239. clearskies/validators/in_the_future_at_least.py +0 -11
  240. clearskies/validators/in_the_future_at_most.py +0 -10
  241. clearskies/validators/in_the_past.py +0 -32
  242. clearskies/validators/in_the_past_at_least.py +0 -10
  243. clearskies/validators/in_the_past_at_most.py +0 -10
  244. clearskies/validators/maximum_length.py +0 -26
  245. clearskies/validators/maximum_value.py +0 -29
  246. clearskies/validators/minimum_length.py +0 -26
  247. clearskies/validators/minimum_value.py +0 -29
  248. clearskies/validators/required.py +0 -34
  249. clearskies/validators/timedelta.py +0 -59
  250. clearskies/validators/unique.py +0 -30
  251. {clear_skies-2.0.3.dist-info → clear_skies-2.0.5.dist-info/licenses}/LICENSE +0 -0
@@ -1,388 +0,0 @@
1
- from __future__ import annotations
2
-
3
- import inspect
4
- from collections import OrderedDict
5
- from typing import TYPE_CHECKING, Any
6
- from typing import Callable as CallableType
7
-
8
- import clearskies.configs
9
- import clearskies.exceptions
10
- from clearskies import authentication, autodoc, typing
11
- from clearskies.endpoint import Endpoint
12
- from clearskies.functional import string
13
- from clearskies.input_outputs import InputOutput
14
-
15
- if TYPE_CHECKING:
16
- from clearskies import Column, SecurityHeader
17
- from clearskies.authentication import Authentication, Authorization
18
- from clearskies.model import Model
19
- from clearskies.schema import Schema
20
-
21
-
22
- class Callable(Endpoint):
23
- """
24
- An endpoint that executes a user-defined function.
25
-
26
- The Callable endpoint does exactly that - you provide a function that will be called when the endpoint is invoked. Like
27
- all callables invoked by clearskies, you can request any defined dependency that can be provided by the clearskies
28
- framework.
29
-
30
- Whatever you return will be returned to the client. By default, the return value is sent along in the `data` parameter
31
- of the standard clearskies response. To suppress this behavior, set `return_standard_response` to `False`. You can also
32
- return a model instance, a model query, or a list of model instances and the callable endpoint will automatically return
33
- the columns specified in `readable_column_names` to the client.
34
-
35
- Here's a basic working example:
36
-
37
- ```python
38
- import clearskies
39
-
40
-
41
- class User(clearskies.Model):
42
- id_column_name = "id"
43
- backend = clearskies.backends.MemoryBackend()
44
- id = clearskies.columns.Uuid()
45
- first_name = clearskies.columns.String()
46
- last_name = clearskies.columns.String()
47
- age = clearskies.columns.Integer()
48
-
49
-
50
- def my_users_callable(users: User):
51
- bob = users.create({"first_name": "Bob", "last_name": "Brown", "age": 10})
52
- jane = users.create({"first_name": "Jane", "last_name": "Brown", "age": 10})
53
- alice = users.create({"first_name": "Alice", "last_name": "Green", "age": 10})
54
-
55
- return jane
56
-
57
-
58
- my_users = clearskies.endpoints.Callable(
59
- my_users_callable,
60
- model_class=User,
61
- readable_column_names=["id", "first_name", "last_name"],
62
- )
63
-
64
- wsgi = clearskies.contexts.WsgiRef(
65
- my_users,
66
- classes=[User],
67
- )
68
- wsgi()
69
- ```
70
-
71
- If you run the above script and invoke the server:
72
-
73
- ```bash
74
- $ curl 'http://localhost:8080' | jq
75
- {
76
- "status": "success",
77
- "error": "",
78
- "data": {
79
- "id": "4a35a616-3d57-456f-8306-7c610a5e80e1",
80
- "first_name": "Jane",
81
- "last_name": "Brown"
82
- },
83
- "pagination": {},
84
- "input_errors": {}
85
- }
86
- ```
87
-
88
- The above example demonstrates returning a model and using readable_column_names to decide what is actually sent to the client
89
- (note that age is left out of the response). The advantage of doing it this way is that clearskies can also auto-generate
90
- OpenAPI documentation using this strategy. Of course, you can also just return any arbitrary data you want. If you do return
91
- custom data, and also want your API to be documented, you can pass a schema along to output_schema so clearskies can document
92
- it:
93
-
94
- ```python
95
- import clearskies
96
-
97
-
98
- class DogResponse(clearskies.Schema):
99
- species = (clearskies.columns.String(),)
100
- nickname = (clearskies.columns.String(),)
101
- level = (clearskies.columns.Integer(),)
102
-
103
-
104
- clearskies.contexts.WsgiRef(
105
- clearskies.endpoints.Callable(
106
- lambda: {"species": "dog", "nickname": "Spot", "level": 100},
107
- output_schema=DogResponse,
108
- )
109
- )()
110
- ```
111
-
112
- """
113
-
114
- """
115
- The callable to execute when the endpoint is invoked
116
- """
117
- to_call = clearskies.configs.Callable(default=None)
118
-
119
- """
120
- A schema that describes the expected input from the client.
121
-
122
- Note that if this is specified it will take precedence over writeable_column_names and model_class, which
123
- can also be used to specify the expected input.
124
-
125
- ```python
126
- import clearskies
127
-
128
- class ExpectedInput(clearskies.Schema):
129
- first_name = clearskies.columns.String(validators=[clearskies.validators.Required()])
130
- last_name = clearskies.columns.String()
131
- age = clearskies.columns.Integer(validators=[clearskies.validators.MinimumValue(0)])
132
-
133
- reflect = clearskies.endpoints.Callable(
134
- lambda request_data: request_data,
135
- request_methods=["POST"],
136
- input_schema=ExpectedInput,
137
- )
138
-
139
- wsgi = clearskies.contexts.WsgiRef(reflect)
140
- wsgi()
141
- ```
142
-
143
- And then valid and invalid requests:
144
-
145
- ```bash
146
- $ curl http://localhost:8080 -d '{"first_name":"Jane","last_name":"Doe","age":1}' | jq
147
- {
148
- "status": "success",
149
- "error": "",
150
- "data": {
151
- "first_name": "Jane",
152
- "last_name": "Doe",
153
- "age": 1
154
- },
155
- "pagination": {},
156
- "input_errors": {}
157
- }
158
-
159
- $ curl http://localhost:8080 -d '{"last_name":10,"age":-1,"check":"cool"}' | jq
160
- {
161
- "status": "input_errors",
162
- "error": "",
163
- "data": [],
164
- "pagination": {},
165
- "input_errors": {
166
- "age": "'age' must be at least 0.",
167
- "first_name": "'first_name' is required.",
168
- "last_name": "value should be a string",
169
- "check": "Input column check is not an allowed input column."
170
- }
171
- }
172
- ```
173
-
174
- """
175
- input_schema = clearskies.configs.Schema(default=None)
176
-
177
- """
178
- Whether or not the return value is meant to be wrapped up in the standard clearskies response schema.
179
-
180
- With the standard response schema, the return value of the function will be placed in the `data` portion of
181
- the standard clearskies response:
182
-
183
- ```python
184
- import clearskies
185
-
186
- wsgi = clearskies.contexts.WsgiRef(
187
- clearskies.endpoints.Callable(
188
- lambda: {"hello": "world"},
189
- return_standard_response=True, # the default value
190
- )
191
- )
192
- wsgi()
193
- ```
194
-
195
- Results in:
196
-
197
- ```bash
198
- $ curl http://localhost:8080 | jq
199
- {
200
- "status": "success",
201
- "error": "",
202
- "data": {
203
- "hello": "world"
204
- },
205
- "pagination": {},
206
- "input_errors": {}
207
- }
208
- ```
209
- But if you want to build your own response:
210
-
211
- ```python
212
- import clearskies
213
-
214
- wsgi = clearskies.contexts.WsgiRef(
215
- clearskies.endpoints.Callable(
216
- lambda: {"hello": "world"},
217
- return_standard_response=False,
218
- )
219
- )
220
- wsgi()
221
- ```
222
-
223
- Results in:
224
-
225
- ```bash
226
- $ curl http://localhost:8080 | jq
227
- {
228
- "hello": "world"
229
- }
230
- ```
231
-
232
- Note that you can also return strings this way instead of objects/JSON.
233
-
234
- """
235
- return_standard_response = clearskies.configs.Boolean(default=True)
236
-
237
- """
238
- Set to true if the callable will be returning multiple records (used when building the auto-documentation)
239
- """
240
- return_records = clearskies.configs.Boolean(default=False)
241
-
242
- @clearskies.decorators.parameters_to_properties
243
- def __init__(
244
- self,
245
- to_call: CallableType,
246
- url: str = "",
247
- request_methods: list[str] = ["GET"],
248
- model_class: type[clearskies.model.Model] | None = None,
249
- readable_column_names: list[str] = [],
250
- writeable_column_names: list[str] = [],
251
- input_schema: Schema | None = None,
252
- output_schema: Schema | None = None,
253
- input_validation_callable: CallableType | None = None,
254
- return_standard_response: bool = True,
255
- return_records: bool = False,
256
- response_headers: list[str | CallableType[..., list[str]]] = [],
257
- output_map: CallableType[..., dict[str, Any]] | None = None,
258
- column_overrides: dict[str, Column] = {},
259
- internal_casing: str = "snake_case",
260
- external_casing: str = "snake_case",
261
- security_headers: list[SecurityHeader] = [],
262
- description: str = "",
263
- authentication: Authentication = authentication.Public(),
264
- authorization: Authorization = authentication.Authorization(),
265
- ):
266
- # 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
267
- # just stores them in parameters, which we have already done. However, the parent does do some extra initialization stuff that we need,
268
- # which is why we have to call the parent.
269
- super().__init__()
270
-
271
- if self.input_schema and not self.writeable_column_names:
272
- self.writeable_column_names = list(self.input_schema.get_columns().keys())
273
-
274
- def handle(self, input_output: InputOutput):
275
- if self.writeable_column_names or self.input_schema:
276
- self.validate_input_against_schema(
277
- self.get_request_data(input_output),
278
- input_output,
279
- self.input_schema if self.input_schema else self.model_class,
280
- )
281
- else:
282
- input_errors = self.find_input_errors_from_callable(input_output.request_data, input_output)
283
- if input_errors:
284
- raise clearskies.exceptions.InputErrors(input_errors)
285
- response = self.di.call_function(self.to_call, **input_output.get_context_for_callables())
286
-
287
- if not self.return_standard_response:
288
- return input_output.respond(response, 200)
289
-
290
- # did the developer return a model?
291
- if self.model_class and isinstance(response, self.model_class):
292
- # and is it a query or a single model?
293
- if response._data:
294
- return self.success(input_output, self.model_as_json(response, input_output))
295
- else:
296
- # with a query we can also get pagination data, maybe?
297
- converted_models = [self.model_as_json(model, input_output) for model in response]
298
- return self.success(
299
- input_output,
300
- converted_models,
301
- number_results=len(response) if response.backend.can_count else None,
302
- next_page=response.next_page_data(),
303
- limit=response.get_query().limit,
304
- )
305
-
306
- # or did they return a list of models?
307
- if isinstance(response, list) and all(isinstance(item, self.model_class) for item in response):
308
- return self.success(input_output, [self.model_as_json(model, input_output) for model in response])
309
-
310
- # if none of the above, just return the data
311
- return self.success(input_output, response)
312
-
313
- def documentation(self) -> list[autodoc.request.Request]:
314
- output_schema = self.output_schema if self.output_schema else self.model_class
315
- nice_model = string.camel_case_to_words(output_schema.__name__)
316
-
317
- schema_model_name = string.camel_case_to_snake_case(output_schema.__name__)
318
- output_data_schema = (
319
- self.documentation_data_schema(output_schema, self.readable_column_names)
320
- if self.readable_column_names
321
- else []
322
- )
323
- output_autodoc = (
324
- autodoc.schema.Object(
325
- self.auto_case_internal_column_name("data"),
326
- children=output_data_schema,
327
- model_name=schema_model_name if self.readable_column_names else "",
328
- ),
329
- )
330
- if self.return_records:
331
- output_autodoc.name = nice_model # type: ignore
332
- output_autodoc = autodoc.schema.Array(
333
- self.auto_case_internal_column_name("data"),
334
- output_autodoc,
335
- ) # type: ignore
336
-
337
- authentication = self.authentication
338
- standard_error_responses = []
339
- if not getattr(authentication, "is_public", False):
340
- standard_error_responses.append(self.documentation_access_denied_response())
341
- if getattr(authentication, "can_authorize", False):
342
- standard_error_responses.append(self.documentation_unauthorized_response())
343
- if self.writeable_column_names:
344
- standard_error_responses.append(self.documentation_input_error_response())
345
-
346
- return [
347
- autodoc.request.Request(
348
- self.description,
349
- [
350
- self.documentation_success_response(
351
- output_autodoc, # type: ignore
352
- description=self.description,
353
- include_pagination=self.return_records,
354
- ),
355
- *standard_error_responses,
356
- self.documentation_generic_error_response(),
357
- ],
358
- relative_path=self.url,
359
- request_methods=self.request_methods,
360
- parameters=[
361
- *self.documentation_request_parameters(),
362
- *self.standard_url_parameters(),
363
- ],
364
- root_properties={
365
- "security": self.documentation_request_security(),
366
- },
367
- ),
368
- ]
369
-
370
- def documentation_request_parameters(self) -> list[autodoc.request.Parameter]:
371
- if not self.writeable_column_names:
372
- return []
373
-
374
- return self.standard_json_request_parameters(self.input_schema if self.input_schema else self.model_class)
375
-
376
- def documentation_models(self) -> dict[str, autodoc.schema.Schema]:
377
- if not self.readable_column_names:
378
- return {}
379
-
380
- output_schema = self.output_schema if self.output_schema else self.model_class
381
- schema_model_name = string.camel_case_to_snake_case(output_schema.__name__)
382
-
383
- return {
384
- schema_model_name: autodoc.schema.Object(
385
- self.auto_case_internal_column_name("data"),
386
- children=self.documentation_data_schema(output_schema, self.readable_column_names),
387
- ),
388
- }
@@ -1,202 +0,0 @@
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.exceptions
9
- from clearskies import authentication, autodoc, typing
10
- from clearskies.endpoint import Endpoint
11
- from clearskies.functional import string
12
- from clearskies.input_outputs import InputOutput
13
-
14
- if TYPE_CHECKING:
15
- from clearskies import Column, SecurityHeader
16
- from clearskies.model import Model
17
-
18
-
19
- class Create(Endpoint):
20
- """
21
- An endpoint to create a record.
22
-
23
- This endpoint accepts user input and uses it to create a record for the given model class. You have
24
- to provide the model class, which columns the end-user can set, and which columns get returned
25
- to the client. The column definitions in the model class are used to strictly validate the user
26
- input. Here's a basic example of a model class with the create endpoint in use:
27
-
28
- ```python
29
- import clearskies
30
- from clearskies import validators, columns
31
-
32
-
33
- class MyAwesomeModel(clearskies.Model):
34
- id_column_name = "id"
35
- backend = clearskies.backends.MemoryBackend()
36
-
37
- id = columns.Uuid()
38
- name = clearskies.columns.String(
39
- validators=[
40
- validators.Required(),
41
- validators.MaximumLength(50),
42
- ]
43
- )
44
- email = columns.Email(validators=[validators.Unique()])
45
- some_number = columns.Integer()
46
- expires_at = columns.Date()
47
- created_at = columns.Created()
48
-
49
-
50
- wsgi = clearskies.contexts.WsgiRef(
51
- clearskies.endpoints.Create(
52
- MyAwesomeModel,
53
- readable_column_names=["id", "name", "email", "some_number", "expires_at", "created_at"],
54
- writeable_column_names=["name", "email", "some_number", "expires_at"],
55
- ),
56
- )
57
- wsgi()
58
- ```
59
-
60
- The following shows how to invoke it, and demonstrates the strict input validation that happens as part of the
61
- process:
62
-
63
- ```bash
64
- $ curl 'http://localhost:8080/' -d '{"name":"Example", "email":"test@example.com","some_number":5,"expires_at":"2024-12-31"}' | jq
65
- {
66
- "status": "success",
67
- "error": "",
68
- "data": {
69
- "id": "74eda1c6-fe66-44ec-9246-758d16e1a304",
70
- "name": "Example",
71
- "email": "test@example.com",
72
- "some_number": 5,
73
- "expires_at": "2024-12-31",
74
- "created_at": "2025-05-23T16:36:30+00:00"
75
- },
76
- "pagination": {},
77
- "input_errors": {}
78
- }
79
-
80
- $ curl 'http://localhost:8080/' -d '{"name":"", "email":"test@example.com","some_number":"asdf","expires_at":"not-a-date", "not_a_column": "sup"}' | jq
81
- {
82
- "status": "input_errors",
83
- "error": "",
84
- "data": [],
85
- "pagination": {},
86
- "input_errors": {
87
- "name": "'name' is required.",
88
- "email": "Invalid value for 'email': the given value already exists, and must be unique.",
89
- "some_number": "value should be an integer",
90
- "expires_at": "given value did not appear to be a valid date",
91
- "not_a_column": "Input column not_a_column is not an allowed input column."
92
- }
93
- }
94
- ```
95
-
96
- The first call successfully creates a new record. The second call fails with a variety of error messages:
97
-
98
- 1. A name wasn't provided by the model class marked this as required
99
- 2. We provided the same email address again, but this column is marked as unique
100
- 3. The number provided in `some_number` wasn't actually a number
101
- 4. The provided value for `expires_at` wasn't actually a date.
102
- 5. We provided an extra column (`not_a_column`) that wasn't in the list of allowed columns.
103
- """
104
-
105
- @clearskies.decorators.parameters_to_properties
106
- def __init__(
107
- self,
108
- model_class: type[Model],
109
- writeable_column_names: list[str],
110
- readable_column_names: list[str],
111
- input_validation_callable: Callable | None = None,
112
- include_routing_data_in_request_data: bool = False,
113
- url: str = "",
114
- request_methods: list[str] = ["POST"],
115
- response_headers: list[str | Callable[..., list[str]]] = [],
116
- output_map: Callable[..., dict[str, Any]] | None = None,
117
- output_schema: clearskies.Schema | None = None,
118
- column_overrides: dict[str, Column] = {},
119
- internal_casing: str = "snake_case",
120
- external_casing: str = "snake_case",
121
- security_headers: list[SecurityHeader] = [],
122
- description: str = "",
123
- authentication: authentication.Authentication = authentication.Public(),
124
- authorization: authentication.Authorization = authentication.Authorization(),
125
- ):
126
- # a bit weird, but we have to do this because the default in the above definition is different than
127
- # the default set on the request_mehtods config in the bsae endpoint class. parameters_to_properties will copy
128
- # parameters to properties, but only for things set by the developer - not for default values set in the kwarg
129
- # definitions. Therefore, we always set it here to make sure we user our default, not the one in the base class.
130
- self.request_methods = request_methods
131
-
132
- # 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
133
- # just stores them in parameters, which we have already done. However, the parent does do some extra initialization stuff that we need,
134
- # which is why we have to call the parent.
135
- super().__init__()
136
-
137
- def handle(self, input_output: InputOutput) -> Any:
138
- request_data = self.get_request_data(input_output)
139
- if not request_data and input_output.has_body():
140
- raise clearskies.exceptions.ClientError("Request body was not valid JSON")
141
- self.validate_input_against_schema(request_data, input_output, self.model_class)
142
- new_model = self.model.create(request_data, columns=self.columns)
143
- return self.success(input_output, self.model_as_json(new_model, input_output))
144
-
145
- def documentation(self) -> list[autodoc.request.Request]:
146
- output_schema = self.model_class
147
- nice_model = string.camel_case_to_words(output_schema.__name__)
148
-
149
- schema_model_name = string.camel_case_to_snake_case(output_schema.__name__)
150
- output_data_schema = self.documentation_data_schema(output_schema, self.readable_column_names)
151
- output_autodoc = (
152
- autodoc.schema.Object(
153
- self.auto_case_internal_column_name("data"), children=output_data_schema, model_name=schema_model_name
154
- ),
155
- )
156
-
157
- authentication = self.authentication
158
- standard_error_responses = [self.documentation_input_error_response()]
159
- if not getattr(authentication, "is_public", False):
160
- standard_error_responses.append(self.documentation_access_denied_response())
161
- if getattr(authentication, "can_authorize", False):
162
- standard_error_responses.append(self.documentation_unauthorized_response())
163
-
164
- return [
165
- autodoc.request.Request(
166
- self.description,
167
- [
168
- self.documentation_success_response(
169
- output_autodoc, # type: ignore
170
- description=self.description,
171
- ),
172
- *standard_error_responses,
173
- self.documentation_generic_error_response(),
174
- ],
175
- relative_path=self.url,
176
- request_methods=self.request_methods,
177
- parameters=[
178
- *self.documentation_request_parameters(),
179
- *self.standard_url_parameters(),
180
- ],
181
- root_properties={
182
- "security": self.documentation_request_security(),
183
- },
184
- ),
185
- ]
186
-
187
- def documentation_request_parameters(self) -> list[autodoc.request.Parameter]:
188
- return [
189
- *self.standard_json_request_parameters(self.model_class),
190
- *(self.standard_url_request_parameters() if self.include_routing_data_in_request_data else []),
191
- ]
192
-
193
- def documentation_models(self) -> dict[str, autodoc.schema.Schema]:
194
- output_schema = self.output_schema if self.output_schema else self.model_class
195
- schema_model_name = string.camel_case_to_snake_case(output_schema.__name__)
196
-
197
- return {
198
- schema_model_name: autodoc.schema.Object(
199
- self.auto_case_internal_column_name("data"),
200
- children=self.documentation_data_schema(output_schema, self.readable_column_names),
201
- ),
202
- }