clear-skies 2.0.7__py3-none-any.whl → 2.0.9__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.
- {clear_skies-2.0.7.dist-info → clear_skies-2.0.9.dist-info}/METADATA +1 -1
- clear_skies-2.0.9.dist-info/RECORD +256 -0
- clearskies/__init__.py +2 -2
- clearskies/authentication/authentication.py +1 -3
- clearskies/authentication/authorization.py +12 -5
- clearskies/authentication/authorization_pass_through.py +5 -3
- clearskies/authentication/jwks.py +25 -23
- clearskies/authentication/secret_bearer.py +15 -17
- clearskies/autodoc/schema/schema.py +1 -1
- clearskies/backends/api_backend.py +50 -56
- clearskies/backends/backend.py +14 -14
- clearskies/backends/cursor_backend.py +17 -23
- clearskies/backends/memory_backend.py +27 -30
- clearskies/backends/secrets_backend.py +13 -18
- clearskies/column.py +44 -56
- clearskies/columns/audit.py +14 -13
- clearskies/columns/belongs_to_id.py +10 -15
- clearskies/columns/belongs_to_model.py +6 -9
- clearskies/columns/belongs_to_self.py +13 -9
- clearskies/columns/boolean.py +13 -16
- clearskies/columns/category_tree.py +9 -11
- clearskies/columns/category_tree_children.py +2 -3
- clearskies/columns/category_tree_descendants.py +1 -1
- clearskies/columns/created.py +8 -11
- clearskies/columns/created_by_authorization_data.py +7 -9
- clearskies/columns/created_by_header.py +12 -8
- clearskies/columns/created_by_ip.py +6 -8
- clearskies/columns/created_by_routing_data.py +12 -7
- clearskies/columns/created_by_user_agent.py +6 -9
- clearskies/columns/date.py +12 -14
- clearskies/columns/datetime.py +19 -17
- clearskies/columns/email.py +3 -1
- clearskies/columns/float.py +10 -14
- clearskies/columns/has_many.py +8 -10
- clearskies/columns/has_many_self.py +13 -7
- clearskies/columns/has_one.py +2 -0
- clearskies/columns/integer.py +9 -11
- clearskies/columns/json.py +10 -12
- clearskies/columns/many_to_many_ids.py +14 -16
- clearskies/columns/many_to_many_ids_with_data.py +16 -16
- clearskies/columns/many_to_many_models.py +5 -7
- clearskies/columns/many_to_many_pivots.py +3 -5
- clearskies/columns/phone.py +12 -9
- clearskies/columns/select.py +12 -9
- clearskies/columns/string.py +1 -1
- clearskies/columns/timestamp.py +15 -15
- clearskies/columns/updated.py +8 -10
- clearskies/columns/uuid.py +7 -10
- clearskies/configs/__init__.py +8 -0
- clearskies/configs/any.py +2 -0
- clearskies/configs/any_dict.py +2 -0
- clearskies/configs/any_dict_or_callable.py +2 -0
- clearskies/configs/boolean.py +2 -0
- clearskies/configs/boolean_or_callable.py +2 -0
- clearskies/configs/callable_config.py +2 -0
- clearskies/configs/config.py +2 -0
- clearskies/configs/datetime.py +2 -0
- clearskies/configs/datetime_or_callable.py +2 -0
- clearskies/configs/email.py +10 -0
- clearskies/configs/email_list.py +17 -0
- clearskies/configs/email_list_or_callable.py +19 -0
- clearskies/configs/email_or_email_list_or_callable.py +59 -0
- clearskies/configs/float.py +2 -0
- clearskies/configs/float_or_callable.py +2 -0
- clearskies/configs/integer.py +2 -0
- clearskies/configs/integer_or_callable.py +2 -0
- clearskies/configs/list_any_dict.py +2 -0
- clearskies/configs/list_any_dict_or_callable.py +2 -0
- clearskies/configs/model_column.py +2 -0
- clearskies/configs/model_columns.py +2 -0
- clearskies/configs/model_destination_name.py +2 -1
- clearskies/configs/model_to_id_column.py +2 -0
- clearskies/configs/readable_model_column.py +2 -0
- clearskies/configs/readable_model_columns.py +2 -0
- clearskies/configs/searchable_model_columns.py +2 -0
- clearskies/configs/select.py +2 -0
- clearskies/configs/select_list.py +2 -0
- clearskies/configs/string.py +4 -2
- clearskies/configs/string_dict.py +2 -0
- clearskies/configs/string_list.py +17 -2
- clearskies/configs/string_list_or_callable.py +13 -0
- clearskies/configs/timedelta.py +2 -0
- clearskies/configs/timezone.py +2 -0
- clearskies/configs/url.py +2 -0
- clearskies/configs/writeable_model_column.py +2 -0
- clearskies/configs/writeable_model_columns.py +2 -0
- clearskies/configurable.py +2 -0
- clearskies/contexts/cli.py +9 -1
- clearskies/contexts/context.py +13 -14
- clearskies/contexts/wsgi.py +12 -10
- clearskies/contexts/wsgi_ref.py +12 -6
- clearskies/decorators.py +1 -1
- clearskies/decorators.pyi +10 -0
- clearskies/di/di.py +7 -6
- clearskies/di/inject/by_class.py +2 -0
- clearskies/di/inject/by_name.py +2 -0
- clearskies/di/inject/di.py +2 -0
- clearskies/di/inject/environment.py +1 -1
- clearskies/di/inject/now.py +2 -0
- clearskies/di/inject/requests.py +2 -0
- clearskies/di/inject/secrets.py +2 -2
- clearskies/di/inject/utcnow.py +2 -0
- clearskies/di/inject/uuid.py +2 -2
- clearskies/end.py +45 -7
- clearskies/endpoint.py +43 -59
- clearskies/endpoint_group.py +15 -18
- clearskies/endpoints/advanced_search.py +19 -26
- clearskies/endpoints/callable.py +10 -16
- clearskies/endpoints/create.py +6 -10
- clearskies/endpoints/delete.py +5 -11
- clearskies/endpoints/get.py +11 -15
- clearskies/endpoints/health_check.py +9 -11
- clearskies/endpoints/list.py +29 -36
- clearskies/endpoints/restful_api.py +43 -53
- clearskies/endpoints/schema.py +14 -18
- clearskies/endpoints/simple_search.py +5 -12
- clearskies/endpoints/update.py +6 -11
- clearskies/environment.py +2 -0
- clearskies/input_outputs/cli.py +2 -0
- clearskies/input_outputs/headers.py +2 -0
- clearskies/input_outputs/input_output.py +15 -15
- clearskies/input_outputs/programmatic.py +2 -2
- clearskies/input_outputs/wsgi.py +2 -2
- clearskies/model.py +120 -25
- clearskies/query/query.py +1 -4
- clearskies/secrets/__init__.py +2 -1
- clearskies/secrets/akeyless.py +16 -11
- clearskies/secrets/secrets.py +7 -2
- clearskies/security_header.py +4 -2
- clearskies/security_headers/cache_control.py +15 -14
- clearskies/security_headers/cors.py +10 -9
- clearskies/security_headers/csp.py +25 -24
- clearskies/security_headers/hsts.py +6 -5
- clearskies/typing.py +1 -1
- clearskies/validator.py +5 -6
- clearskies/validators/after_column.py +6 -7
- clearskies/validators/before_column.py +2 -0
- clearskies/validators/in_the_future.py +5 -8
- clearskies/validators/in_the_future_at_least.py +2 -0
- clearskies/validators/in_the_future_at_most.py +2 -0
- clearskies/validators/in_the_past.py +5 -8
- clearskies/validators/in_the_past_at_least.py +2 -0
- clearskies/validators/in_the_past_at_most.py +2 -0
- clearskies/validators/maximum_length.py +4 -5
- clearskies/validators/maximum_value.py +4 -4
- clearskies/validators/minimum_length.py +4 -4
- clearskies/validators/minimum_value.py +4 -4
- clearskies/validators/required.py +2 -4
- clearskies/validators/timedelta.py +8 -9
- clearskies/validators/unique.py +2 -3
- clear_skies-2.0.7.dist-info/RECORD +0 -251
- {clear_skies-2.0.7.dist-info → clear_skies-2.0.9.dist-info}/WHEEL +0 -0
- {clear_skies-2.0.7.dist-info → clear_skies-2.0.9.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,19 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from collections import OrderedDict
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Type
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
import clearskies.exceptions
|
|
9
|
-
from clearskies import authentication, autodoc, typing
|
|
5
|
+
from clearskies import exceptions
|
|
10
6
|
from clearskies.endpoints.simple_search import SimpleSearch
|
|
11
|
-
from clearskies.functional import string
|
|
12
|
-
from clearskies.input_outputs import InputOutput
|
|
13
7
|
|
|
14
8
|
if TYPE_CHECKING:
|
|
15
|
-
from clearskies import
|
|
16
|
-
from clearskies.model import Model
|
|
9
|
+
from clearskies import Model, autodoc
|
|
17
10
|
|
|
18
11
|
|
|
19
12
|
class AdvancedSearch(SimpleSearch):
|
|
@@ -350,12 +343,12 @@ class AdvancedSearch(SimpleSearch):
|
|
|
350
343
|
if pagination_data:
|
|
351
344
|
error = self.model.validate_pagination_data(pagination_data, self.auto_case_internal_column_name)
|
|
352
345
|
if error:
|
|
353
|
-
raise
|
|
346
|
+
raise exceptions.ClientError(error)
|
|
354
347
|
if query_parameters:
|
|
355
|
-
raise
|
|
348
|
+
raise exceptions.ClientError("Query parameters were found but are not supported.")
|
|
356
349
|
for key in request_data.keys():
|
|
357
350
|
if key not in self.allowed_request_keys:
|
|
358
|
-
raise
|
|
351
|
+
raise exceptions.ClientError(
|
|
359
352
|
f"Invalid request parameter found in request body: '{key}'. Expected parameters: "
|
|
360
353
|
+ ", ".join([self.auto_case_internal_column_name(key) for key in self.allowed_request_keys])
|
|
361
354
|
)
|
|
@@ -363,7 +356,7 @@ class AdvancedSearch(SimpleSearch):
|
|
|
363
356
|
sort_key_name = self.auto_case_internal_column_name("sort")
|
|
364
357
|
sort = request_data.get(sort_key_name, [])
|
|
365
358
|
if not isinstance(sort, list):
|
|
366
|
-
raise
|
|
359
|
+
raise exceptions.ClientError(
|
|
367
360
|
f"'{sort_key_name}' property in request body should be a list, but I found a value of type "
|
|
368
361
|
+ sort.__class__.__name
|
|
369
362
|
)
|
|
@@ -372,25 +365,25 @@ class AdvancedSearch(SimpleSearch):
|
|
|
372
365
|
direction_key_name = self.auto_case_internal_column_name("direction")
|
|
373
366
|
for index, sort_entry in enumerate(sort):
|
|
374
367
|
if not isinstance(sort_entry, dict):
|
|
375
|
-
raise
|
|
368
|
+
raise exceptions.ClientError(
|
|
376
369
|
f"'{sort_key_name}' should be a list of dictionaries, but entry #{index + 1} is a value of type '{sort_entry.__class__.__name}', not a dict"
|
|
377
370
|
)
|
|
378
371
|
for key_name in [column_key_name, direction_key_name]:
|
|
379
372
|
if not sort_entry.get(key_name):
|
|
380
|
-
raise
|
|
373
|
+
raise exceptions.ClientError(
|
|
381
374
|
f"Each entry in the sort list should contain both '{column_key_name}' and '{direction_key_name}' but entry #{index + 1} is missing '{key_name}'"
|
|
382
375
|
)
|
|
383
376
|
if not isinstance(sort_entry[key_name], str):
|
|
384
|
-
raise
|
|
377
|
+
raise exceptions.ClientError(
|
|
385
378
|
f"{key_name}' must be a string, but for entry #{index + 1} it is a value of type "
|
|
386
379
|
+ sort_entry[key_name].__class__.__name__
|
|
387
380
|
)
|
|
388
381
|
if sort_entry[direction_key_name].lower() not in ["asc", "desc"]:
|
|
389
|
-
raise
|
|
382
|
+
raise exceptions.ClientError(
|
|
390
383
|
f"{direction_key_name}' must be either 'ASC' or 'DESC', but a different value was found for entry #{index + 1}"
|
|
391
384
|
)
|
|
392
385
|
if self.auto_case_column_name(sort_entry[column_key_name], False) not in self.sortable_column_names:
|
|
393
|
-
raise
|
|
386
|
+
raise exceptions.ClientError(
|
|
394
387
|
f"Invalid sort column for entry #{index + 1}. Allowed values are: "
|
|
395
388
|
+ ", ".join(
|
|
396
389
|
[
|
|
@@ -402,7 +395,7 @@ class AdvancedSearch(SimpleSearch):
|
|
|
402
395
|
where_key_name = self.auto_case_internal_column_name("where")
|
|
403
396
|
where = request_data.get(where_key_name, [])
|
|
404
397
|
if not isinstance(where, list):
|
|
405
|
-
raise
|
|
398
|
+
raise exceptions.ClientError(
|
|
406
399
|
f"'{where_key_name}' property in request body should be a list, but I found a value of type "
|
|
407
400
|
+ where.__class__.__name
|
|
408
401
|
)
|
|
@@ -412,21 +405,21 @@ class AdvancedSearch(SimpleSearch):
|
|
|
412
405
|
value_key_name = self.auto_case_internal_column_name("value")
|
|
413
406
|
for index, where_entry in enumerate(where):
|
|
414
407
|
if not isinstance(where_entry, dict):
|
|
415
|
-
raise
|
|
408
|
+
raise exceptions.ClientError(
|
|
416
409
|
f"'{where_key_name}' should be a list of dictionaries, but entry #{index + 1} is a value of type '{where_entry.__class__.__name}', not a dict"
|
|
417
410
|
)
|
|
418
411
|
for key_name in [column_key_name, operator_key_name, value_key_name]:
|
|
419
412
|
if key_name not in where_entry:
|
|
420
|
-
raise
|
|
413
|
+
raise exceptions.ClientError(
|
|
421
414
|
f"Each entry in the where list should contain '{column_key_name}', '{operator_key_name}', and '{value_key_name}', but entry #{index + 1} is missing '{key_name}'"
|
|
422
415
|
)
|
|
423
416
|
if key_name != value_key_name and not isinstance(where_entry[key_name], str):
|
|
424
|
-
raise
|
|
417
|
+
raise exceptions.ClientError(
|
|
425
418
|
f"{key_name}' must be a string, but for entry #{index + 1} it is a value of type "
|
|
426
419
|
+ sort_entry[key_name].__class__.__name__
|
|
427
420
|
)
|
|
428
421
|
if where_entry[column_key_name] not in self.searchable_column_names:
|
|
429
|
-
raise
|
|
422
|
+
raise exceptions.ClientError(
|
|
430
423
|
f"Invalid where column for entry #{index + 1}. Allowed values are: "
|
|
431
424
|
+ ", ".join(
|
|
432
425
|
[
|
|
@@ -462,12 +455,12 @@ class AdvancedSearch(SimpleSearch):
|
|
|
462
455
|
value if operator != "in" else value[0], where_entry[operator_key_name]
|
|
463
456
|
)
|
|
464
457
|
if error_allowed_operators:
|
|
465
|
-
raise
|
|
458
|
+
raise exceptions.ClientError(
|
|
466
459
|
f"Invalid operator for entry #{index + 1}. Allowed operators are: "
|
|
467
460
|
+ ", ".join(column.allowed_search_operators(relationship_reference=column_name))
|
|
468
461
|
)
|
|
469
462
|
if error:
|
|
470
|
-
raise
|
|
463
|
+
raise exceptions.ClientError(f"Invalid search value for entry #{index + 1}: {error}")
|
|
471
464
|
|
|
472
465
|
def configure_model_from_request_data(
|
|
473
466
|
self,
|
clearskies/endpoints/callable.py
CHANGED
|
@@ -1,22 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import inspect
|
|
4
|
-
from collections import OrderedDict
|
|
5
3
|
from typing import TYPE_CHECKING, Any
|
|
6
4
|
from typing import Callable as CallableType
|
|
7
5
|
|
|
8
|
-
import
|
|
9
|
-
import clearskies.exceptions
|
|
10
|
-
from clearskies import authentication, autodoc, typing
|
|
6
|
+
from clearskies import authentication, autodoc, configs, decorators, exceptions
|
|
11
7
|
from clearskies.endpoint import Endpoint
|
|
12
8
|
from clearskies.functional import string
|
|
13
|
-
from clearskies.input_outputs import InputOutput
|
|
14
9
|
|
|
15
10
|
if TYPE_CHECKING:
|
|
16
|
-
from clearskies import Column, SecurityHeader
|
|
11
|
+
from clearskies import Column, Model, Schema, SecurityHeader
|
|
17
12
|
from clearskies.authentication import Authentication, Authorization
|
|
18
|
-
from clearskies.
|
|
19
|
-
from clearskies.schema import Schema
|
|
13
|
+
from clearskies.input_outputs import InputOutput
|
|
20
14
|
|
|
21
15
|
|
|
22
16
|
class Callable(Endpoint):
|
|
@@ -114,7 +108,7 @@ class Callable(Endpoint):
|
|
|
114
108
|
"""
|
|
115
109
|
The callable to execute when the endpoint is invoked
|
|
116
110
|
"""
|
|
117
|
-
to_call =
|
|
111
|
+
to_call = configs.Callable(default=None)
|
|
118
112
|
|
|
119
113
|
"""
|
|
120
114
|
A schema that describes the expected input from the client.
|
|
@@ -172,7 +166,7 @@ class Callable(Endpoint):
|
|
|
172
166
|
```
|
|
173
167
|
|
|
174
168
|
"""
|
|
175
|
-
input_schema =
|
|
169
|
+
input_schema = configs.Schema(default=None)
|
|
176
170
|
|
|
177
171
|
"""
|
|
178
172
|
Whether or not the return value is meant to be wrapped up in the standard clearskies response schema.
|
|
@@ -232,20 +226,20 @@ class Callable(Endpoint):
|
|
|
232
226
|
Note that you can also return strings this way instead of objects/JSON.
|
|
233
227
|
|
|
234
228
|
"""
|
|
235
|
-
return_standard_response =
|
|
229
|
+
return_standard_response = configs.Boolean(default=True)
|
|
236
230
|
|
|
237
231
|
"""
|
|
238
232
|
Set to true if the callable will be returning multiple records (used when building the auto-documentation)
|
|
239
233
|
"""
|
|
240
|
-
return_records =
|
|
234
|
+
return_records = configs.Boolean(default=False)
|
|
241
235
|
|
|
242
|
-
@
|
|
236
|
+
@decorators.parameters_to_properties
|
|
243
237
|
def __init__(
|
|
244
238
|
self,
|
|
245
239
|
to_call: CallableType,
|
|
246
240
|
url: str = "",
|
|
247
241
|
request_methods: list[str] = ["GET"],
|
|
248
|
-
model_class: type[
|
|
242
|
+
model_class: type[Model] | None = None,
|
|
249
243
|
readable_column_names: list[str] = [],
|
|
250
244
|
writeable_column_names: list[str] = [],
|
|
251
245
|
input_schema: type[Schema] | None = None,
|
|
@@ -281,7 +275,7 @@ class Callable(Endpoint):
|
|
|
281
275
|
else:
|
|
282
276
|
input_errors = self.find_input_errors_from_callable(input_output.request_data, input_output)
|
|
283
277
|
if input_errors:
|
|
284
|
-
raise
|
|
278
|
+
raise exceptions.InputErrors(input_errors)
|
|
285
279
|
response = self.di.call_function(self.to_call, **input_output.get_context_for_callables())
|
|
286
280
|
|
|
287
281
|
if not self.return_standard_response:
|
clearskies/endpoints/create.py
CHANGED
|
@@ -1,18 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import inspect
|
|
4
|
-
from collections import OrderedDict
|
|
5
3
|
from typing import TYPE_CHECKING, Any, Callable
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
import clearskies.exceptions
|
|
9
|
-
from clearskies import authentication, autodoc, typing
|
|
5
|
+
from clearskies import authentication, autodoc, decorators, exceptions
|
|
10
6
|
from clearskies.endpoint import Endpoint
|
|
11
7
|
from clearskies.functional import string
|
|
12
|
-
from clearskies.input_outputs import InputOutput
|
|
13
8
|
|
|
14
9
|
if TYPE_CHECKING:
|
|
15
|
-
from clearskies import Column, SecurityHeader
|
|
10
|
+
from clearskies import Column, Schema, SecurityHeader
|
|
11
|
+
from clearskies.input_outputs import InputOutput
|
|
16
12
|
from clearskies.model import Model
|
|
17
13
|
|
|
18
14
|
|
|
@@ -102,7 +98,7 @@ class Create(Endpoint):
|
|
|
102
98
|
5. We provided an extra column (`not_a_column`) that wasn't in the list of allowed columns.
|
|
103
99
|
"""
|
|
104
100
|
|
|
105
|
-
@
|
|
101
|
+
@decorators.parameters_to_properties
|
|
106
102
|
def __init__(
|
|
107
103
|
self,
|
|
108
104
|
model_class: type[Model],
|
|
@@ -114,7 +110,7 @@ class Create(Endpoint):
|
|
|
114
110
|
request_methods: list[str] = ["POST"],
|
|
115
111
|
response_headers: list[str | Callable[..., list[str]]] = [],
|
|
116
112
|
output_map: Callable[..., dict[str, Any]] | None = None,
|
|
117
|
-
output_schema:
|
|
113
|
+
output_schema: Schema | None = None,
|
|
118
114
|
column_overrides: dict[str, Column] = {},
|
|
119
115
|
internal_casing: str = "snake_case",
|
|
120
116
|
external_casing: str = "snake_case",
|
|
@@ -137,7 +133,7 @@ class Create(Endpoint):
|
|
|
137
133
|
def handle(self, input_output: InputOutput) -> Any:
|
|
138
134
|
request_data = self.get_request_data(input_output)
|
|
139
135
|
if not request_data and input_output.has_body():
|
|
140
|
-
raise
|
|
136
|
+
raise exceptions.ClientError("Request body was not valid JSON")
|
|
141
137
|
self.validate_input_against_schema(request_data, input_output, self.model_class)
|
|
142
138
|
new_model = self.model.create(request_data, columns=self.columns)
|
|
143
139
|
return self.success(input_output, self.model_as_json(new_model, input_output))
|
clearskies/endpoints/delete.py
CHANGED
|
@@ -1,19 +1,13 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from collections import OrderedDict
|
|
5
|
-
from typing import TYPE_CHECKING, Any, Callable, Type
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
import clearskies.exceptions
|
|
9
|
-
from clearskies import authentication, autodoc, typing
|
|
5
|
+
from clearskies import authentication, autodoc, decorators
|
|
10
6
|
from clearskies.endpoints.get import Get
|
|
11
|
-
from clearskies.functional import routing, string
|
|
12
|
-
from clearskies.input_outputs import InputOutput
|
|
13
7
|
|
|
14
8
|
if TYPE_CHECKING:
|
|
15
|
-
from clearskies import SecurityHeader
|
|
16
|
-
from clearskies.
|
|
9
|
+
from clearskies import Model, SecurityHeader, typing
|
|
10
|
+
from clearskies.input_outputs import InputOutput
|
|
17
11
|
|
|
18
12
|
|
|
19
13
|
class Delete(Get):
|
|
@@ -73,7 +67,7 @@ class Delete(Get):
|
|
|
73
67
|
```
|
|
74
68
|
"""
|
|
75
69
|
|
|
76
|
-
@
|
|
70
|
+
@decorators.parameters_to_properties
|
|
77
71
|
def __init__(
|
|
78
72
|
self,
|
|
79
73
|
model_class: type[Model],
|
clearskies/endpoints/get.py
CHANGED
|
@@ -1,19 +1,15 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from
|
|
6
|
-
|
|
7
|
-
import clearskies.configs
|
|
8
|
-
import clearskies.exceptions
|
|
9
|
-
from clearskies import authentication, autodoc, typing
|
|
10
|
-
from clearskies.authentication import Authentication, Authorization
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
|
+
|
|
5
|
+
from clearskies import autodoc, configs, decorators, exceptions
|
|
6
|
+
from clearskies.authentication import Authentication, Authorization, Public
|
|
11
7
|
from clearskies.endpoint import Endpoint
|
|
12
8
|
from clearskies.functional import routing, string
|
|
13
|
-
from clearskies.input_outputs import InputOutput
|
|
14
9
|
|
|
15
10
|
if TYPE_CHECKING:
|
|
16
|
-
from clearskies import Column, Schema, SecurityHeader
|
|
11
|
+
from clearskies import Column, Schema, SecurityHeader, typing
|
|
12
|
+
from clearskies.input_outputs import InputOutput
|
|
17
13
|
from clearskies.model import Model
|
|
18
14
|
|
|
19
15
|
|
|
@@ -157,9 +153,9 @@ class Get(Endpoint):
|
|
|
157
153
|
}
|
|
158
154
|
```
|
|
159
155
|
"""
|
|
160
|
-
record_lookup_column_name =
|
|
156
|
+
record_lookup_column_name = configs.ReadableModelColumn("model_class", default=None)
|
|
161
157
|
|
|
162
|
-
@
|
|
158
|
+
@decorators.parameters_to_properties
|
|
163
159
|
def __init__(
|
|
164
160
|
self,
|
|
165
161
|
model_class: type[Model],
|
|
@@ -177,8 +173,8 @@ class Get(Endpoint):
|
|
|
177
173
|
description: str = "",
|
|
178
174
|
where: typing.condition | list[typing.condition] = [],
|
|
179
175
|
joins: typing.join | list[typing.join] = [],
|
|
180
|
-
authentication: Authentication =
|
|
181
|
-
authorization: Authorization =
|
|
176
|
+
authentication: Authentication = Public(),
|
|
177
|
+
authorization: Authorization = Authorization(),
|
|
182
178
|
):
|
|
183
179
|
try:
|
|
184
180
|
# we will set the value for this if it isn't already set, and the easiest way is to just fetch it and see if it blows up
|
|
@@ -211,7 +207,7 @@ class Get(Endpoint):
|
|
|
211
207
|
self.record_lookup_column_name + "=" + lookup_column_value
|
|
212
208
|
)
|
|
213
209
|
if not model:
|
|
214
|
-
raise
|
|
210
|
+
raise exceptions.NotFound("Not Found")
|
|
215
211
|
return model
|
|
216
212
|
|
|
217
213
|
def handle(self, input_output: InputOutput) -> Any:
|
|
@@ -1,14 +1,12 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
from typing import Any, Callable, Type
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
5
4
|
|
|
6
|
-
import
|
|
7
|
-
import clearskies.exceptions
|
|
8
|
-
from clearskies import autodoc, typing
|
|
5
|
+
from clearskies import autodoc, configs, decorators
|
|
9
6
|
from clearskies.endpoint import Endpoint
|
|
10
|
-
|
|
11
|
-
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from clearskies.input_outputs import InputOutput
|
|
12
10
|
|
|
13
11
|
|
|
14
12
|
class HealthCheck(Endpoint):
|
|
@@ -80,7 +78,7 @@ class HealthCheck(Endpoint):
|
|
|
80
78
|
If any exceptions are raised when building the dependency injection parameters, the health check will return
|
|
81
79
|
failure.
|
|
82
80
|
"""
|
|
83
|
-
dependency_injection_names =
|
|
81
|
+
dependency_injection_names = configs.StringList(default=[])
|
|
84
82
|
|
|
85
83
|
"""
|
|
86
84
|
A list of classes to build with the dependency injection system.
|
|
@@ -104,7 +102,7 @@ class HealthCheck(Endpoint):
|
|
|
104
102
|
wsgi()
|
|
105
103
|
```
|
|
106
104
|
"""
|
|
107
|
-
classes_to_build =
|
|
105
|
+
classes_to_build = configs.Any(default=[])
|
|
108
106
|
|
|
109
107
|
"""
|
|
110
108
|
A list of callables to invoke.
|
|
@@ -128,9 +126,9 @@ class HealthCheck(Endpoint):
|
|
|
128
126
|
wsgi()
|
|
129
127
|
```
|
|
130
128
|
"""
|
|
131
|
-
callables =
|
|
129
|
+
callables = configs.Any(default=[])
|
|
132
130
|
|
|
133
|
-
@
|
|
131
|
+
@decorators.parameters_to_properties
|
|
134
132
|
def __init__(
|
|
135
133
|
self,
|
|
136
134
|
dependency_injection_names: list[str] = [],
|
clearskies/endpoints/list.py
CHANGED
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import inspect
|
|
4
|
-
from collections import OrderedDict
|
|
5
3
|
from typing import TYPE_CHECKING, Any, Callable
|
|
6
4
|
|
|
7
|
-
import
|
|
8
|
-
import clearskies.exceptions
|
|
9
|
-
from clearskies import authentication, autodoc, typing
|
|
5
|
+
from clearskies import authentication, autodoc, configs, decorators, exceptions
|
|
10
6
|
from clearskies.endpoint import Endpoint
|
|
11
7
|
from clearskies.functional import string
|
|
12
|
-
from clearskies.input_outputs import InputOutput
|
|
13
8
|
|
|
14
9
|
if TYPE_CHECKING:
|
|
15
|
-
from clearskies import Schema, SecurityHeader
|
|
16
|
-
from clearskies.
|
|
17
|
-
from clearskies.model import Model
|
|
10
|
+
from clearskies import Column, Model, Schema, SecurityHeader, typing
|
|
11
|
+
from clearskies.input_outputs import InputOutput
|
|
18
12
|
|
|
19
13
|
|
|
20
14
|
class List(Endpoint):
|
|
@@ -35,6 +29,7 @@ class List(Endpoint):
|
|
|
35
29
|
```python
|
|
36
30
|
import clearskies
|
|
37
31
|
|
|
32
|
+
|
|
38
33
|
class User(clearskies.Model):
|
|
39
34
|
id_column_name = "id"
|
|
40
35
|
backend = clearskies.backends.MemoryBackend()
|
|
@@ -154,35 +149,33 @@ class List(Endpoint):
|
|
|
154
149
|
"""
|
|
155
150
|
The default column to sort by.
|
|
156
151
|
"""
|
|
157
|
-
default_sort_column_name =
|
|
152
|
+
default_sort_column_name = configs.ModelColumn("model_class")
|
|
158
153
|
|
|
159
154
|
"""
|
|
160
155
|
The default sort direction (ASC or DESC).
|
|
161
156
|
"""
|
|
162
|
-
default_sort_direction =
|
|
157
|
+
default_sort_direction = configs.Select(["ASC", "DESC"], default="ASC")
|
|
163
158
|
|
|
164
159
|
"""
|
|
165
160
|
The number of records returned if the client doesn't specify a different number of records (default: 50).
|
|
166
161
|
"""
|
|
167
|
-
default_limit =
|
|
162
|
+
default_limit = configs.Integer(default=50)
|
|
168
163
|
|
|
169
164
|
"""
|
|
170
165
|
The maximum number of records the client is allowed to request (0 == no limit)
|
|
171
166
|
"""
|
|
172
|
-
maximum_limit =
|
|
167
|
+
maximum_limit = configs.Integer(default=200)
|
|
173
168
|
|
|
174
169
|
"""
|
|
175
170
|
A column to group by.
|
|
176
171
|
"""
|
|
177
|
-
group_by_column_name =
|
|
172
|
+
group_by_column_name = configs.ModelColumn("model_class")
|
|
178
173
|
|
|
179
|
-
readable_column_names =
|
|
180
|
-
sortable_column_names =
|
|
181
|
-
searchable_column_names =
|
|
182
|
-
"model_class", allow_relationship_references=True
|
|
183
|
-
)
|
|
174
|
+
readable_column_names = configs.ReadableModelColumns("model_class")
|
|
175
|
+
sortable_column_names = configs.ReadableModelColumns("model_class", allow_relationship_references=True)
|
|
176
|
+
searchable_column_names = configs.SearchableModelColumns("model_class", allow_relationship_references=True)
|
|
184
177
|
|
|
185
|
-
@
|
|
178
|
+
@decorators.parameters_to_properties
|
|
186
179
|
def __init__(
|
|
187
180
|
self,
|
|
188
181
|
model_class: type[Model],
|
|
@@ -236,16 +229,16 @@ class List(Endpoint):
|
|
|
236
229
|
def handle(self, input_output: InputOutput):
|
|
237
230
|
model = self.fetch_model_with_base_query(input_output)
|
|
238
231
|
if not input_output.request_data and input_output.has_body():
|
|
239
|
-
raise
|
|
232
|
+
raise exceptions.ClientError("Request body was not valid JSON")
|
|
240
233
|
if input_output.request_data and not isinstance(input_output.request_data, dict):
|
|
241
|
-
raise
|
|
234
|
+
raise exceptions.ClientError("When present, request body must be a JSON dictionary")
|
|
242
235
|
request_data = self.map_input_to_internal_names(input_output.request_data) # type: ignore
|
|
243
236
|
query_parameters = self.map_input_to_internal_names(input_output.query_parameters)
|
|
244
237
|
pagination_data = {}
|
|
245
238
|
for key in model.allowed_pagination_keys():
|
|
246
239
|
if key in request_data and key in query_parameters:
|
|
247
240
|
original_name = self.auto_case_internal_column_name(key)
|
|
248
|
-
raise
|
|
241
|
+
raise exceptions.ClientError(
|
|
249
242
|
f"Ambiguous request: key '{original_name}' is present in both the JSON body and URL data"
|
|
250
243
|
)
|
|
251
244
|
if key in request_data:
|
|
@@ -340,49 +333,49 @@ class List(Endpoint):
|
|
|
340
333
|
if pagination_data:
|
|
341
334
|
error = self.model.validate_pagination_data(pagination_data, self.auto_case_internal_column_name)
|
|
342
335
|
if error:
|
|
343
|
-
raise
|
|
336
|
+
raise exceptions.ClientError(error)
|
|
344
337
|
for key in request_data.keys():
|
|
345
338
|
if key not in self.allowed_request_keys:
|
|
346
|
-
raise
|
|
339
|
+
raise exceptions.ClientError(f"Invalid request parameter found in request body: '{key}'")
|
|
347
340
|
for key in query_parameters.keys():
|
|
348
341
|
if key not in self.allowed_request_keys:
|
|
349
|
-
raise
|
|
342
|
+
raise exceptions.ClientError(f"Invalid request parameter found in URL data: '{key}'")
|
|
350
343
|
if key in request_data:
|
|
351
|
-
raise
|
|
344
|
+
raise exceptions.ClientError(
|
|
352
345
|
f"Ambiguous request: '{key}' was found in both the request body and URL data"
|
|
353
346
|
)
|
|
354
347
|
self.validate_limit(request_data, query_parameters)
|
|
355
348
|
sort = self.from_either(request_data, query_parameters, "sort")
|
|
356
349
|
direction = self.from_either(request_data, query_parameters, "direction")
|
|
357
350
|
if sort and type(sort) != str:
|
|
358
|
-
raise
|
|
351
|
+
raise exceptions.ClientError("Invalid request: 'sort' should be a string")
|
|
359
352
|
if direction and type(direction) != str:
|
|
360
|
-
raise
|
|
353
|
+
raise exceptions.ClientError("Invalid request: 'direction' should be a string")
|
|
361
354
|
if sort or direction:
|
|
362
355
|
if (sort and not direction) or (direction and not sort):
|
|
363
|
-
raise
|
|
356
|
+
raise exceptions.ClientError(
|
|
364
357
|
"You must specify 'sort' and 'direction' together in the request - not just one of them"
|
|
365
358
|
)
|
|
366
359
|
if sort not in self.sortable_column_names:
|
|
367
|
-
raise
|
|
360
|
+
raise exceptions.ClientError(f"Invalid request: invalid sort column")
|
|
368
361
|
if direction.lower() not in ["asc", "desc"]:
|
|
369
|
-
raise
|
|
362
|
+
raise exceptions.ClientError("Invalid request: direction must be 'asc' or 'desc'")
|
|
370
363
|
self.check_search_in_request_data(request_data, query_parameters)
|
|
371
364
|
|
|
372
365
|
def validate_limit(self, request_data: dict[str, Any], query_parameters: dict[str, Any]) -> None:
|
|
373
366
|
limit = self.from_either(request_data, query_parameters, "limit")
|
|
374
367
|
if limit is not None and type(limit) != int and type(limit) != float and type(limit) != str:
|
|
375
|
-
raise
|
|
368
|
+
raise exceptions.ClientError("Invalid request: 'limit' should be an integer")
|
|
376
369
|
if limit:
|
|
377
370
|
try:
|
|
378
371
|
limit = int(limit)
|
|
379
372
|
except ValueError:
|
|
380
|
-
raise
|
|
373
|
+
raise exceptions.ClientError("Invalid request: 'limit' should be an integer")
|
|
381
374
|
if limit:
|
|
382
375
|
if limit > self.maximum_limit:
|
|
383
|
-
raise
|
|
376
|
+
raise exceptions.ClientError(f"Invalid request: 'limit' must be at most {self.maximum_limit}")
|
|
384
377
|
if limit < 0:
|
|
385
|
-
raise
|
|
378
|
+
raise exceptions.ClientError(f"Invalid request: 'limit' must be positive")
|
|
386
379
|
|
|
387
380
|
def check_search_in_request_data(self, request_data: dict[str, Any], query_parameters: dict[str, Any]):
|
|
388
381
|
return None
|