clear-skies 1.22.10__py3-none-any.whl → 2.0.23__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.
- clear_skies-2.0.23.dist-info/METADATA +76 -0
- clear_skies-2.0.23.dist-info/RECORD +265 -0
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info}/WHEEL +1 -1
- clearskies/__init__.py +37 -21
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +8 -39
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +14 -8
- clearskies/authentication/authorization_pass_through.py +14 -10
- clearskies/authentication/jwks.py +135 -58
- clearskies/authentication/public.py +3 -26
- clearskies/authentication/secret_bearer.py +515 -44
- clearskies/autodoc/formats/oai3_json/__init__.py +2 -2
- clearskies/autodoc/formats/oai3_json/oai3_json.py +11 -9
- 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 +10 -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 +16 -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 +1118 -280
- clearskies/backends/backend.py +54 -85
- clearskies/backends/cursor_backend.py +246 -191
- clearskies/backends/memory_backend.py +514 -208
- clearskies/backends/secrets_backend.py +68 -31
- clearskies/column.py +1221 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +306 -0
- clearskies/columns/belongs_to_id.py +478 -0
- clearskies/columns/belongs_to_model.py +129 -0
- clearskies/columns/belongs_to_self.py +109 -0
- clearskies/columns/boolean.py +110 -0
- clearskies/columns/category_tree.py +273 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +126 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +92 -0
- clearskies/columns/created_by_authorization_data.py +114 -0
- clearskies/columns/created_by_header.py +103 -0
- clearskies/columns/created_by_ip.py +90 -0
- clearskies/columns/created_by_routing_data.py +102 -0
- clearskies/columns/created_by_user_agent.py +89 -0
- clearskies/columns/date.py +232 -0
- clearskies/columns/datetime.py +284 -0
- clearskies/columns/email.py +78 -0
- clearskies/columns/float.py +149 -0
- clearskies/columns/has_many.py +529 -0
- clearskies/columns/has_many_self.py +62 -0
- clearskies/columns/has_one.py +21 -0
- clearskies/columns/integer.py +158 -0
- clearskies/columns/json.py +126 -0
- clearskies/columns/many_to_many_ids.py +335 -0
- clearskies/columns/many_to_many_ids_with_data.py +274 -0
- clearskies/columns/many_to_many_models.py +156 -0
- clearskies/columns/many_to_many_pivots.py +132 -0
- clearskies/columns/phone.py +162 -0
- clearskies/columns/select.py +95 -0
- clearskies/columns/string.py +102 -0
- clearskies/columns/timestamp.py +164 -0
- clearskies/columns/updated.py +107 -0
- clearskies/columns/uuid.py +83 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +170 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +15 -0
- clearskies/configs/any_dict.py +24 -0
- clearskies/configs/any_dict_or_callable.py +25 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +18 -0
- clearskies/configs/boolean_or_callable.py +20 -0
- clearskies/configs/callable_config.py +20 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +26 -0
- clearskies/configs/datetime.py +20 -0
- clearskies/configs/datetime_or_callable.py +21 -0
- clearskies/configs/email.py +10 -0
- clearskies/configs/email_list.py +17 -0
- clearskies/configs/email_list_or_callable.py +17 -0
- clearskies/configs/email_or_email_list_or_callable.py +59 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +29 -0
- clearskies/configs/float.py +18 -0
- clearskies/configs/float_or_callable.py +20 -0
- clearskies/configs/headers.py +28 -0
- clearskies/configs/integer.py +18 -0
- clearskies/configs/integer_or_callable.py +20 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +32 -0
- clearskies/configs/list_any_dict_or_callable.py +33 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +67 -0
- clearskies/configs/model_columns.py +58 -0
- clearskies/configs/model_destination_name.py +26 -0
- clearskies/configs/model_to_id_column.py +45 -0
- clearskies/configs/readable_model_column.py +11 -0
- clearskies/configs/readable_model_columns.py +11 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +11 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +28 -0
- clearskies/configs/select_list.py +49 -0
- clearskies/configs/string.py +31 -0
- clearskies/configs/string_dict.py +34 -0
- clearskies/configs/string_list.py +47 -0
- clearskies/configs/string_list_or_callable.py +48 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +20 -0
- clearskies/configs/timezone.py +20 -0
- clearskies/configs/url.py +25 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +11 -0
- clearskies/configs/writeable_model_columns.py +11 -0
- clearskies/configurable.py +78 -0
- clearskies/contexts/__init__.py +8 -8
- clearskies/contexts/cli.py +129 -43
- clearskies/contexts/context.py +93 -56
- clearskies/contexts/wsgi.py +79 -33
- clearskies/contexts/wsgi_ref.py +87 -0
- clearskies/cursors/__init__.py +7 -0
- clearskies/cursors/cursor.py +166 -0
- clearskies/cursors/from_environment/__init__.py +5 -0
- clearskies/cursors/from_environment/mysql.py +51 -0
- clearskies/cursors/from_environment/postgresql.py +49 -0
- clearskies/cursors/from_environment/sqlite.py +35 -0
- clearskies/cursors/mysql.py +61 -0
- clearskies/cursors/postgresql.py +61 -0
- clearskies/cursors/sqlite.py +62 -0
- clearskies/decorators.py +33 -0
- clearskies/decorators.pyi +10 -0
- clearskies/di/__init__.py +11 -7
- clearskies/di/additional_config.py +115 -4
- clearskies/di/additional_config_auto_import.py +12 -0
- clearskies/di/di.py +714 -125
- clearskies/di/inject/__init__.py +23 -0
- clearskies/di/inject/akeyless_sdk.py +16 -0
- clearskies/di/inject/by_class.py +24 -0
- clearskies/di/inject/by_name.py +22 -0
- clearskies/di/inject/di.py +16 -0
- clearskies/di/inject/environment.py +15 -0
- clearskies/di/inject/input_output.py +19 -0
- clearskies/di/inject/now.py +16 -0
- clearskies/di/inject/requests.py +16 -0
- clearskies/di/inject/secrets.py +15 -0
- clearskies/di/inject/utcnow.py +16 -0
- clearskies/di/inject/uuid.py +16 -0
- clearskies/di/injectable.py +32 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/end.py +219 -0
- clearskies/endpoint.py +1303 -0
- clearskies/endpoint_group.py +333 -0
- clearskies/endpoints/__init__.py +25 -0
- clearskies/endpoints/advanced_search.py +519 -0
- clearskies/endpoints/callable.py +382 -0
- clearskies/endpoints/create.py +201 -0
- clearskies/endpoints/delete.py +133 -0
- clearskies/endpoints/get.py +267 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +567 -0
- clearskies/endpoints/restful_api.py +417 -0
- clearskies/endpoints/schema.py +185 -0
- clearskies/endpoints/simple_search.py +279 -0
- clearskies/endpoints/update.py +188 -0
- clearskies/environment.py +7 -3
- clearskies/exceptions/__init__.py +19 -0
- clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/functional/__init__.py +2 -2
- clearskies/functional/json.py +47 -0
- 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 +135 -160
- clearskies/input_outputs/exceptions/__init__.py +6 -1
- clearskies/input_outputs/headers.py +54 -0
- clearskies/input_outputs/input_output.py +77 -123
- clearskies/input_outputs/programmatic.py +62 -0
- clearskies/input_outputs/wsgi.py +36 -48
- clearskies/model.py +1874 -193
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +228 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +193 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +4 -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 +421 -155
- clearskies/secrets/exceptions/__init__.py +7 -1
- clearskies/secrets/exceptions/not_found_error.py +2 -0
- clearskies/secrets/exceptions/permissions_error.py +2 -0
- clearskies/secrets/secrets.py +12 -11
- clearskies/security_header.py +17 -0
- clearskies/security_headers/__init__.py +8 -8
- clearskies/security_headers/cache_control.py +47 -109
- clearskies/security_headers/cors.py +38 -92
- clearskies/security_headers/csp.py +76 -150
- clearskies/security_headers/hsts.py +14 -15
- clearskies/typing.py +11 -0
- clearskies/validator.py +36 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +61 -0
- clearskies/validators/before_column.py +15 -0
- clearskies/validators/in_the_future.py +29 -0
- clearskies/validators/in_the_future_at_least.py +13 -0
- clearskies/validators/in_the_future_at_most.py +12 -0
- clearskies/validators/in_the_past.py +29 -0
- clearskies/validators/in_the_past_at_least.py +12 -0
- clearskies/validators/in_the_past_at_most.py +12 -0
- clearskies/validators/maximum_length.py +25 -0
- clearskies/validators/maximum_value.py +28 -0
- clearskies/validators/minimum_length.py +25 -0
- clearskies/validators/minimum_value.py +28 -0
- clearskies/{input_requirements → validators}/required.py +18 -9
- clearskies/validators/timedelta.py +58 -0
- clearskies/validators/unique.py +28 -0
- clear_skies-1.22.10.dist-info/METADATA +0 -47
- clear_skies-1.22.10.dist-info/RECORD +0 -213
- 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 -13
- 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 -58
- 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 -39
- 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/di/test_module/__init__.py +0 -6
- clearskies/di/test_module/another_module/__init__.py +0 -2
- clearskies/di/test_module/module_class.py +0 -5
- clearskies/handlers/__init__.py +0 -41
- clearskies/handlers/advanced_search.py +0 -271
- clearskies/handlers/base.py +0 -479
- clearskies/handlers/callable.py +0 -191
- 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 -192
- clearskies/handlers/simple_search.py +0 -136
- clearskies/handlers/update.py +0 -96
- 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/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.10.dist-info → clear_skies-2.0.23.dist-info/licenses}/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/{secrets/exceptions → exceptions}/not_found.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
|
|
2
|
-
|
|
3
|
-
from ..handlers.exceptions import ClientError
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
4
3
|
import json
|
|
4
|
+
from abc import ABC, abstractmethod
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
5
6
|
|
|
7
|
+
from clearskies import configs, configurable
|
|
6
8
|
|
|
7
|
-
|
|
8
|
-
_response_headers = None
|
|
9
|
-
_body_as_json = None
|
|
10
|
-
_body_loaded_as_json = False
|
|
11
|
-
_routing_data = None
|
|
12
|
-
_authorization_data = None
|
|
9
|
+
from .headers import Headers
|
|
13
10
|
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
pass
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from clearskies import typing
|
|
17
13
|
|
|
18
|
-
def error(self, body):
|
|
19
|
-
return self.respond(body, 400)
|
|
20
14
|
|
|
21
|
-
|
|
22
|
-
|
|
15
|
+
class InputOutput(ABC, configurable.Configurable):
|
|
16
|
+
"""Manage the request and response to the client."""
|
|
23
17
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
18
|
+
response_headers = configs.Headers(default=Headers())
|
|
19
|
+
request_headers = configs.Headers(default=Headers())
|
|
20
|
+
query_parameters = configs.AnyDict(default={})
|
|
21
|
+
routing_data = configs.StringDict(default={})
|
|
22
|
+
authorization_data = configs.AnyDict(default={})
|
|
23
|
+
request_method = configs.Select(["GET", "POST", "PATCH", "OPTIONS", "DELETE", "SEARCH"], default="GET")
|
|
24
|
+
supports_request_method = configs.Boolean(default=True)
|
|
25
|
+
supports_url = configs.Boolean(default=True)
|
|
31
26
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
self._response_headers = OrderedDict()
|
|
35
|
-
self._response_headers[key.upper()] = value
|
|
27
|
+
_body_as_json: dict[str, Any] | list[Any] | None = {}
|
|
28
|
+
_body_loaded_as_json = False
|
|
36
29
|
|
|
37
|
-
def
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
if self.has_header(key):
|
|
41
|
-
del self._response_headers[key.upper()]
|
|
30
|
+
def __init__(self):
|
|
31
|
+
self.response_headers = Headers()
|
|
32
|
+
self.finalize_and_validate_configuration()
|
|
42
33
|
|
|
43
|
-
|
|
44
|
-
|
|
34
|
+
@abstractmethod
|
|
35
|
+
def respond(self, body: 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):
|
|
73
|
-
request_data = self.json_body(False)
|
|
74
|
-
if not request_data:
|
|
75
|
-
if self.has_body():
|
|
76
|
-
raise ClientError("Request body was not valid JSON")
|
|
77
|
-
request_data = {}
|
|
78
|
-
return request_data
|
|
79
|
-
|
|
80
|
-
def json_body(self, required=True):
|
|
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():
|
|
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,30 +66,7 @@ class InputOutput(ABC):
|
|
|
102
66
|
return self._body_as_json
|
|
103
67
|
|
|
104
68
|
@abstractmethod
|
|
105
|
-
def
|
|
106
|
-
pass
|
|
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):
|
|
125
|
-
pass
|
|
126
|
-
|
|
127
|
-
@abstractmethod
|
|
128
|
-
def get_content_type(self):
|
|
69
|
+
def get_client_ip(self):
|
|
129
70
|
pass
|
|
130
71
|
|
|
131
72
|
@abstractmethod
|
|
@@ -133,30 +74,43 @@ class InputOutput(ABC):
|
|
|
133
74
|
pass
|
|
134
75
|
|
|
135
76
|
@abstractmethod
|
|
136
|
-
def
|
|
137
|
-
pass
|
|
138
|
-
|
|
139
|
-
@abstractmethod
|
|
140
|
-
def get_request_header(self, header_name, silent=True):
|
|
141
|
-
pass
|
|
142
|
-
|
|
143
|
-
@abstractmethod
|
|
144
|
-
def get_query_parameter(self, key):
|
|
77
|
+
def get_full_path(self) -> str:
|
|
145
78
|
pass
|
|
146
79
|
|
|
147
|
-
@abstractmethod
|
|
148
|
-
def get_query_parameters(self):
|
|
149
|
-
pass
|
|
150
|
-
|
|
151
|
-
@abstractmethod
|
|
152
|
-
def get_client_ip(self):
|
|
153
|
-
pass
|
|
154
|
-
|
|
155
|
-
def set_authorization_data(self, data):
|
|
156
|
-
self._authorization_data = data
|
|
157
|
-
|
|
158
|
-
def get_authorization_data(self):
|
|
159
|
-
return self._authorization_data if self._authorization_data else {}
|
|
160
|
-
|
|
161
80
|
def context_specifics(self):
|
|
162
81
|
return {}
|
|
82
|
+
|
|
83
|
+
def get_context_for_callables(self) -> dict[str, Any]:
|
|
84
|
+
"""
|
|
85
|
+
Return a dictionary with various important parts of the request that are passed along to user-defined functions.
|
|
86
|
+
|
|
87
|
+
It's common to make various aspects of an incoming request available to user-defined functions that are
|
|
88
|
+
attached to clearskies hooks everywhere. This function centralizes the definition of what aspects of
|
|
89
|
+
the reequest shouuld be passed along to callables in this case. When this is in use it typically
|
|
90
|
+
looks like this:
|
|
91
|
+
|
|
92
|
+
di.call_function(some_function, **input_output.get_context_for_callables())
|
|
93
|
+
|
|
94
|
+
And this function returns a dictionary with the following values:
|
|
95
|
+
|
|
96
|
+
| Key | Type | Ref | Value |
|
|
97
|
+
|--------------------|----------------------------------|------------------------------------|---------------------------------------------------------------------------------|
|
|
98
|
+
| routing_data | dict[str, str] | input_output.routing_data | A dictionary of data extracted from URL path parameters. |
|
|
99
|
+
| authorization_data | dict[str, Any] | input_output.authorization_data | A dictionary containing the authorization data set by the authentication method |
|
|
100
|
+
| request_data | dict[str, Any] | None | input_output.request_data | The data sent along with the request (assuming a JSON request body) |
|
|
101
|
+
| query_parameters | dict[str, Any] | input_output.query_parameters | The query parameters |
|
|
102
|
+
| request_headers | clearskies.input_outputs.Headers | input_output.request_headers | The request headers sent by the client |
|
|
103
|
+
| **routing_data | string | **input_output.routing_data | The routing data is unpacked so keys can be fetched directly |
|
|
104
|
+
| **[varies] | varies | **input_output.context_specifics() | Any additional properties added on by the context (see your context docs) |
|
|
105
|
+
"""
|
|
106
|
+
return {
|
|
107
|
+
**self.routing_data,
|
|
108
|
+
**self.context_specifics(),
|
|
109
|
+
**{
|
|
110
|
+
"routing_data": self.routing_data,
|
|
111
|
+
"authorization_data": self.authorization_data,
|
|
112
|
+
"request_data": self.request_data,
|
|
113
|
+
"request_headers": self.request_headers,
|
|
114
|
+
"query_parameters": self.query_parameters,
|
|
115
|
+
},
|
|
116
|
+
}
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any
|
|
4
|
+
|
|
5
|
+
from clearskies.input_outputs.headers import Headers
|
|
6
|
+
from clearskies.input_outputs.input_output import InputOutput
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
class Programmatic(InputOutput):
|
|
10
|
+
_body: str | dict[str, Any] | list[Any] = ""
|
|
11
|
+
url: str = ""
|
|
12
|
+
ip_address: str = "127.0.0.1"
|
|
13
|
+
protocol: str = "https"
|
|
14
|
+
|
|
15
|
+
def __init__(
|
|
16
|
+
self,
|
|
17
|
+
url: str = "",
|
|
18
|
+
request_method: str = "GET",
|
|
19
|
+
body: str | dict[str, Any] | list[Any] = "",
|
|
20
|
+
query_parameters: dict[str, Any] = {},
|
|
21
|
+
request_headers: dict[str, str] = {},
|
|
22
|
+
ip_address: str = "127.0.0.1",
|
|
23
|
+
protocol: str = "https",
|
|
24
|
+
):
|
|
25
|
+
self.url = url
|
|
26
|
+
self.request_headers = Headers(request_headers)
|
|
27
|
+
self.query_parameters = query_parameters
|
|
28
|
+
self.ip_address = ip_address
|
|
29
|
+
self.protocol = protocol
|
|
30
|
+
self._body_loaded_as_json = True
|
|
31
|
+
self._body_as_json = None
|
|
32
|
+
self.request_method = request_method
|
|
33
|
+
if body:
|
|
34
|
+
self._body = body
|
|
35
|
+
if isinstance(body, dict) or isinstance(body, list):
|
|
36
|
+
self._body_as_json = body
|
|
37
|
+
|
|
38
|
+
super().__init__()
|
|
39
|
+
|
|
40
|
+
def respond(self, response, status_code=200):
|
|
41
|
+
return (status_code, response, self.response_headers)
|
|
42
|
+
|
|
43
|
+
def get_full_path(self):
|
|
44
|
+
return self.url
|
|
45
|
+
|
|
46
|
+
def has_body(self):
|
|
47
|
+
return bool(self._body)
|
|
48
|
+
|
|
49
|
+
def get_body(self):
|
|
50
|
+
if not self.has_body():
|
|
51
|
+
return ""
|
|
52
|
+
|
|
53
|
+
return self._body
|
|
54
|
+
|
|
55
|
+
def context_specifics(self):
|
|
56
|
+
return {}
|
|
57
|
+
|
|
58
|
+
def get_client_ip(self):
|
|
59
|
+
return self.ip_address
|
|
60
|
+
|
|
61
|
+
def get_protocol(self):
|
|
62
|
+
return self.protocol
|
clearskies/input_outputs/wsgi.py
CHANGED
|
@@ -1,31 +1,42 @@
|
|
|
1
|
-
from
|
|
2
|
-
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
3
|
import json
|
|
4
|
+
from typing import Callable
|
|
5
|
+
from urllib.parse import parse_qs
|
|
6
|
+
|
|
7
|
+
from clearskies.input_outputs.headers import Headers
|
|
8
|
+
from clearskies.input_outputs.input_output import InputOutput
|
|
4
9
|
|
|
5
10
|
|
|
6
|
-
class
|
|
7
|
-
_environment =
|
|
8
|
-
_start_response
|
|
9
|
-
|
|
10
|
-
_cached_body = None
|
|
11
|
-
_query_parameters = None
|
|
11
|
+
class Wsgi(InputOutput):
|
|
12
|
+
_environment: dict[str, str] = {}
|
|
13
|
+
_start_response: Callable
|
|
14
|
+
_cached_body: str | None = None
|
|
12
15
|
|
|
13
16
|
def __init__(self, environment, start_response):
|
|
14
17
|
self._environment = environment
|
|
15
18
|
self._start_response = start_response
|
|
16
|
-
|
|
19
|
+
request_headers = {}
|
|
17
20
|
for key, value in self._environment.items():
|
|
18
21
|
if key.upper()[0:5] == "HTTP_":
|
|
19
|
-
|
|
22
|
+
request_headers[key[5:].lower()] = value
|
|
23
|
+
|
|
24
|
+
self.request_headers = Headers(request_headers)
|
|
25
|
+
self.query_parameters = {
|
|
26
|
+
key: val[0] for (key, val) in parse_qs(self._environment.get("QUERY_STRING", "")).items()
|
|
27
|
+
}
|
|
28
|
+
self.request_method = self._environment.get("REQUEST_METHOD").upper()
|
|
29
|
+
|
|
30
|
+
super().__init__()
|
|
20
31
|
|
|
21
32
|
def _from_environment(self, key):
|
|
22
33
|
return self._environment[key] if key in self._environment else ""
|
|
23
34
|
|
|
24
35
|
def respond(self, body, status_code=200):
|
|
25
|
-
if
|
|
26
|
-
self.
|
|
36
|
+
if "content-type" not in self.response_headers:
|
|
37
|
+
self.response_headers.content_type = "application/json; charset=UTF-8"
|
|
27
38
|
|
|
28
|
-
self._start_response(f"{status_code} Ok", [header for header in self.
|
|
39
|
+
self._start_response(f"{status_code} Ok", [header for header in self.response_headers.items()]) # type: ignore
|
|
29
40
|
if type(body) == bytes:
|
|
30
41
|
final_body = body
|
|
31
42
|
elif type(body) == str:
|
|
@@ -35,56 +46,33 @@ class WSGI(InputOutput):
|
|
|
35
46
|
return [final_body]
|
|
36
47
|
|
|
37
48
|
def has_body(self):
|
|
38
|
-
return bool(self.
|
|
49
|
+
return bool(self._from_environment("CONTENT_LENGTH"))
|
|
39
50
|
|
|
40
51
|
def get_body(self):
|
|
41
52
|
if self._cached_body is None:
|
|
42
|
-
self._cached_body =
|
|
53
|
+
self._cached_body = (
|
|
54
|
+
self._from_environment("wsgi.input").read(int(self._from_environment("CONTENT_LENGTH"))).decode("utf-8")
|
|
55
|
+
if self._from_environment("CONTENT_LENGTH")
|
|
56
|
+
else ""
|
|
57
|
+
)
|
|
43
58
|
return self._cached_body
|
|
44
59
|
|
|
45
|
-
def get_request_method(self):
|
|
46
|
-
return self._from_environment("REQUEST_METHOD").upper()
|
|
47
|
-
|
|
48
60
|
def get_script_name(self):
|
|
49
61
|
return self._from_environment("SCRIPT_NAME")
|
|
50
62
|
|
|
51
63
|
def get_path_info(self):
|
|
52
64
|
return self._from_environment("PATH_INFO")
|
|
53
65
|
|
|
54
|
-
def
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
66
|
+
def get_full_path(self):
|
|
67
|
+
path_info = self.get_path_info()
|
|
68
|
+
script_name = self.get_script_name()
|
|
69
|
+
if not path_info or path_info[0] != "/":
|
|
70
|
+
path_info = f"/{path_info}"
|
|
71
|
+
return f"{path_info}{script_name}".replace("//", "/")
|
|
59
72
|
|
|
60
73
|
def get_protocol(self):
|
|
61
74
|
return self._from_environment("wsgi.url_scheme").lower()
|
|
62
75
|
|
|
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
76
|
def context_specifics(self):
|
|
89
77
|
return {"wsgi_environment": self._environment}
|
|
90
78
|
|