clear-skies 1.22.31__py3-none-any.whl → 2.0.1__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-1.22.31.dist-info → clear_skies-2.0.1.dist-info}/METADATA +12 -14
- clear_skies-2.0.1.dist-info/RECORD +249 -0
- {clear_skies-1.22.31.dist-info → clear_skies-2.0.1.dist-info}/WHEEL +1 -1
- clearskies/__init__.py +42 -25
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +8 -41
- clearskies/authentication/authentication.py +46 -0
- clearskies/authentication/authorization.py +8 -9
- clearskies/authentication/authorization_pass_through.py +11 -9
- clearskies/authentication/jwks.py +133 -58
- clearskies/authentication/public.py +3 -38
- clearskies/authentication/secret_bearer.py +516 -54
- clearskies/autodoc/formats/oai3_json/__init__.py +1 -1
- clearskies/autodoc/formats/oai3_json/oai3_json.py +9 -7
- clearskies/autodoc/formats/oai3_json/parameter.py +6 -3
- clearskies/autodoc/formats/oai3_json/request.py +7 -5
- clearskies/autodoc/formats/oai3_json/response.py +7 -4
- clearskies/autodoc/formats/oai3_json/schema/object.py +4 -1
- clearskies/autodoc/request/__init__.py +2 -0
- clearskies/autodoc/request/header.py +4 -6
- clearskies/autodoc/request/json_body.py +4 -6
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +7 -4
- clearskies/autodoc/request/url_parameter.py +4 -6
- clearskies/autodoc/request/url_path.py +4 -6
- clearskies/autodoc/schema/__init__.py +4 -2
- clearskies/autodoc/schema/array.py +5 -6
- clearskies/autodoc/schema/boolean.py +4 -10
- clearskies/autodoc/schema/date.py +0 -3
- clearskies/autodoc/schema/datetime.py +1 -4
- clearskies/autodoc/schema/double.py +0 -3
- clearskies/autodoc/schema/enum.py +4 -2
- clearskies/autodoc/schema/integer.py +4 -9
- clearskies/autodoc/schema/long.py +0 -3
- clearskies/autodoc/schema/number.py +4 -9
- clearskies/autodoc/schema/object.py +5 -7
- clearskies/autodoc/schema/password.py +0 -3
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +4 -10
- clearskies/backends/__init__.py +55 -20
- clearskies/backends/api_backend.py +1100 -284
- clearskies/backends/backend.py +53 -84
- clearskies/backends/cursor_backend.py +236 -186
- clearskies/backends/memory_backend.py +519 -226
- clearskies/backends/secrets_backend.py +75 -31
- clearskies/column.py +1229 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +205 -0
- clearskies/columns/belongs_to_id.py +483 -0
- clearskies/columns/belongs_to_model.py +128 -0
- clearskies/columns/belongs_to_self.py +105 -0
- clearskies/columns/boolean.py +109 -0
- clearskies/columns/category_tree.py +275 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +127 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +94 -0
- clearskies/columns/created_by_authorization_data.py +116 -0
- clearskies/columns/created_by_header.py +99 -0
- clearskies/columns/created_by_ip.py +92 -0
- clearskies/columns/created_by_routing_data.py +96 -0
- clearskies/columns/created_by_user_agent.py +92 -0
- clearskies/columns/date.py +230 -0
- clearskies/columns/datetime.py +278 -0
- clearskies/columns/email.py +76 -0
- clearskies/columns/float.py +149 -0
- clearskies/columns/has_many.py +505 -0
- clearskies/columns/has_many_self.py +56 -0
- clearskies/columns/has_one.py +14 -0
- clearskies/columns/integer.py +156 -0
- clearskies/columns/json.py +122 -0
- clearskies/columns/many_to_many_ids.py +333 -0
- clearskies/columns/many_to_many_ids_with_data.py +270 -0
- clearskies/columns/many_to_many_models.py +154 -0
- clearskies/columns/many_to_many_pivots.py +133 -0
- clearskies/columns/phone.py +158 -0
- clearskies/columns/select.py +91 -0
- clearskies/columns/string.py +98 -0
- clearskies/columns/timestamp.py +160 -0
- clearskies/columns/updated.py +110 -0
- clearskies/columns/uuid.py +86 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +162 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +13 -0
- clearskies/configs/any_dict.py +22 -0
- clearskies/configs/any_dict_or_callable.py +23 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +16 -0
- clearskies/configs/boolean_or_callable.py +18 -0
- clearskies/configs/callable_config.py +18 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +24 -0
- clearskies/configs/datetime.py +18 -0
- clearskies/configs/datetime_or_callable.py +19 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +28 -0
- clearskies/configs/float.py +16 -0
- clearskies/configs/float_or_callable.py +18 -0
- clearskies/configs/integer.py +16 -0
- clearskies/configs/integer_or_callable.py +18 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +30 -0
- clearskies/configs/list_any_dict_or_callable.py +31 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +65 -0
- clearskies/configs/model_columns.py +56 -0
- clearskies/configs/model_destination_name.py +25 -0
- clearskies/configs/model_to_id_column.py +43 -0
- clearskies/configs/readable_model_column.py +9 -0
- clearskies/configs/readable_model_columns.py +9 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +9 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +26 -0
- clearskies/configs/select_list.py +47 -0
- clearskies/configs/string.py +29 -0
- clearskies/configs/string_dict.py +32 -0
- clearskies/configs/string_list.py +32 -0
- clearskies/configs/string_list_or_callable.py +35 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +18 -0
- clearskies/configs/timezone.py +18 -0
- clearskies/configs/url.py +23 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +9 -0
- clearskies/configs/writeable_model_columns.py +9 -0
- clearskies/configurable.py +76 -0
- clearskies/contexts/__init__.py +8 -8
- clearskies/contexts/cli.py +8 -41
- clearskies/contexts/context.py +91 -56
- clearskies/contexts/wsgi.py +16 -29
- clearskies/contexts/wsgi_ref.py +53 -0
- clearskies/di/__init__.py +10 -7
- clearskies/di/additional_config.py +115 -4
- clearskies/di/additional_config_auto_import.py +12 -0
- clearskies/di/di.py +742 -121
- clearskies/di/inject/__init__.py +23 -0
- clearskies/di/inject/by_class.py +21 -0
- clearskies/di/inject/by_name.py +18 -0
- clearskies/di/inject/di.py +13 -0
- clearskies/di/inject/environment.py +14 -0
- clearskies/di/inject/input_output.py +20 -0
- clearskies/di/inject/now.py +13 -0
- clearskies/di/inject/requests.py +13 -0
- clearskies/di/inject/secrets.py +14 -0
- clearskies/di/inject/utcnow.py +13 -0
- clearskies/di/inject/uuid.py +15 -0
- clearskies/di/injectable.py +29 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/end.py +183 -0
- clearskies/endpoint.py +1310 -0
- clearskies/endpoint_group.py +310 -0
- clearskies/endpoints/__init__.py +23 -0
- clearskies/endpoints/advanced_search.py +526 -0
- clearskies/endpoints/callable.py +388 -0
- clearskies/endpoints/create.py +202 -0
- clearskies/endpoints/delete.py +139 -0
- clearskies/endpoints/get.py +275 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +573 -0
- clearskies/endpoints/restful_api.py +427 -0
- clearskies/endpoints/simple_search.py +286 -0
- clearskies/endpoints/update.py +190 -0
- clearskies/environment.py +5 -3
- clearskies/exceptions/__init__.py +17 -0
- clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/exceptions/not_found.py +2 -0
- clearskies/functional/__init__.py +2 -2
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +19 -11
- clearskies/functional/validations.py +61 -9
- clearskies/input_outputs/__init__.py +9 -7
- clearskies/input_outputs/cli.py +130 -142
- clearskies/input_outputs/exceptions/__init__.py +1 -1
- clearskies/input_outputs/headers.py +45 -0
- clearskies/input_outputs/input_output.py +91 -122
- clearskies/input_outputs/programmatic.py +69 -0
- clearskies/input_outputs/wsgi.py +23 -38
- clearskies/model.py +984 -183
- clearskies/parameters_to_properties.py +31 -0
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +223 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +196 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +3 -31
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
- clearskies/secrets/akeyless.py +88 -147
- clearskies/secrets/secrets.py +8 -8
- clearskies/security_header.py +15 -0
- clearskies/security_headers/__init__.py +8 -8
- clearskies/security_headers/cache_control.py +47 -110
- clearskies/security_headers/cors.py +40 -95
- clearskies/security_headers/csp.py +76 -151
- clearskies/security_headers/hsts.py +14 -16
- clearskies/test_base.py +8 -0
- clearskies/typing.py +11 -0
- clearskies/validator.py +37 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +62 -0
- clearskies/validators/before_column.py +13 -0
- clearskies/validators/in_the_future.py +32 -0
- clearskies/validators/in_the_future_at_least.py +11 -0
- clearskies/validators/in_the_future_at_most.py +10 -0
- clearskies/validators/in_the_past.py +32 -0
- clearskies/validators/in_the_past_at_least.py +10 -0
- clearskies/validators/in_the_past_at_most.py +10 -0
- clearskies/validators/maximum_length.py +26 -0
- clearskies/validators/maximum_value.py +29 -0
- clearskies/validators/minimum_length.py +26 -0
- clearskies/validators/minimum_value.py +29 -0
- clearskies/validators/required.py +35 -0
- clearskies/validators/timedelta.py +59 -0
- clearskies/validators/unique.py +31 -0
- clear_skies-1.22.31.dist-info/RECORD +0 -214
- clearskies/application.py +0 -29
- clearskies/authentication/auth0_jwks.py +0 -118
- clearskies/authentication/auth_exception.py +0 -2
- clearskies/authentication/jwks_jwcrypto.py +0 -51
- clearskies/backends/api_get_only_backend.py +0 -48
- clearskies/backends/example_backend.py +0 -43
- clearskies/backends/file_backend.py +0 -48
- clearskies/backends/json_backend.py +0 -7
- clearskies/backends/restful_api_advanced_search_backend.py +0 -103
- clearskies/binding_config.py +0 -16
- clearskies/column_types/__init__.py +0 -203
- clearskies/column_types/audit.py +0 -249
- clearskies/column_types/belongs_to.py +0 -271
- clearskies/column_types/boolean.py +0 -60
- clearskies/column_types/category_tree.py +0 -304
- clearskies/column_types/column.py +0 -373
- clearskies/column_types/created.py +0 -26
- clearskies/column_types/created_by_authorization_data.py +0 -26
- clearskies/column_types/created_by_header.py +0 -24
- clearskies/column_types/created_by_ip.py +0 -17
- clearskies/column_types/created_by_routing_data.py +0 -25
- clearskies/column_types/created_by_user_agent.py +0 -17
- clearskies/column_types/created_micro.py +0 -26
- clearskies/column_types/datetime.py +0 -109
- clearskies/column_types/datetime_micro.py +0 -12
- clearskies/column_types/email.py +0 -18
- clearskies/column_types/float.py +0 -43
- clearskies/column_types/has_many.py +0 -179
- clearskies/column_types/has_one.py +0 -60
- clearskies/column_types/integer.py +0 -41
- clearskies/column_types/json.py +0 -25
- clearskies/column_types/many_to_many.py +0 -278
- clearskies/column_types/many_to_many_with_data.py +0 -162
- clearskies/column_types/phone.py +0 -48
- clearskies/column_types/select.py +0 -11
- clearskies/column_types/string.py +0 -24
- clearskies/column_types/timestamp.py +0 -73
- clearskies/column_types/updated.py +0 -26
- clearskies/column_types/updated_micro.py +0 -26
- clearskies/column_types/uuid.py +0 -25
- clearskies/columns.py +0 -123
- clearskies/condition_parser.py +0 -172
- clearskies/contexts/build_context.py +0 -54
- clearskies/contexts/convert_to_application.py +0 -190
- clearskies/contexts/extract_handler.py +0 -37
- clearskies/contexts/test.py +0 -94
- clearskies/decorators/__init__.py +0 -41
- clearskies/decorators/allow_non_json_bodies.py +0 -9
- clearskies/decorators/auth0_jwks.py +0 -22
- clearskies/decorators/authorization.py +0 -10
- clearskies/decorators/binding_classes.py +0 -9
- clearskies/decorators/binding_modules.py +0 -9
- clearskies/decorators/bindings.py +0 -9
- clearskies/decorators/create.py +0 -10
- clearskies/decorators/delete.py +0 -10
- clearskies/decorators/docs.py +0 -14
- clearskies/decorators/get.py +0 -10
- clearskies/decorators/jwks.py +0 -26
- clearskies/decorators/merge.py +0 -124
- clearskies/decorators/patch.py +0 -10
- clearskies/decorators/post.py +0 -10
- clearskies/decorators/public.py +0 -11
- clearskies/decorators/response_headers.py +0 -10
- clearskies/decorators/return_raw_response.py +0 -9
- clearskies/decorators/schema.py +0 -10
- clearskies/decorators/secret_bearer.py +0 -24
- clearskies/decorators/security_headers.py +0 -10
- clearskies/di/standard_dependencies.py +0 -151
- clearskies/handlers/__init__.py +0 -41
- clearskies/handlers/advanced_search.py +0 -271
- clearskies/handlers/base.py +0 -479
- clearskies/handlers/callable.py +0 -192
- clearskies/handlers/create.py +0 -35
- clearskies/handlers/crud_by_method.py +0 -18
- clearskies/handlers/database_connector.py +0 -32
- clearskies/handlers/delete.py +0 -61
- clearskies/handlers/exceptions/__init__.py +0 -5
- clearskies/handlers/exceptions/not_found.py +0 -3
- clearskies/handlers/get.py +0 -156
- clearskies/handlers/health_check.py +0 -59
- clearskies/handlers/input_processing.py +0 -79
- clearskies/handlers/list.py +0 -530
- clearskies/handlers/mygrations.py +0 -82
- clearskies/handlers/request_method_routing.py +0 -47
- clearskies/handlers/restful_api.py +0 -218
- clearskies/handlers/routing.py +0 -62
- clearskies/handlers/schema_helper.py +0 -128
- clearskies/handlers/simple_routing.py +0 -206
- clearskies/handlers/simple_routing_route.py +0 -197
- clearskies/handlers/simple_search.py +0 -136
- clearskies/handlers/update.py +0 -102
- clearskies/handlers/write.py +0 -193
- clearskies/input_requirements/__init__.py +0 -78
- clearskies/input_requirements/after.py +0 -36
- clearskies/input_requirements/before.py +0 -36
- clearskies/input_requirements/in_the_future_at_least.py +0 -19
- clearskies/input_requirements/in_the_future_at_most.py +0 -19
- clearskies/input_requirements/in_the_past_at_least.py +0 -19
- clearskies/input_requirements/in_the_past_at_most.py +0 -19
- clearskies/input_requirements/maximum_length.py +0 -19
- clearskies/input_requirements/maximum_value.py +0 -19
- clearskies/input_requirements/minimum_length.py +0 -22
- clearskies/input_requirements/minimum_value.py +0 -19
- clearskies/input_requirements/required.py +0 -23
- clearskies/input_requirements/requirement.py +0 -25
- clearskies/input_requirements/time_delta.py +0 -38
- clearskies/input_requirements/unique.py +0 -18
- clearskies/mocks/__init__.py +0 -7
- clearskies/mocks/input_output.py +0 -124
- clearskies/mocks/models.py +0 -142
- clearskies/models.py +0 -350
- clearskies/security_headers/base.py +0 -12
- clearskies/tests/simple_api/models/__init__.py +0 -2
- clearskies/tests/simple_api/models/status.py +0 -23
- clearskies/tests/simple_api/models/user.py +0 -21
- clearskies/tests/simple_api/users_api.py +0 -64
- {clear_skies-1.22.31.dist-info → clear_skies-2.0.1.dist-info}/LICENSE +0 -0
- /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
- /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
- /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
|
@@ -1,100 +1,64 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from collections import OrderedDict
|
|
3
|
-
from ..handlers.exceptions import ClientError
|
|
4
1
|
import json
|
|
2
|
+
from abc import ABC, abstractmethod
|
|
3
|
+
from typing import Any
|
|
4
|
+
from urllib.parse import parse_qs
|
|
5
5
|
|
|
6
|
+
import clearskies.configurable
|
|
7
|
+
import clearskies.typing
|
|
8
|
+
from clearskies.configs import AnyDict, StringDict
|
|
9
|
+
from clearskies.exceptions import ClientError
|
|
6
10
|
|
|
7
|
-
|
|
8
|
-
_response_headers = None
|
|
9
|
-
_body_as_json = None
|
|
10
|
-
_body_loaded_as_json = False
|
|
11
|
-
_routing_data = None
|
|
12
|
-
_authorization_data = None
|
|
13
|
-
|
|
14
|
-
@abstractmethod
|
|
15
|
-
def respond(self, body, status_code=200):
|
|
16
|
-
pass
|
|
17
|
-
|
|
18
|
-
def error(self, body):
|
|
19
|
-
return self.respond(body, 400)
|
|
11
|
+
from .headers import Headers
|
|
20
12
|
|
|
21
|
-
def success(self, body):
|
|
22
|
-
return self.respond(body)
|
|
23
13
|
|
|
24
|
-
|
|
25
|
-
|
|
14
|
+
class InputOutput(ABC, clearskies.configurable.Configurable):
|
|
15
|
+
"""Manage the request and response to the client."""
|
|
26
16
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
17
|
+
response_headers: Headers = None # type: ignore
|
|
18
|
+
request_headers: Headers = None # type: ignore
|
|
19
|
+
query_parameters = clearskies.configs.AnyDict(default={})
|
|
20
|
+
routing_data = clearskies.configs.StringDict(default={})
|
|
21
|
+
authorization_data = clearskies.configs.AnyDict(default={})
|
|
31
22
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
self._response_headers = OrderedDict()
|
|
35
|
-
self._response_headers[key.upper()] = value
|
|
23
|
+
_body_as_json: dict[str, Any] | list[Any] | None = {}
|
|
24
|
+
_body_loaded_as_json = False
|
|
36
25
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
26
|
+
def __init__(self):
|
|
27
|
+
self.response_headers = Headers()
|
|
28
|
+
self.request_headers = Headers(self.get_request_headers())
|
|
29
|
+
self.query_parameters = {key: val[0] for (key, val) in parse_qs(self.get_query_string()).items()}
|
|
30
|
+
self.authorization_data = {}
|
|
31
|
+
self.routing_data = {}
|
|
32
|
+
self.finalize_and_validate_configuration()
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def respond(self, body: clearskies.typing.response, status_code: int = 200) -> Any:
|
|
36
|
+
"""
|
|
37
|
+
Pass along a response to the client.
|
|
45
38
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
39
|
+
Accepts a string, bytes, dictionary, or list. If a content type has not been set then it will automatically
|
|
40
|
+
be set to application/json
|
|
41
|
+
"""
|
|
42
|
+
pass
|
|
49
43
|
|
|
50
44
|
@abstractmethod
|
|
51
|
-
def get_body(self):
|
|
45
|
+
def get_body(self) -> str:
|
|
46
|
+
"""Return the raw body set by the client."""
|
|
52
47
|
pass
|
|
53
48
|
|
|
54
49
|
@abstractmethod
|
|
55
|
-
def has_body(self):
|
|
50
|
+
def has_body(self) -> bool:
|
|
51
|
+
"""Whether or not the request included a body."""
|
|
56
52
|
pass
|
|
57
53
|
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
def set_routing_data(self, data):
|
|
62
|
-
self._routing_data = data
|
|
63
|
-
|
|
64
|
-
def add_routing_data(self, key, value=None):
|
|
65
|
-
if self._routing_data is None:
|
|
66
|
-
self._routing_data = {}
|
|
67
|
-
if type(key) == dict:
|
|
68
|
-
self._routing_data = {**self._routing_data, **key}
|
|
69
|
-
else:
|
|
70
|
-
self._routing_data[key] = value
|
|
71
|
-
|
|
72
|
-
def request_data(self, required=True, allow_non_json_bodies=False):
|
|
73
|
-
request_data = self.json_body(False, allow_non_json_bodies=allow_non_json_bodies)
|
|
74
|
-
if not request_data:
|
|
75
|
-
if self.has_body() and not allow_non_json_bodies:
|
|
76
|
-
raise ClientError("Request body was not valid JSON")
|
|
77
|
-
request_data = {}
|
|
78
|
-
return request_data
|
|
79
|
-
|
|
80
|
-
def json_body(self, required=True, allow_non_json_bodies=False):
|
|
81
|
-
json = self._get_json_body()
|
|
82
|
-
# if we get None then either the body was not JSON or was empty.
|
|
83
|
-
# If it is required then we have an exception either way. If it is not required
|
|
84
|
-
# then we have an exception if a body was provided but it was not JSON. We can check for this
|
|
85
|
-
# if json is None and there is an actual request body. If json is none, the body is empty,
|
|
86
|
-
# and it was not required, then we can just return None
|
|
87
|
-
if json is None:
|
|
88
|
-
if required or (self.has_body() and not allow_non_json_bodies):
|
|
89
|
-
raise ClientError("Request body was not valid JSON")
|
|
90
|
-
return json
|
|
91
|
-
|
|
92
|
-
def _get_json_body(self):
|
|
54
|
+
@property
|
|
55
|
+
def request_data(self) -> dict[str, Any] | list[Any] | None:
|
|
56
|
+
"""Return the data from the request body, assuming it is JSON."""
|
|
93
57
|
if not self._body_loaded_as_json:
|
|
94
|
-
|
|
58
|
+
self._body_loaded_as_json = True
|
|
59
|
+
if not self.has_body():
|
|
95
60
|
self._body_as_json = None
|
|
96
61
|
else:
|
|
97
|
-
self._body_loaded_as_json = True
|
|
98
62
|
try:
|
|
99
63
|
self._body_as_json = json.loads(self.get_body())
|
|
100
64
|
except json.JSONDecodeError:
|
|
@@ -102,68 +66,73 @@ class InputOutput(ABC):
|
|
|
102
66
|
return self._body_as_json
|
|
103
67
|
|
|
104
68
|
@abstractmethod
|
|
105
|
-
def get_request_method(self):
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
@abstractmethod
|
|
109
|
-
def get_script_name(self):
|
|
110
|
-
pass
|
|
111
|
-
|
|
112
|
-
@abstractmethod
|
|
113
|
-
def get_path_info(self):
|
|
114
|
-
pass
|
|
115
|
-
|
|
116
|
-
def get_full_path(self):
|
|
117
|
-
path_info = self.get_path_info()
|
|
118
|
-
script_name = self.get_script_name()
|
|
119
|
-
if not path_info or path_info[0] != "/":
|
|
120
|
-
path_info = f"/{path_info}"
|
|
121
|
-
return f"{path_info}{script_name}".replace("//", "/")
|
|
122
|
-
|
|
123
|
-
@abstractmethod
|
|
124
|
-
def get_query_string(self):
|
|
69
|
+
def get_request_method(self) -> str:
|
|
70
|
+
"""Return the request method set by the client."""
|
|
125
71
|
pass
|
|
126
72
|
|
|
127
73
|
@abstractmethod
|
|
128
|
-
def
|
|
74
|
+
def get_script_name(self) -> str:
|
|
75
|
+
"""Return the script name, e.g. the path requested."""
|
|
129
76
|
pass
|
|
130
77
|
|
|
131
78
|
@abstractmethod
|
|
132
|
-
def
|
|
79
|
+
def get_path_info(self) -> str:
|
|
80
|
+
"""Return the path info for the request."""
|
|
133
81
|
pass
|
|
134
82
|
|
|
135
83
|
@abstractmethod
|
|
136
|
-
def
|
|
84
|
+
def get_query_string(self) -> str:
|
|
85
|
+
"""Return the full query string for the request (everything after the first question mark in the document URL)."""
|
|
137
86
|
pass
|
|
138
87
|
|
|
139
88
|
@abstractmethod
|
|
140
|
-
def
|
|
89
|
+
def get_client_ip(self):
|
|
141
90
|
pass
|
|
142
91
|
|
|
143
|
-
def _parse_query_parameters(self):
|
|
144
|
-
if self._query_parameters is None:
|
|
145
|
-
self._query_parameters = {
|
|
146
|
-
key: val[0] if len(val) == 1 else val
|
|
147
|
-
for (key, val) in urllib.parse.parse_qs(self.get_query_string()).items()
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
def get_query_parameter(self, key):
|
|
151
|
-
self._parse_query_parameters()
|
|
152
|
-
return self._query_parameters[key] if key in self._query_parameters else None
|
|
153
|
-
|
|
154
|
-
def get_query_parameters(self):
|
|
155
|
-
self._parse_query_parameters()
|
|
156
|
-
return self._query_parameters
|
|
157
|
-
|
|
158
92
|
@abstractmethod
|
|
159
|
-
def
|
|
93
|
+
def get_request_headers(self) -> dict[str, str]:
|
|
160
94
|
pass
|
|
161
95
|
|
|
162
|
-
def
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
96
|
+
def get_full_path(self) -> str:
|
|
97
|
+
"""Return the full path requested by the client."""
|
|
98
|
+
path_info = self.get_path_info()
|
|
99
|
+
script_name = self.get_script_name()
|
|
100
|
+
if not path_info or path_info[0] != "/":
|
|
101
|
+
path_info = f"/{path_info}"
|
|
102
|
+
return f"{path_info}{script_name}".replace("//", "/")
|
|
167
103
|
|
|
168
104
|
def context_specifics(self):
|
|
169
105
|
return {}
|
|
106
|
+
|
|
107
|
+
def get_context_for_callables(self) -> dict[str, Any]:
|
|
108
|
+
"""
|
|
109
|
+
Return a dictionary with various important parts of the request that are passed along to user-defined functions.
|
|
110
|
+
|
|
111
|
+
It's common to make various aspects of an incoming request available to user-defined functions that are
|
|
112
|
+
attached to clearskies hooks everywhere. This function centralizes the definition of what aspects of
|
|
113
|
+
the reequest shouuld be passed along to callables in this case. When this is in use it typically
|
|
114
|
+
looks like this:
|
|
115
|
+
|
|
116
|
+
di.call_function(some_function, **input_output.get_context_for_callables())
|
|
117
|
+
|
|
118
|
+
And this function returns a dictionary with the following values:
|
|
119
|
+
|
|
120
|
+
| Key | Type | Ref | Value |
|
|
121
|
+
|--------------------|----------------------------------|---------------------------------|---------------------------------------------------------------------------------|
|
|
122
|
+
| routing_data | dict[str, str] | input_output.routing_data | A dictionary of data extracted from URL path parameters. |
|
|
123
|
+
| authorization_data | dict[str, Any] | input_output.authorization_data | A dictionary containing the authorization data set by the authentication method |
|
|
124
|
+
| request_data | dict[str, Any] | None | input_output.request_data | The data sent along with the request (assuming a JSON request body) |
|
|
125
|
+
| query_parameters | dict[str, Any] | input_output.query_parameters | The query parameters |
|
|
126
|
+
| request_headers | clearskies.input_outputs.Headers | input_output.request_headers | The request headers sent by the client |
|
|
127
|
+
| **routing_data | string | **input_output.routing_data | The routing data is unpacked so keys can be fetched directly |
|
|
128
|
+
"""
|
|
129
|
+
return {
|
|
130
|
+
**self.routing_data,
|
|
131
|
+
**{
|
|
132
|
+
"routing_data": self.routing_data,
|
|
133
|
+
"authorization_data": self.authorization_data,
|
|
134
|
+
"request_data": self.request_data,
|
|
135
|
+
"request_headers": self.request_headers,
|
|
136
|
+
"query_parameters": self.query_parameters,
|
|
137
|
+
},
|
|
138
|
+
}
|
|
@@ -0,0 +1,69 @@
|
|
|
1
|
+
from typing import Any
|
|
2
|
+
|
|
3
|
+
from clearskies.input_outputs.input_output import InputOutput
|
|
4
|
+
|
|
5
|
+
from .headers import Headers
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Programmatic(InputOutput):
|
|
9
|
+
_body: str | dict[str, Any] | list[Any] = ""
|
|
10
|
+
_request_method: str = ""
|
|
11
|
+
_request_headers: dict[str, Any] = {}
|
|
12
|
+
url: str = ""
|
|
13
|
+
|
|
14
|
+
def __init__(
|
|
15
|
+
self,
|
|
16
|
+
url: str = "",
|
|
17
|
+
request_method: str = "GET",
|
|
18
|
+
body: str | dict[str, Any] | list[Any] = "",
|
|
19
|
+
query_parameters: dict[str, Any] = {},
|
|
20
|
+
request_headers: dict[str, str] = {},
|
|
21
|
+
):
|
|
22
|
+
self.url = url
|
|
23
|
+
self._request_headers = {**request_headers}
|
|
24
|
+
self._body_loaded_as_json = True
|
|
25
|
+
self._body_as_json = None
|
|
26
|
+
self._request_method = request_method
|
|
27
|
+
if body:
|
|
28
|
+
self._body = body
|
|
29
|
+
if isinstance(body, dict) or isinstance(body, list):
|
|
30
|
+
self._body_as_json = body
|
|
31
|
+
|
|
32
|
+
super().__init__()
|
|
33
|
+
self.query_parameters = {**query_parameters}
|
|
34
|
+
|
|
35
|
+
def respond(self, response, status_code=200):
|
|
36
|
+
return (status_code, response, self.response_headers)
|
|
37
|
+
|
|
38
|
+
def get_script_name(self):
|
|
39
|
+
return self.url
|
|
40
|
+
|
|
41
|
+
def get_path_info(self):
|
|
42
|
+
return self.url
|
|
43
|
+
|
|
44
|
+
def get_full_path(self):
|
|
45
|
+
return self.url
|
|
46
|
+
|
|
47
|
+
def get_request_method(self):
|
|
48
|
+
return self._request_method
|
|
49
|
+
|
|
50
|
+
def has_body(self):
|
|
51
|
+
return bool(self._body)
|
|
52
|
+
|
|
53
|
+
def get_body(self):
|
|
54
|
+
if not self.has_body():
|
|
55
|
+
return ""
|
|
56
|
+
|
|
57
|
+
return self._body
|
|
58
|
+
|
|
59
|
+
def context_specifics(self):
|
|
60
|
+
return {}
|
|
61
|
+
|
|
62
|
+
def get_client_ip(self):
|
|
63
|
+
return "127.0.0.1"
|
|
64
|
+
|
|
65
|
+
def get_query_string(self):
|
|
66
|
+
return ""
|
|
67
|
+
|
|
68
|
+
def get_request_headers(self):
|
|
69
|
+
return self._request_headers
|
clearskies/input_outputs/wsgi.py
CHANGED
|
@@ -1,14 +1,16 @@
|
|
|
1
|
-
from .input_output import InputOutput
|
|
2
|
-
import urllib, urllib.parse
|
|
3
1
|
import json
|
|
2
|
+
import urllib
|
|
3
|
+
import urllib.parse
|
|
4
|
+
from typing import Callable
|
|
5
|
+
|
|
6
|
+
from .input_output import InputOutput
|
|
4
7
|
|
|
5
8
|
|
|
6
|
-
class
|
|
7
|
-
_environment =
|
|
8
|
-
_start_response = None
|
|
9
|
-
_request_headers =
|
|
10
|
-
_cached_body = None
|
|
11
|
-
_query_parameters = None
|
|
9
|
+
class Wsgi(InputOutput):
|
|
10
|
+
_environment: dict[str, str] = {}
|
|
11
|
+
_start_response: Callable = None # type: ignore
|
|
12
|
+
_request_headers: dict[str, str] = {}
|
|
13
|
+
_cached_body: str | None = None
|
|
12
14
|
|
|
13
15
|
def __init__(self, environment, start_response):
|
|
14
16
|
self._environment = environment
|
|
@@ -17,15 +19,16 @@ class WSGI(InputOutput):
|
|
|
17
19
|
for key, value in self._environment.items():
|
|
18
20
|
if key.upper()[0:5] == "HTTP_":
|
|
19
21
|
self._request_headers[key[5:].lower()] = value
|
|
22
|
+
super().__init__()
|
|
20
23
|
|
|
21
24
|
def _from_environment(self, key):
|
|
22
25
|
return self._environment[key] if key in self._environment else ""
|
|
23
26
|
|
|
24
27
|
def respond(self, body, status_code=200):
|
|
25
|
-
if
|
|
26
|
-
self.
|
|
28
|
+
if "content-type" not in self.response_headers:
|
|
29
|
+
self.response_headers.content_type = "application/json; charset=UTF-8"
|
|
27
30
|
|
|
28
|
-
self._start_response(f"{status_code} Ok", [header for header in self.
|
|
31
|
+
self._start_response(f"{status_code} Ok", [header for header in self.response_headers.items()]) # type: ignore
|
|
29
32
|
if type(body) == bytes:
|
|
30
33
|
final_body = body
|
|
31
34
|
elif type(body) == str:
|
|
@@ -35,11 +38,15 @@ class WSGI(InputOutput):
|
|
|
35
38
|
return [final_body]
|
|
36
39
|
|
|
37
40
|
def has_body(self):
|
|
38
|
-
return bool(self.
|
|
41
|
+
return bool(self._from_environment("CONTENT_LENGTH"))
|
|
39
42
|
|
|
40
43
|
def get_body(self):
|
|
41
44
|
if self._cached_body is None:
|
|
42
|
-
self._cached_body =
|
|
45
|
+
self._cached_body = (
|
|
46
|
+
self._from_environment("wsgi.input").read(int(self._from_environment("CONTENT_LENGTH"))).decode("utf-8")
|
|
47
|
+
if self._from_environment("CONTENT_LENGTH")
|
|
48
|
+
else ""
|
|
49
|
+
)
|
|
43
50
|
return self._cached_body
|
|
44
51
|
|
|
45
52
|
def get_request_method(self):
|
|
@@ -60,33 +67,11 @@ class WSGI(InputOutput):
|
|
|
60
67
|
def get_protocol(self):
|
|
61
68
|
return self._from_environment("wsgi.url_scheme").lower()
|
|
62
69
|
|
|
63
|
-
def has_request_header(self, header_name):
|
|
64
|
-
return header_name.lower() in self._request_headers
|
|
65
|
-
|
|
66
|
-
def get_request_header(self, header_name, silent=False):
|
|
67
|
-
if not header_name.lower() in self._request_headers:
|
|
68
|
-
if not silent:
|
|
69
|
-
raise KeyError(f"HTTP header '{header_name}' was not found in request")
|
|
70
|
-
return ""
|
|
71
|
-
return self._request_headers[header_name.lower()]
|
|
72
|
-
|
|
73
|
-
def _parse_query_parameters(self):
|
|
74
|
-
if self._query_parameters is None:
|
|
75
|
-
self._query_parameters = {
|
|
76
|
-
key: val[0] if len(val) == 1 else val
|
|
77
|
-
for (key, val) in urllib.parse.parse_qs(self.get_query_string()).items()
|
|
78
|
-
}
|
|
79
|
-
|
|
80
|
-
def get_query_parameter(self, key):
|
|
81
|
-
self._parse_query_parameters()
|
|
82
|
-
return self._query_parameters[key] if key in self._query_parameters else []
|
|
83
|
-
|
|
84
|
-
def get_query_parameters(self):
|
|
85
|
-
self._parse_query_parameters()
|
|
86
|
-
return self._query_parameters
|
|
87
|
-
|
|
88
70
|
def context_specifics(self):
|
|
89
71
|
return {"wsgi_environment": self._environment}
|
|
90
72
|
|
|
91
73
|
def get_client_ip(self):
|
|
92
74
|
return self._environment.get("REMOTE_ADDR")
|
|
75
|
+
|
|
76
|
+
def get_request_headers(self):
|
|
77
|
+
return self._request_headers
|