clear-skies 2.0.7__py3-none-any.whl → 2.0.8__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.8.dist-info}/METADATA +1 -1
- clear_skies-2.0.8.dist-info/RECORD +252 -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/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/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 +2 -0
- clearskies/configs/string_dict.py +2 -0
- clearskies/configs/string_list.py +2 -0
- clearskies/configs/string_list_or_callable.py +2 -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 +12 -10
- 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.8.dist-info}/WHEEL +0 -0
- {clear_skies-2.0.7.dist-info → clear_skies-2.0.8.dist-info}/licenses/LICENSE +0 -0
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
|
|
@@ -1,27 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import inspect
|
|
4
|
-
from collections import OrderedDict
|
|
5
4
|
from typing import TYPE_CHECKING, Any, Callable
|
|
6
5
|
|
|
7
|
-
import
|
|
8
|
-
import clearskies.decorators
|
|
9
|
-
import clearskies.exceptions
|
|
10
|
-
from clearskies import authentication, autodoc, typing
|
|
6
|
+
from clearskies import configs, decorators
|
|
11
7
|
from clearskies.authentication import Authentication, Authorization, Public
|
|
12
|
-
from clearskies.endpoint import Endpoint
|
|
13
8
|
from clearskies.endpoint_group import EndpointGroup
|
|
14
9
|
from clearskies.endpoints.create import Create
|
|
15
10
|
from clearskies.endpoints.delete import Delete
|
|
16
11
|
from clearskies.endpoints.get import Get
|
|
17
12
|
from clearskies.endpoints.simple_search import SimpleSearch
|
|
18
13
|
from clearskies.endpoints.update import Update
|
|
19
|
-
from clearskies.functional import string
|
|
20
|
-
from clearskies.input_outputs import InputOutput
|
|
21
14
|
|
|
22
15
|
if TYPE_CHECKING:
|
|
23
|
-
from clearskies import SecurityHeader
|
|
24
|
-
from clearskies.model import Column, Model, Schema
|
|
16
|
+
from clearskies import Column, Endpoint, Model, Schema, SecurityHeader
|
|
25
17
|
|
|
26
18
|
|
|
27
19
|
class RestfulApi(EndpointGroup):
|
|
@@ -190,7 +182,7 @@ class RestfulApi(EndpointGroup):
|
|
|
190
182
|
This defaults to `clearskies.endpoints.Create`. To disable the create operation all together,
|
|
191
183
|
set this to None.
|
|
192
184
|
"""
|
|
193
|
-
create_endpoint =
|
|
185
|
+
create_endpoint = configs.Endpoint(default=Create)
|
|
194
186
|
|
|
195
187
|
"""
|
|
196
188
|
The endpoint class to use for managing the delete operation.
|
|
@@ -198,7 +190,7 @@ class RestfulApi(EndpointGroup):
|
|
|
198
190
|
This defaults to `clearskies.endpoints.Delete`. To disable the delete operation all together,
|
|
199
191
|
set this to None.
|
|
200
192
|
"""
|
|
201
|
-
delete_endpoint =
|
|
193
|
+
delete_endpoint = configs.Endpoint(default=Delete)
|
|
202
194
|
|
|
203
195
|
"""
|
|
204
196
|
The endpoint class to use for managing the update operation.
|
|
@@ -206,7 +198,7 @@ class RestfulApi(EndpointGroup):
|
|
|
206
198
|
This defaults to `clearskies.endpoints.Update`. To disable the update operation all together,
|
|
207
199
|
set this to None.
|
|
208
200
|
"""
|
|
209
|
-
update_endpoint =
|
|
201
|
+
update_endpoint = configs.Endpoint(default=Update)
|
|
210
202
|
|
|
211
203
|
"""
|
|
212
204
|
The endpoint class to use to fetch individual records.
|
|
@@ -214,7 +206,7 @@ class RestfulApi(EndpointGroup):
|
|
|
214
206
|
This defaults to `clearskies.endpoints.Get`. To disable the get operation all together,
|
|
215
207
|
set this to None.
|
|
216
208
|
"""
|
|
217
|
-
get_endpoint =
|
|
209
|
+
get_endpoint = configs.Endpoint(default=Get)
|
|
218
210
|
|
|
219
211
|
"""
|
|
220
212
|
The endpoint class to use to list records.
|
|
@@ -222,78 +214,76 @@ class RestfulApi(EndpointGroup):
|
|
|
222
214
|
This defaults to `clearskies.endpoints.SimpleSearch`. To disable the list operation all together,
|
|
223
215
|
set this to None.
|
|
224
216
|
"""
|
|
225
|
-
list_endpoint =
|
|
217
|
+
list_endpoint = configs.Endpoint(default=SimpleSearch)
|
|
226
218
|
|
|
227
219
|
"""
|
|
228
220
|
The request method(s) to use to route to the create operation. Default is ["POST"].
|
|
229
221
|
"""
|
|
230
|
-
create_request_methods =
|
|
222
|
+
create_request_methods = configs.SelectList(
|
|
231
223
|
allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["POST"]
|
|
232
224
|
)
|
|
233
225
|
|
|
234
226
|
"""
|
|
235
227
|
The request method(s) to use to route to the update operation. Default is ["PATCH"].
|
|
236
228
|
"""
|
|
237
|
-
update_request_methods =
|
|
229
|
+
update_request_methods = configs.SelectList(
|
|
238
230
|
allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["PATCH"]
|
|
239
231
|
)
|
|
240
232
|
|
|
241
233
|
"""
|
|
242
234
|
The request method(s) to use to route to the delete operation. Default is ["DELETE"].
|
|
243
235
|
"""
|
|
244
|
-
delete_request_methods =
|
|
236
|
+
delete_request_methods = configs.SelectList(
|
|
245
237
|
allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["DELETE"]
|
|
246
238
|
)
|
|
247
239
|
|
|
248
240
|
"""
|
|
249
241
|
The request method(s) to use to route to the get operation. Default is ["GET"].
|
|
250
242
|
"""
|
|
251
|
-
get_request_methods =
|
|
252
|
-
allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["GET"]
|
|
253
|
-
)
|
|
243
|
+
get_request_methods = configs.SelectList(allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH"], default=["GET"])
|
|
254
244
|
|
|
255
245
|
"""
|
|
256
246
|
The request method(s) to use to route to the create operation. Default is ["GET"].
|
|
257
247
|
"""
|
|
258
|
-
list_request_methods =
|
|
248
|
+
list_request_methods = configs.SelectList(
|
|
259
249
|
allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH", "QUERY"], default=["GET", "POST", "QUERY"]
|
|
260
250
|
)
|
|
261
251
|
|
|
262
252
|
"""
|
|
263
253
|
The request method(s) to use to route to the create operation. Default is ["POST"].
|
|
264
254
|
"""
|
|
265
|
-
id_column_name =
|
|
255
|
+
id_column_name = configs.ModelColumn("model_class", default=None)
|
|
266
256
|
|
|
267
257
|
"""
|
|
268
258
|
The base URL to be used for all the endpoints.
|
|
269
259
|
"""
|
|
270
|
-
url =
|
|
271
|
-
|
|
272
|
-
authentication =
|
|
273
|
-
authorization =
|
|
274
|
-
output_map =
|
|
275
|
-
output_schema =
|
|
276
|
-
model_class =
|
|
277
|
-
readable_column_names =
|
|
278
|
-
writeable_column_names =
|
|
279
|
-
searchable_column_names =
|
|
280
|
-
sortable_column_names =
|
|
281
|
-
default_sort_column_name =
|
|
282
|
-
default_sort_direction =
|
|
283
|
-
default_limit =
|
|
284
|
-
maximum_limit =
|
|
285
|
-
group_by_column_name =
|
|
286
|
-
input_validation_callable =
|
|
287
|
-
include_routing_data_in_request_data =
|
|
288
|
-
column_overrides =
|
|
289
|
-
internal_casing =
|
|
290
|
-
external_casing =
|
|
291
|
-
security_headers =
|
|
292
|
-
description =
|
|
293
|
-
where =
|
|
260
|
+
url = configs.String(default="")
|
|
261
|
+
|
|
262
|
+
authentication = configs.Authentication(default=Public())
|
|
263
|
+
authorization = configs.Authorization(default=Authorization())
|
|
264
|
+
output_map = configs.Callable(default=None)
|
|
265
|
+
output_schema = configs.Schema(default=None)
|
|
266
|
+
model_class = configs.ModelClass(default=None)
|
|
267
|
+
readable_column_names = configs.ReadableModelColumns("model_class", default=[])
|
|
268
|
+
writeable_column_names = configs.WriteableModelColumns("model_class", default=[])
|
|
269
|
+
searchable_column_names = configs.SearchableModelColumns("model_class", default=[])
|
|
270
|
+
sortable_column_names = configs.ReadableModelColumns("model_class", default=[])
|
|
271
|
+
default_sort_column_name = configs.ModelColumn("model_class", required=True)
|
|
272
|
+
default_sort_direction = configs.Select(["ASC", "DESC"], default="ASC")
|
|
273
|
+
default_limit = configs.Integer(default=50)
|
|
274
|
+
maximum_limit = configs.Integer(default=200)
|
|
275
|
+
group_by_column_name = configs.ModelColumn("model_class")
|
|
276
|
+
input_validation_callable = configs.Callable(default=None)
|
|
277
|
+
include_routing_data_in_request_data = configs.Boolean(default=False)
|
|
278
|
+
column_overrides = configs.Columns(default={})
|
|
279
|
+
internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
280
|
+
external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
281
|
+
security_headers = configs.SecurityHeaders(default=[])
|
|
282
|
+
description = configs.String(default="")
|
|
283
|
+
where = configs.Conditions(default=[])
|
|
294
284
|
_descriptor_config_map = None
|
|
295
285
|
|
|
296
|
-
@
|
|
286
|
+
@decorators.parameters_to_properties
|
|
297
287
|
def __init__(
|
|
298
288
|
self,
|
|
299
289
|
model_class: type[Model],
|
|
@@ -303,11 +293,11 @@ class RestfulApi(EndpointGroup):
|
|
|
303
293
|
sortable_column_names: list[str],
|
|
304
294
|
default_sort_column_name: str,
|
|
305
295
|
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,
|
|
296
|
+
create_endpoint: type[Endpoint] | None = Create,
|
|
297
|
+
delete_endpoint: type[Endpoint] | None = Delete,
|
|
298
|
+
update_endpoint: type[Endpoint] | None = Update,
|
|
299
|
+
get_endpoint: type[Endpoint] | None = Get,
|
|
300
|
+
list_endpoint: type[Endpoint] | None = SimpleSearch,
|
|
311
301
|
create_request_methods: list[str] = ["POST"],
|
|
312
302
|
update_request_methods: list[str] = ["PATCH"],
|
|
313
303
|
delete_request_methods: list[str] = ["DELETE"],
|
clearskies/endpoints/schema.py
CHANGED
|
@@ -1,25 +1,19 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
import
|
|
4
|
-
|
|
5
|
-
from
|
|
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
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
|
+
|
|
5
|
+
from clearskies import authentication, autodoc, configs, decorators
|
|
12
6
|
from clearskies.endpoint import Endpoint
|
|
13
|
-
from clearskies.input_outputs import InputOutput
|
|
14
7
|
|
|
15
8
|
if TYPE_CHECKING:
|
|
16
|
-
from clearskies import
|
|
17
|
-
from clearskies.
|
|
9
|
+
from clearskies import SecurityHeader
|
|
10
|
+
from clearskies.authentication import Authentication
|
|
11
|
+
from clearskies.input_outputs import InputOutput
|
|
18
12
|
|
|
19
13
|
|
|
20
14
|
class Schema(Endpoint):
|
|
21
15
|
"""
|
|
22
|
-
An endpoint that automatically creates a swagger doc for the application
|
|
16
|
+
An endpoint that automatically creates a swagger doc for the application.
|
|
23
17
|
|
|
24
18
|
The schema endpoint must always be attached to an endpoint group. It will document all endpoints
|
|
25
19
|
attached to its parent endpoint group.
|
|
@@ -74,11 +68,12 @@ class Schema(Endpoint):
|
|
|
74
68
|
sortable_column_names=readable_column_names,
|
|
75
69
|
searchable_column_names=readable_column_names,
|
|
76
70
|
default_sort_column_name="name",
|
|
77
|
-
)
|
|
71
|
+
),
|
|
78
72
|
],
|
|
79
73
|
url="/users",
|
|
80
74
|
)
|
|
81
75
|
|
|
76
|
+
|
|
82
77
|
class SomeThing(clearskies.Model):
|
|
83
78
|
id_column_name = "id"
|
|
84
79
|
backend = clearskies.backends.MemoryBackend()
|
|
@@ -87,6 +82,7 @@ class Schema(Endpoint):
|
|
|
87
82
|
thing_1 = clearskies.columns.String(validators=[Required()])
|
|
88
83
|
thing_2 = clearskies.columns.String(validators=[Unique()])
|
|
89
84
|
|
|
85
|
+
|
|
90
86
|
more_endpoints = clearskies.EndpointGroup(
|
|
91
87
|
[
|
|
92
88
|
clearskies.endpoints.HealthCheck(url="health"),
|
|
@@ -130,7 +126,7 @@ class Schema(Endpoint):
|
|
|
130
126
|
"""
|
|
131
127
|
The doc builder class/format to use
|
|
132
128
|
"""
|
|
133
|
-
schema_format =
|
|
129
|
+
schema_format = configs.Any(default=autodoc.formats.oai3_json.Oai3Json)
|
|
134
130
|
|
|
135
131
|
"""
|
|
136
132
|
Addiional data to inject into the schema doc.
|
|
@@ -138,13 +134,13 @@ class Schema(Endpoint):
|
|
|
138
134
|
This is typically used for setting info/server settings in the resultant swagger doc. Anything
|
|
139
135
|
in this dictionary is injected into the "root" of the generated documentation file.
|
|
140
136
|
"""
|
|
141
|
-
schema_configuration =
|
|
137
|
+
schema_configuration = configs.AnyDict(default={})
|
|
142
138
|
|
|
143
|
-
@
|
|
139
|
+
@decorators.parameters_to_properties
|
|
144
140
|
def __init__(
|
|
145
141
|
self,
|
|
146
142
|
url: str,
|
|
147
|
-
schema_format=
|
|
143
|
+
schema_format=autodoc.formats.oai3_json.Oai3Json,
|
|
148
144
|
request_methods: list[str] = ["GET"],
|
|
149
145
|
response_headers: list[str | Callable[..., list[str]]] = [],
|
|
150
146
|
security_headers: list[SecurityHeader] = [],
|