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
clearskies/contexts/wsgi_ref.py
CHANGED
|
@@ -1,14 +1,18 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
1
3
|
import datetime
|
|
2
4
|
from types import ModuleType
|
|
3
|
-
from typing import Any, Callable
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
6
|
from wsgiref.simple_server import make_server
|
|
5
7
|
from wsgiref.util import setup_testing_defaults
|
|
6
8
|
|
|
7
|
-
import clearskies.endpoint
|
|
8
|
-
import clearskies.endpoint_group
|
|
9
9
|
from clearskies.contexts.context import Context
|
|
10
|
-
|
|
11
|
-
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from clearskies.di import AdditionalConfig
|
|
13
|
+
from clearskies.endpoint import Endpoint
|
|
14
|
+
from clearskies.endpoint_group import EndpointGroup
|
|
15
|
+
from clearskies.input_outputs import Wsgi as WsgiInputOutput
|
|
12
16
|
|
|
13
17
|
|
|
14
18
|
class WsgiRef(Context):
|
|
@@ -26,9 +30,11 @@ class WsgiRef(Context):
|
|
|
26
30
|
#!/usr/bin/env python
|
|
27
31
|
import clearskies
|
|
28
32
|
|
|
33
|
+
|
|
29
34
|
def hello_world(name):
|
|
30
35
|
return f"Hello {name}!"
|
|
31
36
|
|
|
37
|
+
|
|
32
38
|
wsgi = clearskies.contexts.WsgiRef(
|
|
33
39
|
clearskies.endpoints.Callable(
|
|
34
40
|
hello_world,
|
|
@@ -49,7 +55,7 @@ class WsgiRef(Context):
|
|
|
49
55
|
|
|
50
56
|
def __init__(
|
|
51
57
|
self,
|
|
52
|
-
application: Callable |
|
|
58
|
+
application: Callable | Endpoint | EndpointGroup,
|
|
53
59
|
port: int = 8080,
|
|
54
60
|
classes: type | list[type] = [],
|
|
55
61
|
modules: ModuleType | list[ModuleType] = [],
|
clearskies/decorators.py
CHANGED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# decorators.pyi
|
|
2
|
+
from typing import Any, Callable, TypeVar
|
|
3
|
+
|
|
4
|
+
# A TypeVar is used to say "whatever kind of function comes in,
|
|
5
|
+
# the same kind of function goes out."
|
|
6
|
+
_F = TypeVar("_F", bound=Callable[..., Any])
|
|
7
|
+
|
|
8
|
+
# This is the type signature for your decorator.
|
|
9
|
+
# It tells Pylance that it preserves the signature of the function it wraps.
|
|
10
|
+
def parameters_to_properties(wrapped: _F) -> _F: ...
|
clearskies/di/di.py
CHANGED
|
@@ -11,7 +11,6 @@ from requests.adapters import HTTPAdapter
|
|
|
11
11
|
from requests.packages.urllib3.util.retry import Retry # type: ignore
|
|
12
12
|
|
|
13
13
|
import clearskies.input_outputs.input_output
|
|
14
|
-
import clearskies.secrets
|
|
15
14
|
from clearskies.di.additional_config import AdditionalConfig
|
|
16
15
|
from clearskies.di.additional_config_auto_import import AdditionalConfigAutoImport
|
|
17
16
|
from clearskies.environment import Environment
|
|
@@ -350,9 +349,9 @@ class Di:
|
|
|
350
349
|
import my_module
|
|
351
350
|
|
|
352
351
|
di = Di()
|
|
353
|
-
di.add_modules(
|
|
354
|
-
my_module
|
|
355
|
-
|
|
352
|
+
di.add_modules(
|
|
353
|
+
[my_module]
|
|
354
|
+
) # also equivalent: di.add_modules(my_module), or Di(modules=[my_module])
|
|
356
355
|
|
|
357
356
|
|
|
358
357
|
def my_function(my_class):
|
|
@@ -956,6 +955,8 @@ class Di:
|
|
|
956
955
|
return uuid
|
|
957
956
|
|
|
958
957
|
def provide_secrets(self):
|
|
958
|
+
import clearskies.secrets
|
|
959
|
+
|
|
959
960
|
return clearskies.secrets.Secrets()
|
|
960
961
|
|
|
961
962
|
def provide_memory_backend_default_data(self):
|
|
@@ -964,8 +965,8 @@ class Di:
|
|
|
964
965
|
def provide_global_table_prefix(self):
|
|
965
966
|
return ""
|
|
966
967
|
|
|
967
|
-
def
|
|
968
|
-
import akeyless # type: ignore
|
|
968
|
+
def provide_akeyles_sdk(self):
|
|
969
|
+
import akeyless # type: ignore[import-untyped]
|
|
969
970
|
|
|
970
971
|
return akeyless
|
|
971
972
|
|
clearskies/di/inject/by_class.py
CHANGED
clearskies/di/inject/by_name.py
CHANGED
clearskies/di/inject/di.py
CHANGED
clearskies/di/inject/now.py
CHANGED
clearskies/di/inject/requests.py
CHANGED
clearskies/di/inject/secrets.py
CHANGED
clearskies/di/inject/utcnow.py
CHANGED
clearskies/di/inject/uuid.py
CHANGED
clearskies/end.py
CHANGED
|
@@ -1,17 +1,22 @@
|
|
|
1
|
-
# type: ignore
|
|
2
1
|
from __future__ import annotations
|
|
3
2
|
|
|
4
3
|
from abc import ABC
|
|
5
4
|
from typing import TYPE_CHECKING, Any
|
|
6
5
|
|
|
7
6
|
if TYPE_CHECKING:
|
|
8
|
-
from clearskies
|
|
7
|
+
from clearskies import typing
|
|
8
|
+
from clearskies.input_outputs import InputOutput
|
|
9
9
|
|
|
10
|
-
from clearskies import exceptions
|
|
10
|
+
from clearskies import configs, configurable, di, exceptions
|
|
11
|
+
from clearskies.authentication import Authorization, Public
|
|
11
12
|
from clearskies.functional import string
|
|
12
13
|
|
|
13
14
|
|
|
14
|
-
class End(
|
|
15
|
+
class End(
|
|
16
|
+
ABC,
|
|
17
|
+
configurable.Configurable,
|
|
18
|
+
di.InjectableProperties,
|
|
19
|
+
):
|
|
15
20
|
"""
|
|
16
21
|
DRY for endpoint and endpoint groups.
|
|
17
22
|
|
|
@@ -20,6 +25,24 @@ class End(ABC):
|
|
|
20
25
|
from the other.
|
|
21
26
|
"""
|
|
22
27
|
|
|
28
|
+
di = di.inject.Di()
|
|
29
|
+
|
|
30
|
+
url = configs.String(required=False, default="")
|
|
31
|
+
|
|
32
|
+
authentication = configs.Authentication(default=None)
|
|
33
|
+
|
|
34
|
+
authorization = configs.Authorization(default=None)
|
|
35
|
+
|
|
36
|
+
internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
37
|
+
external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
38
|
+
response_headers = configs.StringListOrCallable(default=[])
|
|
39
|
+
authentication = configs.Authentication(default=Public())
|
|
40
|
+
authorization = configs.Authorization(default=Authorization())
|
|
41
|
+
security_headers = configs.SecurityHeaders(default=[])
|
|
42
|
+
|
|
43
|
+
cors_header: SecurityHeader = None # type: ignore
|
|
44
|
+
has_cors: bool = False
|
|
45
|
+
|
|
23
46
|
def add_url_prefix(self, prefix: str) -> None:
|
|
24
47
|
self.url = (prefix.rstrip("/") + "/" + self.url.lstrip("/")).lstrip("/")
|
|
25
48
|
|
|
@@ -41,7 +64,7 @@ class End(ABC):
|
|
|
41
64
|
if not self.authorization.gate(input_output.authorization_data, input_output):
|
|
42
65
|
raise exceptions.Authorization("Not Authorized")
|
|
43
66
|
except exceptions.ClientError as client_error:
|
|
44
|
-
raise
|
|
67
|
+
raise exceptions.Authorization(str(client_error))
|
|
45
68
|
|
|
46
69
|
def __call__(self, input_output: InputOutput) -> Any:
|
|
47
70
|
"""
|
|
@@ -116,7 +139,7 @@ class End(ABC):
|
|
|
116
139
|
for index, response_header in enumerate(response_headers):
|
|
117
140
|
if not isinstance(response_header, str):
|
|
118
141
|
raise TypeError(
|
|
119
|
-
f"Invalid response header in entry #{index + 1}: the header should be a string, but I was given a type of '{
|
|
142
|
+
f"Invalid response header in entry #{index + 1}: the header should be a string, but I was given a type of '{response_header.__class__.__name__}' instead."
|
|
120
143
|
)
|
|
121
144
|
parts = response_header.split(":", 1)
|
|
122
145
|
if len(parts) != 2:
|
|
@@ -127,7 +150,7 @@ class End(ABC):
|
|
|
127
150
|
for security_header in self.security_headers:
|
|
128
151
|
security_header.set_headers_for_input_output(input_output)
|
|
129
152
|
|
|
130
|
-
def respond(self, input_output: InputOutput, response:
|
|
153
|
+
def respond(self, input_output: InputOutput, response: typing.response, status_code: int) -> Any:
|
|
131
154
|
self.add_response_headers(input_output)
|
|
132
155
|
return input_output.respond(response, status_code)
|
|
133
156
|
|
|
@@ -181,3 +204,18 @@ class End(ABC):
|
|
|
181
204
|
self.external_casing,
|
|
182
205
|
self.internal_casing,
|
|
183
206
|
)
|
|
207
|
+
|
|
208
|
+
def error(self, input_output: InputOutput, message: str, status_code: int) -> Any:
|
|
209
|
+
"""Return a client-side error (e.g. 400)."""
|
|
210
|
+
return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
|
|
211
|
+
|
|
212
|
+
def input_errors(self, input_output: InputOutput, errors: dict[str, str], status_code: int = 200) -> Any:
|
|
213
|
+
"""Return input errors to the client."""
|
|
214
|
+
return self.respond_json(input_output, {"status": "input_errors", "input_errors": errors}, status_code)
|
|
215
|
+
|
|
216
|
+
def redirect(self, input_output: InputOutput, location: str, status_code: int) -> Any:
|
|
217
|
+
"""Return a redirect response (e.g. 302)."""
|
|
218
|
+
return self.respond_json(input_output, {"status": "redirect", "location": location}, status_code)
|
|
219
|
+
|
|
220
|
+
def handle(self, input_output: InputOutput) -> Any:
|
|
221
|
+
raise NotImplementedError()
|
clearskies/endpoint.py
CHANGED
|
@@ -3,16 +3,9 @@ from __future__ import annotations
|
|
|
3
3
|
import inspect
|
|
4
4
|
import urllib.parse
|
|
5
5
|
from collections import OrderedDict
|
|
6
|
-
from typing import TYPE_CHECKING, Any, Callable
|
|
7
|
-
|
|
8
|
-
import
|
|
9
|
-
import clearskies.configs
|
|
10
|
-
import clearskies.configurable
|
|
11
|
-
import clearskies.decorators
|
|
12
|
-
import clearskies.di
|
|
13
|
-
import clearskies.end
|
|
14
|
-
import clearskies.typing
|
|
15
|
-
from clearskies import autodoc, exceptions
|
|
6
|
+
from typing import TYPE_CHECKING, Any, Callable, Optional
|
|
7
|
+
|
|
8
|
+
from clearskies import autodoc, column, configs, configurable, decorators, di, end, exceptions
|
|
16
9
|
from clearskies.authentication import Authentication, Authorization, Public
|
|
17
10
|
from clearskies.autodoc import schema
|
|
18
11
|
from clearskies.autodoc.request import Parameter, Request
|
|
@@ -27,12 +20,12 @@ if TYPE_CHECKING:
|
|
|
27
20
|
|
|
28
21
|
|
|
29
22
|
class Endpoint(
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
23
|
+
end.End, # type: ignore
|
|
24
|
+
configurable.Configurable,
|
|
25
|
+
di.InjectableProperties,
|
|
33
26
|
):
|
|
34
27
|
"""
|
|
35
|
-
Automating drudgery
|
|
28
|
+
Automating drudgery.
|
|
36
29
|
|
|
37
30
|
With clearskies, endpoints exist to offload some drudgery and make your life easier, but they can also
|
|
38
31
|
get out of your way when you don't need them. Think of them as pre-built endpoints that can execute
|
|
@@ -47,7 +40,7 @@ class Endpoint(
|
|
|
47
40
|
"""
|
|
48
41
|
The dependency injection container
|
|
49
42
|
"""
|
|
50
|
-
di =
|
|
43
|
+
di = di.inject.Di()
|
|
51
44
|
|
|
52
45
|
"""
|
|
53
46
|
Whether or not this endpoint can handle CORS
|
|
@@ -57,7 +50,7 @@ class Endpoint(
|
|
|
57
50
|
"""
|
|
58
51
|
The actual CORS header
|
|
59
52
|
"""
|
|
60
|
-
cors_header: Cors
|
|
53
|
+
cors_header: Optional[Cors] = None
|
|
61
54
|
|
|
62
55
|
"""
|
|
63
56
|
Set some response headers that should be returned for this endpoint.
|
|
@@ -82,7 +75,7 @@ class Endpoint(
|
|
|
82
75
|
wsgi()
|
|
83
76
|
```
|
|
84
77
|
"""
|
|
85
|
-
response_headers =
|
|
78
|
+
response_headers = configs.StringListOrCallable(default=[])
|
|
86
79
|
|
|
87
80
|
"""
|
|
88
81
|
Set the URL for the endpoint
|
|
@@ -161,7 +154,7 @@ class Endpoint(
|
|
|
161
154
|
```
|
|
162
155
|
|
|
163
156
|
"""
|
|
164
|
-
url =
|
|
157
|
+
url = configs.Url(default="")
|
|
165
158
|
|
|
166
159
|
"""
|
|
167
160
|
The allowed request methods for this endpoint.
|
|
@@ -204,7 +197,7 @@ class Endpoint(
|
|
|
204
197
|
}
|
|
205
198
|
```
|
|
206
199
|
"""
|
|
207
|
-
request_methods =
|
|
200
|
+
request_methods = configs.SelectList(
|
|
208
201
|
allowed_values=["GET", "POST", "PUT", "DELETE", "PATCH", "QUERY"], default=["GET"]
|
|
209
202
|
)
|
|
210
203
|
|
|
@@ -214,7 +207,7 @@ class Endpoint(
|
|
|
214
207
|
Use this to attach an instance of `clearskies.authentication.Authentication` to an endpoint, which enforces authentication.
|
|
215
208
|
For more details, see the dedicated documentation section on authentication and authorization. By default, all endpoints are public.
|
|
216
209
|
"""
|
|
217
|
-
authentication =
|
|
210
|
+
authentication = configs.Authentication(default=Public())
|
|
218
211
|
|
|
219
212
|
"""
|
|
220
213
|
The authorization rules for this endpoint
|
|
@@ -222,7 +215,7 @@ class Endpoint(
|
|
|
222
215
|
Use this to attach an instance of `clearskies.authentication.Authorization` to an endpoint, which enforces authorization.
|
|
223
216
|
For more details, see the dedicated documentation section on authentication and authorization. By default, no authorization is enforced.
|
|
224
217
|
"""
|
|
225
|
-
authorization =
|
|
218
|
+
authorization = configs.Authorization(default=Authorization())
|
|
226
219
|
|
|
227
220
|
"""
|
|
228
221
|
An override of the default model-to-json mapping for endpoints that auto-convert models to json.
|
|
@@ -329,7 +322,7 @@ class Endpoint(
|
|
|
329
322
|
```
|
|
330
323
|
|
|
331
324
|
"""
|
|
332
|
-
output_map =
|
|
325
|
+
output_map = configs.Callable(default=None)
|
|
333
326
|
|
|
334
327
|
"""
|
|
335
328
|
A schema that describes the expected output to the client.
|
|
@@ -338,14 +331,14 @@ class Endpoint(
|
|
|
338
331
|
Note that this is typically not required - when returning models and relying on clearskies to auto-convert to JSON,
|
|
339
332
|
it will also automatically generate your documentation.
|
|
340
333
|
"""
|
|
341
|
-
output_schema =
|
|
334
|
+
output_schema = configs.Schema(default=None)
|
|
342
335
|
|
|
343
336
|
"""
|
|
344
337
|
The model class used by this endpoint.
|
|
345
338
|
|
|
346
339
|
The endpoint will use this to fetch/save/validate incoming data as needed.
|
|
347
340
|
"""
|
|
348
|
-
model_class =
|
|
341
|
+
model_class = configs.ModelClass(default=None)
|
|
349
342
|
|
|
350
343
|
"""
|
|
351
344
|
Columns from the model class that should be returned to the client.
|
|
@@ -421,7 +414,7 @@ class Endpoint(
|
|
|
421
414
|
|
|
422
415
|
```
|
|
423
416
|
"""
|
|
424
|
-
readable_column_names =
|
|
417
|
+
readable_column_names = configs.ReadableModelColumns("model_class", default=[])
|
|
425
418
|
|
|
426
419
|
"""
|
|
427
420
|
Specifies which columns from a model class can be set by the client.
|
|
@@ -486,14 +479,14 @@ class Endpoint(
|
|
|
486
479
|
```
|
|
487
480
|
|
|
488
481
|
"""
|
|
489
|
-
writeable_column_names =
|
|
482
|
+
writeable_column_names = configs.WriteableModelColumns("model_class", default=[])
|
|
490
483
|
|
|
491
484
|
"""
|
|
492
485
|
Columns from the model class that can be searched by the client.
|
|
493
486
|
|
|
494
487
|
Sets which columns the client is allowed to search (for endpoints that support searching).
|
|
495
488
|
"""
|
|
496
|
-
searchable_column_names =
|
|
489
|
+
searchable_column_names = configs.SearchableModelColumns("model_class", default=[])
|
|
497
490
|
|
|
498
491
|
"""
|
|
499
492
|
A function to call to add custom input validation logic.
|
|
@@ -557,7 +550,7 @@ class Endpoint(
|
|
|
557
550
|
```
|
|
558
551
|
|
|
559
552
|
"""
|
|
560
|
-
input_validation_callable =
|
|
553
|
+
input_validation_callable = configs.Callable(default=None)
|
|
561
554
|
|
|
562
555
|
"""
|
|
563
556
|
A dictionary with columns that should override columns in the model.
|
|
@@ -579,7 +572,7 @@ class Endpoint(
|
|
|
579
572
|
)
|
|
580
573
|
```
|
|
581
574
|
"""
|
|
582
|
-
column_overrides =
|
|
575
|
+
column_overrides = configs.Columns(default={})
|
|
583
576
|
|
|
584
577
|
"""
|
|
585
578
|
Used in conjunction with external_casing to change the casing of the key names in the outputted JSON of the endpoint.
|
|
@@ -638,14 +631,14 @@ class Endpoint(
|
|
|
638
631
|
}
|
|
639
632
|
```
|
|
640
633
|
"""
|
|
641
|
-
internal_casing =
|
|
634
|
+
internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
642
635
|
|
|
643
636
|
"""
|
|
644
637
|
Used in conjunction with internal_casing to change the casing of the key names in the outputted JSON of the endpoint.
|
|
645
638
|
|
|
646
639
|
See the docs for `internal_casing` for more details and usage examples.
|
|
647
640
|
"""
|
|
648
|
-
external_casing =
|
|
641
|
+
external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
649
642
|
|
|
650
643
|
"""
|
|
651
644
|
Configure standard security headers to be sent along in the response from this endpoint.
|
|
@@ -689,19 +682,19 @@ class Endpoint(
|
|
|
689
682
|
```
|
|
690
683
|
|
|
691
684
|
"""
|
|
692
|
-
security_headers =
|
|
685
|
+
security_headers = configs.SecurityHeaders(default=[])
|
|
693
686
|
|
|
694
687
|
"""
|
|
695
688
|
A description for this endpoint. This is added to any auto-documentation
|
|
696
689
|
"""
|
|
697
|
-
description =
|
|
690
|
+
description = configs.String(default="")
|
|
698
691
|
|
|
699
692
|
"""
|
|
700
693
|
Whether or not the routing data should also be persisted to the model. Defaults to False.
|
|
701
694
|
|
|
702
695
|
Note: this is only relevant for handlers that accept request data
|
|
703
696
|
"""
|
|
704
|
-
include_routing_data_in_request_data =
|
|
697
|
+
include_routing_data_in_request_data = configs.Boolean(default=False)
|
|
705
698
|
|
|
706
699
|
"""
|
|
707
700
|
Additional conditions to always add to the results.
|
|
@@ -783,7 +776,7 @@ class Endpoint(
|
|
|
783
776
|
and note that neither Greg nor Ann are returned. Ann because she doesn't make the grade criteria, and Greg because
|
|
784
777
|
he won't graduate.
|
|
785
778
|
"""
|
|
786
|
-
where =
|
|
779
|
+
where = configs.Conditions(default=[])
|
|
787
780
|
|
|
788
781
|
"""
|
|
789
782
|
Additional joins to always add to the query.
|
|
@@ -868,18 +861,18 @@ class Endpoint(
|
|
|
868
861
|
e.g., the inner join reomves all the students that don't have an entry in the PastRecord model.
|
|
869
862
|
|
|
870
863
|
"""
|
|
871
|
-
joins =
|
|
864
|
+
joins = configs.Joins(default=[])
|
|
872
865
|
|
|
873
866
|
cors_header: Cors = None # type: ignore
|
|
874
|
-
_model:
|
|
875
|
-
_columns: dict[str,
|
|
876
|
-
_readable_columns: dict[str,
|
|
877
|
-
_writeable_columns: dict[str,
|
|
878
|
-
_searchable_columns: dict[str,
|
|
879
|
-
_sortable_columns: dict[str,
|
|
880
|
-
_as_json_map: dict[str,
|
|
881
|
-
|
|
882
|
-
@
|
|
867
|
+
_model: Model = None # type: ignore
|
|
868
|
+
_columns: dict[str, column.Column] = None # type: ignore
|
|
869
|
+
_readable_columns: dict[str, column.Column] = None # type: ignore
|
|
870
|
+
_writeable_columns: dict[str, column.Column] = None # type: ignore
|
|
871
|
+
_searchable_columns: dict[str, column.Column] = None # type: ignore
|
|
872
|
+
_sortable_columns: dict[str, column.Column] = None # type: ignore
|
|
873
|
+
_as_json_map: dict[str, column.Column] = None # type: ignore
|
|
874
|
+
|
|
875
|
+
@decorators.parameters_to_properties
|
|
883
876
|
def __init__(
|
|
884
877
|
self,
|
|
885
878
|
url: str = "",
|
|
@@ -972,9 +965,6 @@ class Endpoint(
|
|
|
972
965
|
)
|
|
973
966
|
return self.authorization.filter_model(model, input_output.authorization_data, input_output)
|
|
974
967
|
|
|
975
|
-
def handle(self, input_output: InputOutput) -> Any:
|
|
976
|
-
raise NotImplementedError()
|
|
977
|
-
|
|
978
968
|
def matches_request(self, input_output: InputOutput, allow_partial=False) -> bool:
|
|
979
969
|
"""Whether or not we can handle an incoming request based on URL and request method."""
|
|
980
970
|
# soo..... this excessively duplicates the logic in __call__, but I'm being lazy right now
|
|
@@ -1012,20 +1002,14 @@ class Endpoint(
|
|
|
1012
1002
|
def failure(self, input_output: InputOutput) -> Any:
|
|
1013
1003
|
return self.respond_json(input_output, {"status": "failure"}, 500)
|
|
1014
1004
|
|
|
1015
|
-
def input_errors(self, input_output: InputOutput, errors: dict[str, str], status_code: int = 200) -> Any:
|
|
1016
|
-
"""Return input errors to the client."""
|
|
1017
|
-
return self.respond_json(input_output, {"status": "input_errors", "input_errors": errors}, status_code)
|
|
1018
|
-
|
|
1019
|
-
def error(self, input_output: InputOutput, message: str, status_code: int) -> Any:
|
|
1020
|
-
"""Return a client-side error (e.g. 400)."""
|
|
1021
|
-
return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
|
|
1022
|
-
|
|
1023
1005
|
def redirect(self, input_output: InputOutput, location: str, status_code: int) -> Any:
|
|
1024
1006
|
"""Return a redirect."""
|
|
1025
1007
|
input_output.response_headers.add("content-type", "text/html")
|
|
1026
1008
|
input_output.response_headers.add("location", location)
|
|
1027
1009
|
return self.respond(
|
|
1028
|
-
|
|
1010
|
+
input_output,
|
|
1011
|
+
'<meta http-equiv="refresh" content="0; url=' + urllib.parse.quote(location) + '">Redirecting',
|
|
1012
|
+
status_code,
|
|
1029
1013
|
)
|
|
1030
1014
|
|
|
1031
1015
|
def success(
|
|
@@ -1053,7 +1037,7 @@ class Endpoint(
|
|
|
1053
1037
|
|
|
1054
1038
|
return self.respond_json(input_output, response_data, 200)
|
|
1055
1039
|
|
|
1056
|
-
def model_as_json(self, model:
|
|
1040
|
+
def model_as_json(self, model: Model, input_output: InputOutput) -> dict[str, Any]:
|
|
1057
1041
|
if self.output_map:
|
|
1058
1042
|
return self.di.call_function(self.output_map, model=model, **input_output.get_context_for_callables())
|
|
1059
1043
|
|
|
@@ -1070,7 +1054,7 @@ class Endpoint(
|
|
|
1070
1054
|
json[self.auto_case_column_name(key, True)] = value
|
|
1071
1055
|
return json
|
|
1072
1056
|
|
|
1073
|
-
def _build_as_json_map(self, model:
|
|
1057
|
+
def _build_as_json_map(self, model: Model) -> dict[str, column.Column]:
|
|
1074
1058
|
conversion_map = {}
|
|
1075
1059
|
if not self.readable_column_names:
|
|
1076
1060
|
raise ValueError(
|
clearskies/endpoint_group.py
CHANGED
|
@@ -2,10 +2,7 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
from typing import TYPE_CHECKING, Any, Callable, Self
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import clearskies.di
|
|
7
|
-
import clearskies.end
|
|
8
|
-
from clearskies import exceptions
|
|
5
|
+
from clearskies import configs, configurable, decorators, di, end
|
|
9
6
|
from clearskies.authentication import Authentication, Authorization, Public
|
|
10
7
|
from clearskies.endpoint import Endpoint
|
|
11
8
|
from clearskies.functional import routing
|
|
@@ -16,9 +13,9 @@ if TYPE_CHECKING:
|
|
|
16
13
|
|
|
17
14
|
|
|
18
15
|
class EndpointGroup(
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
16
|
+
end.End, # type: ignore
|
|
17
|
+
configurable.Configurable,
|
|
18
|
+
di.InjectableProperties,
|
|
22
19
|
):
|
|
23
20
|
"""
|
|
24
21
|
An endpoint group brings endpoints together: it basically handles routing.
|
|
@@ -216,32 +213,32 @@ class EndpointGroup(
|
|
|
216
213
|
"""
|
|
217
214
|
The dependency injection container
|
|
218
215
|
"""
|
|
219
|
-
di =
|
|
216
|
+
di = di.inject.Di()
|
|
220
217
|
|
|
221
218
|
"""
|
|
222
219
|
The base URL for the endpoint group.
|
|
223
220
|
|
|
224
221
|
This URL is added as a prefix to all endpoints attached to the group. This includes any named URL parameters:
|
|
225
222
|
"""
|
|
226
|
-
url =
|
|
223
|
+
url = configs.String(default="")
|
|
227
224
|
|
|
228
225
|
"""
|
|
229
226
|
The list of endpoints connected to this endpoint group
|
|
230
227
|
"""
|
|
231
|
-
endpoints =
|
|
228
|
+
endpoints = configs.EndpointList()
|
|
232
229
|
|
|
233
|
-
internal_casing =
|
|
234
|
-
external_casing =
|
|
235
|
-
response_headers =
|
|
236
|
-
authentication =
|
|
237
|
-
authorization =
|
|
238
|
-
security_headers =
|
|
230
|
+
internal_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
231
|
+
external_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
232
|
+
response_headers = configs.StringListOrCallable(default=[])
|
|
233
|
+
authentication = configs.Authentication(default=Public())
|
|
234
|
+
authorization = configs.Authorization(default=Authorization())
|
|
235
|
+
security_headers = configs.SecurityHeaders(default=[])
|
|
239
236
|
|
|
240
237
|
cors_header: SecurityHeader = None # type: ignore
|
|
241
238
|
has_cors: bool = False
|
|
242
239
|
endpoints_initialized = False
|
|
243
240
|
|
|
244
|
-
@
|
|
241
|
+
@decorators.parameters_to_properties
|
|
245
242
|
def __init__(
|
|
246
243
|
self,
|
|
247
244
|
endpoints: list[Endpoint | Self],
|
|
@@ -325,7 +322,7 @@ class EndpointGroup(
|
|
|
325
322
|
return self.respond_json(input_output, {"status": "client_error", "error": message}, status_code)
|
|
326
323
|
|
|
327
324
|
def all_endpoints(self) -> list[Endpoint]:
|
|
328
|
-
"""
|
|
325
|
+
"""Return the full (recursive) list of all endpoints associated with this endpoint group."""
|
|
329
326
|
all_endpoints: list[Endpoint] = []
|
|
330
327
|
for endpoint in self.endpoints:
|
|
331
328
|
if hasattr(endpoint, "all_endpoints"):
|