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
clearskies/handlers/base.py
DELETED
|
@@ -1,479 +0,0 @@
|
|
|
1
|
-
from abc import ABC, abstractmethod
|
|
2
|
-
from . import exceptions
|
|
3
|
-
from collections import OrderedDict
|
|
4
|
-
import inspect
|
|
5
|
-
import re
|
|
6
|
-
from ..autodoc.schema import Integer as AutoDocInteger
|
|
7
|
-
from ..autodoc.schema import String as AutoDocString
|
|
8
|
-
from ..autodoc.schema import Object as AutoDocObject
|
|
9
|
-
from ..autodoc.response import Response as AutoDocResponse
|
|
10
|
-
from ..functional import string
|
|
11
|
-
from typing import List, Dict
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
class Base(ABC):
|
|
15
|
-
_configuration = None
|
|
16
|
-
_configuration_defaults = {}
|
|
17
|
-
_as_json_map = None
|
|
18
|
-
_global_configuration_defaults = {
|
|
19
|
-
"base_url": "",
|
|
20
|
-
"response_headers": None,
|
|
21
|
-
"authentication": None,
|
|
22
|
-
"authorization": None,
|
|
23
|
-
"output_map": None,
|
|
24
|
-
"column_overrides": None,
|
|
25
|
-
"id_column_name": None,
|
|
26
|
-
"doc_description": "",
|
|
27
|
-
"internal_casing": "",
|
|
28
|
-
"external_casing": "",
|
|
29
|
-
"security_headers": None,
|
|
30
|
-
}
|
|
31
|
-
_di = None
|
|
32
|
-
_configuration = None
|
|
33
|
-
_cors_header = None
|
|
34
|
-
has_cors = False
|
|
35
|
-
|
|
36
|
-
def __init__(self, di):
|
|
37
|
-
self._di = di
|
|
38
|
-
self._configuration = None
|
|
39
|
-
|
|
40
|
-
@abstractmethod
|
|
41
|
-
def handle(self):
|
|
42
|
-
pass
|
|
43
|
-
|
|
44
|
-
def configure(self, configuration):
|
|
45
|
-
for key in configuration.keys():
|
|
46
|
-
if key not in self._configuration_defaults and key not in self._global_configuration_defaults:
|
|
47
|
-
class_name = self.__class__.__name__
|
|
48
|
-
raise KeyError(f"Attempt to set unknown configuration setting '{key}' for handler '{class_name}'")
|
|
49
|
-
|
|
50
|
-
self._check_configuration(configuration)
|
|
51
|
-
self._configuration = self._finalize_configuration(self.apply_default_configuration(configuration))
|
|
52
|
-
|
|
53
|
-
def _check_configuration(self, configuration):
|
|
54
|
-
if not "authentication" in configuration:
|
|
55
|
-
raise KeyError(
|
|
56
|
-
f"You must provide authentication in the configuration for handler '{self.__class__.__name__}'"
|
|
57
|
-
)
|
|
58
|
-
if configuration.get("authorization", None):
|
|
59
|
-
# authorization can be a function (in which case we'll just call it for gating) or it can be an object
|
|
60
|
-
# with 'gate' and 'filter_models' attributes, per the authentication.authorization base class
|
|
61
|
-
# or it can be a binding config
|
|
62
|
-
authorization = configuration["authorization"]
|
|
63
|
-
if type(authorization) == str:
|
|
64
|
-
# if we have a binding name then we need to build the authorization object
|
|
65
|
-
authorization = self._di.build(authorization, cache=True)
|
|
66
|
-
elif hasattr(authorization, "object_class"):
|
|
67
|
-
# if it's a binding config then pull out the target class, since we just need to check attributes here
|
|
68
|
-
authorization = authorization.object_class
|
|
69
|
-
is_callable = callable(authorization)
|
|
70
|
-
gates_or_filters = hasattr(authorization, "gate") or hasattr(authorization, "filter_models")
|
|
71
|
-
if not is_callable and not gates_or_filters:
|
|
72
|
-
raise ValueError("'authorization' should be a callable or a provide 'gate' or 'filter_models' methods")
|
|
73
|
-
if configuration.get("output_map") is not None:
|
|
74
|
-
if not callable(configuration["output_map"]):
|
|
75
|
-
raise ValueError("'output_map' should be a callable")
|
|
76
|
-
number_casings = 0
|
|
77
|
-
internal_casing = configuration.get("internal_casing")
|
|
78
|
-
if internal_casing and internal_casing not in string.casings:
|
|
79
|
-
raise ValueError(
|
|
80
|
-
f"Invalid internal_casing config for handler '{self.__class__.__name__}': expected one of "
|
|
81
|
-
+ "'"
|
|
82
|
-
+ ", '".join(string.casings)
|
|
83
|
-
+ f"' but found '{internal_casing}'"
|
|
84
|
-
)
|
|
85
|
-
number_casings += 1
|
|
86
|
-
external_casing = configuration.get("external_casing")
|
|
87
|
-
if external_casing and external_casing not in string.casings:
|
|
88
|
-
raise ValueError(
|
|
89
|
-
f"Invalid external_casing config for handler '{self.__class__.__name__}': expected one of "
|
|
90
|
-
+ "'"
|
|
91
|
-
+ ", '".join(string.casings)
|
|
92
|
-
+ f"' but found '{external_casing}'"
|
|
93
|
-
)
|
|
94
|
-
number_casings += 1
|
|
95
|
-
if number_casings == 1:
|
|
96
|
-
raise ValueError(
|
|
97
|
-
f"Configuration error for handler '{self.__class__.__name__}': external_casing and internal_casing"
|
|
98
|
-
+ " must be specified together, but only one was found"
|
|
99
|
-
)
|
|
100
|
-
if "base_url" in configuration and configuration["base_url"] != None and type(configuration["base_url"]) != str:
|
|
101
|
-
raise ValueError(
|
|
102
|
-
f"Configuration error for handler '{self.__class__.__name__}': if provided, base_url must be a string"
|
|
103
|
-
)
|
|
104
|
-
|
|
105
|
-
def apply_default_configuration(self, configuration):
|
|
106
|
-
return {
|
|
107
|
-
**self._global_configuration_defaults,
|
|
108
|
-
**self._configuration_defaults,
|
|
109
|
-
**configuration,
|
|
110
|
-
}
|
|
111
|
-
|
|
112
|
-
def configuration(self, key):
|
|
113
|
-
if self._configuration is None:
|
|
114
|
-
raise ValueError("Cannot fetch configuration values before setting the configuration")
|
|
115
|
-
if key not in self._configuration:
|
|
116
|
-
class_name = self.__class__.__name__
|
|
117
|
-
raise KeyError(f"Configuration key '{key}' does not exist for handler '{class_name}'")
|
|
118
|
-
return self._configuration[key]
|
|
119
|
-
|
|
120
|
-
def _finalize_configuration(self, configuration):
|
|
121
|
-
configuration["authentication"] = self._di.build(configuration["authentication"], cache=True)
|
|
122
|
-
authorization = configuration.get("authorization")
|
|
123
|
-
if authorization and (hasattr(authorization, "object_class") or type(authorization) == str):
|
|
124
|
-
configuration["authorization"] = self._di.build(configuration["authorization"], cache=True)
|
|
125
|
-
if configuration.get("base_url") is None:
|
|
126
|
-
configuration["base_url"] = "/"
|
|
127
|
-
if not configuration["base_url"] or configuration["base_url"][0] != "/":
|
|
128
|
-
configuration["base_url"] = "/" + configuration["base_url"]
|
|
129
|
-
security_headers = configuration.get("security_headers")
|
|
130
|
-
if not security_headers:
|
|
131
|
-
configuration["security_headers"] = []
|
|
132
|
-
else:
|
|
133
|
-
# should be a list or a binding config. If it's a binding config, convert it to a list
|
|
134
|
-
if hasattr(security_headers, "object_class"):
|
|
135
|
-
security_headers = [security_headers]
|
|
136
|
-
if type(security_headers) != list:
|
|
137
|
-
raise ValueError(
|
|
138
|
-
f"Configuration error for handler '{self.__class__.__name__}': if provided, security_headers must be a list or binding config"
|
|
139
|
-
)
|
|
140
|
-
final_security_headers = []
|
|
141
|
-
for index, security_header in enumerate(security_headers):
|
|
142
|
-
if hasattr(security_header, "object_class"):
|
|
143
|
-
security_header = self._di.build(security_header, cache=True)
|
|
144
|
-
if not hasattr(security_header, "set_headers_for_input_output"):
|
|
145
|
-
raise ValueError(
|
|
146
|
-
f"Configuration error for handler '{self.__class__.__name__}': security header #{index+1} did not resolve to a security header"
|
|
147
|
-
)
|
|
148
|
-
if security_header.is_cors:
|
|
149
|
-
self._cors_header = security_header
|
|
150
|
-
self.has_cors = True
|
|
151
|
-
final_security_headers.append(security_header)
|
|
152
|
-
configuration["security_headers"] = final_security_headers
|
|
153
|
-
return configuration
|
|
154
|
-
|
|
155
|
-
def top_level_authentication_and_authorization(self, input_output, authentication=None):
|
|
156
|
-
if authentication is None:
|
|
157
|
-
authentication = self._configuration.get("authentication")
|
|
158
|
-
if not authentication:
|
|
159
|
-
return
|
|
160
|
-
try:
|
|
161
|
-
if not authentication.authenticate(input_output):
|
|
162
|
-
raise exceptions.Authentication("Not Authenticated")
|
|
163
|
-
except exceptions.ClientError as client_error:
|
|
164
|
-
raise exceptions.Authentication(str(client_error))
|
|
165
|
-
authorization = self._configuration.get("authorization")
|
|
166
|
-
if authorization:
|
|
167
|
-
authorization_data = input_output.get_authorization_data()
|
|
168
|
-
try:
|
|
169
|
-
allowed = True
|
|
170
|
-
if hasattr(authorization, "gate"):
|
|
171
|
-
allowed = authorization.gate(authorization_data, input_output)
|
|
172
|
-
elif callable(authorization):
|
|
173
|
-
allowed = authorization(authorization_data, input_output)
|
|
174
|
-
if not allowed:
|
|
175
|
-
raise exceptions.Authorization("Not Authorized")
|
|
176
|
-
except exceptions.ClientError as client_error:
|
|
177
|
-
raise exception.Authorization(str(client_error))
|
|
178
|
-
|
|
179
|
-
def __call__(self, input_output):
|
|
180
|
-
self._di.bind("input_output", input_output)
|
|
181
|
-
if self._configuration is None:
|
|
182
|
-
raise ValueError("Must configure handler before calling")
|
|
183
|
-
try:
|
|
184
|
-
self.top_level_authentication_and_authorization(input_output)
|
|
185
|
-
except exceptions.Authentication as auth_error:
|
|
186
|
-
return self.error(input_output, str(auth_error), 401)
|
|
187
|
-
except exceptions.Authorization as auth_error:
|
|
188
|
-
return self.error(input_output, str(auth_error), 403)
|
|
189
|
-
except exceptions.NotFound as auth_error:
|
|
190
|
-
return self.error(input_output, str(auth_error), 404)
|
|
191
|
-
|
|
192
|
-
try:
|
|
193
|
-
response = self.handle(input_output)
|
|
194
|
-
except exceptions.ClientError as client_error:
|
|
195
|
-
return self.error(input_output, str(client_error), 400)
|
|
196
|
-
except exceptions.InputError as input_error:
|
|
197
|
-
return self.input_errors(input_output, input_error.errors)
|
|
198
|
-
except exceptions.Authentication as auth_error:
|
|
199
|
-
return self.error(input_output, str(auth_error), 401)
|
|
200
|
-
except exceptions.Authorization as auth_error:
|
|
201
|
-
return self.error(input_output, str(auth_error), 403)
|
|
202
|
-
except exceptions.NotFound as auth_error:
|
|
203
|
-
return self.error(input_output, str(auth_error), 404)
|
|
204
|
-
|
|
205
|
-
return response
|
|
206
|
-
|
|
207
|
-
def input_errors(self, input_output, errors, status_code=200):
|
|
208
|
-
return self.respond(input_output, {"status": "input_errors", "input_errors": errors}, status_code)
|
|
209
|
-
|
|
210
|
-
def error(self, input_output, message, status_code):
|
|
211
|
-
return self.respond(input_output, {"status": "client_error", "error": message}, status_code)
|
|
212
|
-
|
|
213
|
-
def success(self, input_output, data, number_results=None, limit=None, next_page=None):
|
|
214
|
-
response_data = {"status": "success", "data": data, "pagination": {}}
|
|
215
|
-
|
|
216
|
-
if number_results is not None:
|
|
217
|
-
for value in [number_results, limit]:
|
|
218
|
-
if value is not None and type(value) != int:
|
|
219
|
-
raise ValueError("number_results and limit must all be integers")
|
|
220
|
-
|
|
221
|
-
response_data["pagination"] = {
|
|
222
|
-
"number_results": number_results,
|
|
223
|
-
"limit": limit,
|
|
224
|
-
"next_page": next_page,
|
|
225
|
-
}
|
|
226
|
-
|
|
227
|
-
return self.respond(input_output, response_data, 200)
|
|
228
|
-
|
|
229
|
-
def respond(self, input_output, response_data, status_code):
|
|
230
|
-
response_headers = self.configuration("response_headers")
|
|
231
|
-
if response_headers:
|
|
232
|
-
input_output.set_headers(response_headers)
|
|
233
|
-
for security_header in self.configuration("security_headers"):
|
|
234
|
-
security_header.set_headers_for_input_output(input_output)
|
|
235
|
-
return input_output.respond(self._normalize_response(response_data), status_code)
|
|
236
|
-
|
|
237
|
-
def _normalize_response(self, response_data):
|
|
238
|
-
if not "status" in response_data:
|
|
239
|
-
raise ValueError("Huh, status got left out somehow")
|
|
240
|
-
return {
|
|
241
|
-
self.auto_case_internal_column_name("status"): self.auto_case_internal_column_name(response_data["status"]),
|
|
242
|
-
self.auto_case_internal_column_name("error"): response_data.get("error", ""),
|
|
243
|
-
self.auto_case_internal_column_name("data"): response_data.get("data", []),
|
|
244
|
-
self.auto_case_internal_column_name("pagination"): self._normalize_pagination(
|
|
245
|
-
response_data.get("pagination", {})
|
|
246
|
-
),
|
|
247
|
-
self.auto_case_internal_column_name("input_errors"): response_data.get("input_errors", {}),
|
|
248
|
-
}
|
|
249
|
-
|
|
250
|
-
def _normalize_pagination(self, pagination):
|
|
251
|
-
# pagination isn't always relevant so if it is completely empty then leave it that way
|
|
252
|
-
if not pagination:
|
|
253
|
-
return pagination
|
|
254
|
-
return {
|
|
255
|
-
self.auto_case_internal_column_name("number_results"): pagination.get("number_results", 0),
|
|
256
|
-
self.auto_case_internal_column_name("limit"): pagination.get("limit", 0),
|
|
257
|
-
self.auto_case_internal_column_name("next_page"): {
|
|
258
|
-
self.auto_case_internal_column_name(key): value
|
|
259
|
-
for (key, value) in pagination.get("next_page", {}).items()
|
|
260
|
-
},
|
|
261
|
-
}
|
|
262
|
-
|
|
263
|
-
def _model_as_json(self, model, input_output):
|
|
264
|
-
if self.configuration("output_map"):
|
|
265
|
-
return self._di.call_function(self.configuration("output_map"), model=model)
|
|
266
|
-
|
|
267
|
-
if self._as_json_map is None:
|
|
268
|
-
self._as_json_map = self._build_as_json_map(model)
|
|
269
|
-
|
|
270
|
-
json = OrderedDict()
|
|
271
|
-
for output_name, column in self._as_json_map.items():
|
|
272
|
-
column_data = column.to_json(model)
|
|
273
|
-
if len(column_data) == 1:
|
|
274
|
-
json[output_name] = list(column_data.values())[0]
|
|
275
|
-
else:
|
|
276
|
-
for key, value in column_data.items():
|
|
277
|
-
json[self.auto_case_column_name(key, True)] = value
|
|
278
|
-
return json
|
|
279
|
-
|
|
280
|
-
def _build_as_json_map(self, model):
|
|
281
|
-
conversion_map = {}
|
|
282
|
-
if self.configuration("id_column_name"):
|
|
283
|
-
conversion_map[self.auto_case_internal_column_name("id")] = model.columns()[self.id_column_name]
|
|
284
|
-
|
|
285
|
-
for column in self._get_readable_columns().values():
|
|
286
|
-
conversion_map[self.auto_case_column_name(column.name, True)] = column
|
|
287
|
-
return conversion_map
|
|
288
|
-
|
|
289
|
-
def auto_case_internal_column_name(self, column_name):
|
|
290
|
-
if self._configuration["external_casing"]:
|
|
291
|
-
return string.swap_casing(column_name, "snake_case", self._configuration["external_casing"])
|
|
292
|
-
return column_name
|
|
293
|
-
|
|
294
|
-
def auto_case_to_internal_column_name(self, column_name):
|
|
295
|
-
if self._configuration["external_casing"]:
|
|
296
|
-
return string.swap_casing(column_name, self._configuration["external_casing"], "snake_case")
|
|
297
|
-
return column_name
|
|
298
|
-
|
|
299
|
-
def auto_case_column_name(self, column_name, internal_to_external):
|
|
300
|
-
if not self._configuration["internal_casing"]:
|
|
301
|
-
return column_name
|
|
302
|
-
if internal_to_external:
|
|
303
|
-
return string.swap_casing(
|
|
304
|
-
column_name,
|
|
305
|
-
self._configuration["internal_casing"],
|
|
306
|
-
self._configuration["external_casing"],
|
|
307
|
-
)
|
|
308
|
-
return string.swap_casing(
|
|
309
|
-
column_name,
|
|
310
|
-
self._configuration["external_casing"],
|
|
311
|
-
self._configuration["internal_casing"],
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
@property
|
|
315
|
-
def id_column_name(self) -> str:
|
|
316
|
-
"""
|
|
317
|
-
This returns the name of the id column to use for requests
|
|
318
|
-
|
|
319
|
-
There are three ways to determine the id column:
|
|
320
|
-
|
|
321
|
-
1. It may be defined in the handler configuration.
|
|
322
|
-
2. It may be overridden in the model class
|
|
323
|
-
3. It defaults to 'id'
|
|
324
|
-
|
|
325
|
-
The first happens if the developer wants to expose a different "id" column to the client.
|
|
326
|
-
The second happens if the developer wants to use a different id column internally.
|
|
327
|
-
The third is the clearskies default.
|
|
328
|
-
|
|
329
|
-
The first is easy to detect because the dev will set `id_column_name` in the handler config.
|
|
330
|
-
The second happens if the model class defines a different `id_column_name` property in the model class.
|
|
331
|
-
However, this is tricky because there is nothing in this base case that allows us to pull up the model.
|
|
332
|
-
In fact, not all handlers use a model, or they may use multiple models, etc... Still, it's pretty
|
|
333
|
-
common for the handler to have a configuration named `model_class` or `model`, so let's check for that and assume
|
|
334
|
-
the handler will only ask for the id_column_name() if the handler has a `self.configuration('model_class')`
|
|
335
|
-
"""
|
|
336
|
-
id_column_name = self.configuration("id_column_name")
|
|
337
|
-
if id_column_name is not None:
|
|
338
|
-
return id_column_name
|
|
339
|
-
if not self._configuration.get("model_class", False) and not self._configuration.get("model", False):
|
|
340
|
-
raise KeyError(
|
|
341
|
-
"To properly use handler.id_column_name, the handler must have a 'model_class' or 'model' configuration key"
|
|
342
|
-
)
|
|
343
|
-
if self._configuration.get("model_class", False):
|
|
344
|
-
return self._configuration.get("model_class").id_column_name
|
|
345
|
-
return self._configuration.get("model").id_column_name
|
|
346
|
-
|
|
347
|
-
def cors(self, input_output):
|
|
348
|
-
cors = self._cors_header
|
|
349
|
-
if not cors:
|
|
350
|
-
return self.error(input_output, "not found", 404)
|
|
351
|
-
authentication = self._configuration.get("authentication")
|
|
352
|
-
if authentication:
|
|
353
|
-
authentication.set_headers_for_cors(cors)
|
|
354
|
-
cors.set_headers_for_input_output(input_output)
|
|
355
|
-
return input_output.respond("", 200)
|
|
356
|
-
|
|
357
|
-
def documentation(self):
|
|
358
|
-
return []
|
|
359
|
-
|
|
360
|
-
def documentation_components(self):
|
|
361
|
-
return {
|
|
362
|
-
"models": self.documentation_models(),
|
|
363
|
-
"securitySchemes": self.documentation_security_schemes(),
|
|
364
|
-
}
|
|
365
|
-
|
|
366
|
-
def documentation_security_schemes(self):
|
|
367
|
-
authentication = self._configuration.get("authentication")
|
|
368
|
-
if not authentication or not authentication.documentation_security_scheme_name():
|
|
369
|
-
return {}
|
|
370
|
-
|
|
371
|
-
return {
|
|
372
|
-
authentication.documentation_security_scheme_name(): authentication.documentation_security_scheme(),
|
|
373
|
-
}
|
|
374
|
-
|
|
375
|
-
def documentation_models(self):
|
|
376
|
-
return {}
|
|
377
|
-
|
|
378
|
-
def documentation_pagination_response(self, include_pagination=True):
|
|
379
|
-
if not include_pagination:
|
|
380
|
-
return AutoDocObject(self.auto_case_internal_column_name("pagination"), [], value={})
|
|
381
|
-
return AutoDocObject(
|
|
382
|
-
self.auto_case_internal_column_name("pagination"),
|
|
383
|
-
[
|
|
384
|
-
AutoDocInteger(self.auto_case_internal_column_name("number_results"), example=10),
|
|
385
|
-
AutoDocInteger(self.auto_case_internal_column_name("limit"), example=100),
|
|
386
|
-
AutoDocObject(
|
|
387
|
-
self.auto_case_internal_column_name("next_page"),
|
|
388
|
-
self._model.documentation_pagination_next_page_response(self.auto_case_internal_column_name),
|
|
389
|
-
self._model.documentation_pagination_next_page_example(self.auto_case_internal_column_name),
|
|
390
|
-
),
|
|
391
|
-
],
|
|
392
|
-
)
|
|
393
|
-
|
|
394
|
-
def documentation_success_response(self, data_schema, description="", include_pagination=False):
|
|
395
|
-
return AutoDocResponse(
|
|
396
|
-
200,
|
|
397
|
-
AutoDocObject(
|
|
398
|
-
"body",
|
|
399
|
-
[
|
|
400
|
-
AutoDocString(self.auto_case_internal_column_name("status"), value="success"),
|
|
401
|
-
data_schema,
|
|
402
|
-
self.documentation_pagination_response(include_pagination=include_pagination),
|
|
403
|
-
AutoDocString(self.auto_case_internal_column_name("error"), value=""),
|
|
404
|
-
AutoDocObject(self.auto_case_internal_column_name("input_errors"), [], value={}),
|
|
405
|
-
],
|
|
406
|
-
),
|
|
407
|
-
description=description,
|
|
408
|
-
)
|
|
409
|
-
|
|
410
|
-
def documentation_generic_error_response(self, description="Invalid Call", status=400):
|
|
411
|
-
return AutoDocResponse(
|
|
412
|
-
status,
|
|
413
|
-
AutoDocObject(
|
|
414
|
-
"body",
|
|
415
|
-
[
|
|
416
|
-
AutoDocString(self.auto_case_internal_column_name("status"), value="error"),
|
|
417
|
-
AutoDocObject(self.auto_case_internal_column_name("data"), [], value={}),
|
|
418
|
-
self.documentation_pagination_response(include_pagination=False),
|
|
419
|
-
AutoDocString(self.auto_case_internal_column_name("error"), example="User readable error message"),
|
|
420
|
-
AutoDocObject(self.auto_case_internal_column_name("input_errors"), [], value={}),
|
|
421
|
-
],
|
|
422
|
-
),
|
|
423
|
-
description=description,
|
|
424
|
-
)
|
|
425
|
-
|
|
426
|
-
def documentation_input_error_response(self, description="Invalid client-side input"):
|
|
427
|
-
email_example = self.auto_case_internal_column_name("email")
|
|
428
|
-
return AutoDocResponse(
|
|
429
|
-
200,
|
|
430
|
-
AutoDocObject(
|
|
431
|
-
"body",
|
|
432
|
-
[
|
|
433
|
-
AutoDocString(self.auto_case_internal_column_name("status"), value="input_errors"),
|
|
434
|
-
AutoDocObject(self.auto_case_internal_column_name("data"), [], value={}),
|
|
435
|
-
self.documentation_pagination_response(include_pagination=False),
|
|
436
|
-
AutoDocString(self.auto_case_internal_column_name("error"), value=""),
|
|
437
|
-
AutoDocObject(
|
|
438
|
-
self.auto_case_internal_column_name("input_errors"),
|
|
439
|
-
[AutoDocString("[COLUMN_NAME]", example="User friendly error message")],
|
|
440
|
-
example={email_example: f"{email_example} was not a valid email address"},
|
|
441
|
-
),
|
|
442
|
-
],
|
|
443
|
-
),
|
|
444
|
-
description=description,
|
|
445
|
-
)
|
|
446
|
-
|
|
447
|
-
def documentation_access_denied_response(self):
|
|
448
|
-
return self.documentation_generic_error_response(description="Access Denied", status=401)
|
|
449
|
-
|
|
450
|
-
def documentation_unauthorized_response(self):
|
|
451
|
-
return self.documentation_generic_error_response(description="Unauthorized", status=403)
|
|
452
|
-
|
|
453
|
-
def documentation_not_found(self):
|
|
454
|
-
return self.documentation_generic_error_response(description="Not Found", status=404)
|
|
455
|
-
|
|
456
|
-
def documentation_request_security(self):
|
|
457
|
-
authentication = self.configuration("authentication")
|
|
458
|
-
name = authentication.documentation_security_scheme_name()
|
|
459
|
-
return [{name: []}] if name else []
|
|
460
|
-
|
|
461
|
-
def documentation_data_schema(self):
|
|
462
|
-
id_column_name = self.id_column_name
|
|
463
|
-
properties = []
|
|
464
|
-
if self.configuration("id_column_name"):
|
|
465
|
-
properties.append(
|
|
466
|
-
self._columns[id_column_name].documentation(name=self.auto_case_internal_column_name("id"))
|
|
467
|
-
if id_column_name in self._columns
|
|
468
|
-
else AutoDocString(self.auto_case_internal_column_name("id"))
|
|
469
|
-
)
|
|
470
|
-
|
|
471
|
-
for column in self._get_readable_columns().values():
|
|
472
|
-
column_doc = column.documentation()
|
|
473
|
-
if type(column_doc) != list:
|
|
474
|
-
column_doc = [column_doc]
|
|
475
|
-
for doc in column_doc:
|
|
476
|
-
doc.name = self.auto_case_internal_column_name(doc.name)
|
|
477
|
-
properties.append(doc)
|
|
478
|
-
|
|
479
|
-
return properties
|
clearskies/handlers/callable.py
DELETED
|
@@ -1,191 +0,0 @@
|
|
|
1
|
-
from .base import Base
|
|
2
|
-
from .schema_helper import SchemaHelper
|
|
3
|
-
from .exceptions import InputError, ClientError, NotFound
|
|
4
|
-
import inspect
|
|
5
|
-
import json
|
|
6
|
-
from ..functional import validations, string
|
|
7
|
-
from .. import autodoc
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class Callable(Base, SchemaHelper):
|
|
11
|
-
_columns = None
|
|
12
|
-
|
|
13
|
-
_global_configuration_defaults = {
|
|
14
|
-
"response_headers": None,
|
|
15
|
-
"authentication": None,
|
|
16
|
-
"authorization": None,
|
|
17
|
-
"callable": None,
|
|
18
|
-
"id_column_name": None,
|
|
19
|
-
"doc_description": "",
|
|
20
|
-
"internal_casing": "",
|
|
21
|
-
"external_casing": "",
|
|
22
|
-
"security_headers": None,
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
_configuration_defaults = {
|
|
26
|
-
"base_url": "",
|
|
27
|
-
"return_raw_response": False,
|
|
28
|
-
"schema": None,
|
|
29
|
-
"writeable_columns": None,
|
|
30
|
-
"doc_model_name": "",
|
|
31
|
-
"doc_response_data_schema": None,
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
def __init__(self, di):
|
|
35
|
-
super().__init__(di)
|
|
36
|
-
|
|
37
|
-
def handle(self, input_output):
|
|
38
|
-
self._di.bind("input_output", input_output)
|
|
39
|
-
try:
|
|
40
|
-
if self.configuration("schema"):
|
|
41
|
-
request_data = self.request_data(input_output)
|
|
42
|
-
input_errors = {
|
|
43
|
-
**self._extra_column_errors(request_data),
|
|
44
|
-
**self._find_input_errors(request_data),
|
|
45
|
-
}
|
|
46
|
-
if input_errors:
|
|
47
|
-
return self.input_errors(input_output, input_errors)
|
|
48
|
-
response = self._di.call_function(
|
|
49
|
-
self.configuration("callable"),
|
|
50
|
-
**input_output.routing_data(),
|
|
51
|
-
**input_output.context_specifics(),
|
|
52
|
-
request_data=request_data,
|
|
53
|
-
authorization_data=input_output.get_authorization_data(),
|
|
54
|
-
)
|
|
55
|
-
else:
|
|
56
|
-
response = self._di.call_function(
|
|
57
|
-
self.configuration("callable"),
|
|
58
|
-
**input_output.routing_data(),
|
|
59
|
-
**input_output.context_specifics(),
|
|
60
|
-
request_data=self.request_data(input_output, required=False),
|
|
61
|
-
authorization_data=input_output.get_authorization_data(),
|
|
62
|
-
)
|
|
63
|
-
if response:
|
|
64
|
-
return self.success(input_output, response)
|
|
65
|
-
return
|
|
66
|
-
except InputError as e:
|
|
67
|
-
if e.errors:
|
|
68
|
-
return self.input_errors(input_output, e.errors)
|
|
69
|
-
else:
|
|
70
|
-
return self.input_errors(input_output, str(e))
|
|
71
|
-
except ClientError as e:
|
|
72
|
-
return self.error(input_output, str(e), 400)
|
|
73
|
-
except NotFound as e:
|
|
74
|
-
return self.error(input_output, str(e), 404)
|
|
75
|
-
|
|
76
|
-
def _check_configuration(self, configuration):
|
|
77
|
-
super()._check_configuration(configuration)
|
|
78
|
-
error_prefix = "Configuration error for %s:" % (self.__class__.__name__)
|
|
79
|
-
if not "callable" in configuration:
|
|
80
|
-
raise KeyError(f"{error_prefix} you must specify 'callable'")
|
|
81
|
-
if not callable(configuration["callable"]):
|
|
82
|
-
raise ValueError(f"{error_prefix} the provided callable is not actually callable")
|
|
83
|
-
if configuration.get("schema") is not None:
|
|
84
|
-
self._check_schema(configuration["schema"], configuration.get("writeable_columns"), error_prefix)
|
|
85
|
-
|
|
86
|
-
def _finalize_configuration(self, configuration):
|
|
87
|
-
configuration = super()._finalize_configuration(configuration)
|
|
88
|
-
if configuration.get("schema") is not None:
|
|
89
|
-
if validations.is_model(configuration["schema"]):
|
|
90
|
-
configuration["doc_model_name"] = configuration["schema"].__class__.__name__
|
|
91
|
-
elif validations.is_model_class(configuration["schema"]):
|
|
92
|
-
configuration["doc_model_name"] = configuration["schema"].__name__
|
|
93
|
-
configuration["schema"] = self._schema_to_columns(
|
|
94
|
-
configuration["schema"], columns_to_keep=configuration.get("writeable_columns")
|
|
95
|
-
)
|
|
96
|
-
return configuration
|
|
97
|
-
|
|
98
|
-
def request_data(self, input_output, required=True):
|
|
99
|
-
if not self.configuration("schema"):
|
|
100
|
-
return input_output.request_data(required=required)
|
|
101
|
-
# we have to map from internal names to external names, because case mapping
|
|
102
|
-
# isn't always one-to-one, so we want to do it exactly the same way that the documentation
|
|
103
|
-
# is built.
|
|
104
|
-
key_map = {self.auto_case_column_name(key, True): key for key in self.configuration("schema").keys()}
|
|
105
|
-
# in case the id comes up in the request body
|
|
106
|
-
key_map[self.auto_case_internal_column_name("id")] = "id"
|
|
107
|
-
|
|
108
|
-
# and make sure we don't drop any data along the way, because the input validation
|
|
109
|
-
# needs to return an error for unexpected data.
|
|
110
|
-
request_data = {
|
|
111
|
-
key_map.get(key, key): value for (key, value) in input_output.request_data(required=required).items()
|
|
112
|
-
}
|
|
113
|
-
# the parent handler should provide our resource id (we don't do any routing ourselves)
|
|
114
|
-
# However, our update/etc handlers need to find the id easily, so I'm going to be lazy and
|
|
115
|
-
# just dump it into the request. I'll probably regret that.
|
|
116
|
-
routing_data = input_output.routing_data()
|
|
117
|
-
# we don't have to worry about casing on the 'id' in routing_data because it doesn't come in from the
|
|
118
|
-
# route with a name. Rather, it is populated by clearskies, so will always just be 'id'
|
|
119
|
-
if "id" in routing_data:
|
|
120
|
-
request_data["id"] = routing_data["id"]
|
|
121
|
-
return request_data
|
|
122
|
-
|
|
123
|
-
def success(self, input_output, data, number_results=None, limit=None, next_page=None):
|
|
124
|
-
if self.configuration("return_raw_response"):
|
|
125
|
-
return input_output.respond(data, 200)
|
|
126
|
-
|
|
127
|
-
return super().success(input_output, data, number_results=number_results, limit=limit, next_page=next_page)
|
|
128
|
-
|
|
129
|
-
def documentation(self):
|
|
130
|
-
schema = self.configuration("schema")
|
|
131
|
-
|
|
132
|
-
# our request parameters
|
|
133
|
-
parameters = []
|
|
134
|
-
if schema:
|
|
135
|
-
parameters = [
|
|
136
|
-
autodoc.request.JSONBody(
|
|
137
|
-
column.documentation(name=self.auto_case_column_name(column.name, True)),
|
|
138
|
-
description=f"Set '{column.name}'",
|
|
139
|
-
required=column.is_required,
|
|
140
|
-
)
|
|
141
|
-
for column in schema.values()
|
|
142
|
-
]
|
|
143
|
-
|
|
144
|
-
authentication = self.configuration("authentication")
|
|
145
|
-
standard_error_responses = []
|
|
146
|
-
if not getattr(authentication, "is_public", False):
|
|
147
|
-
standard_error_responses.append(self.documentation_access_denied_response())
|
|
148
|
-
if getattr(authentication, "can_authorize", False):
|
|
149
|
-
standard_error_responses.append(self.documentation_unauthorized_response())
|
|
150
|
-
|
|
151
|
-
response_data_schema = self.configuration("doc_response_data_schema")
|
|
152
|
-
if not response_data_schema:
|
|
153
|
-
response_data_schema = []
|
|
154
|
-
|
|
155
|
-
return [
|
|
156
|
-
autodoc.request.Request(
|
|
157
|
-
self.configuration("doc_description"),
|
|
158
|
-
[
|
|
159
|
-
self.documentation_success_response(
|
|
160
|
-
autodoc.schema.Object(
|
|
161
|
-
self.auto_case_internal_column_name("data"),
|
|
162
|
-
children=response_data_schema,
|
|
163
|
-
model_name=self.configuration("doc_model_name"),
|
|
164
|
-
),
|
|
165
|
-
include_pagination=False,
|
|
166
|
-
),
|
|
167
|
-
*standard_error_responses,
|
|
168
|
-
self.documentation_not_found(),
|
|
169
|
-
],
|
|
170
|
-
request_methods="POST" if schema else "GET",
|
|
171
|
-
relative_path=self.configuration("base_url"),
|
|
172
|
-
parameters=[
|
|
173
|
-
*parameters,
|
|
174
|
-
],
|
|
175
|
-
root_properties={
|
|
176
|
-
"security": self.documentation_request_security(),
|
|
177
|
-
},
|
|
178
|
-
)
|
|
179
|
-
]
|
|
180
|
-
|
|
181
|
-
def documentation_models(self):
|
|
182
|
-
if not self.configuration("doc_model_name") or not self.configuration("doc_response_data_schema"):
|
|
183
|
-
return {}
|
|
184
|
-
|
|
185
|
-
schema_model_name = self.configuration("doc_model_name")
|
|
186
|
-
return {
|
|
187
|
-
schema_model_name: autodoc.schema.Object(
|
|
188
|
-
"data",
|
|
189
|
-
children=self.configuration("doc_response_data_schema"),
|
|
190
|
-
),
|
|
191
|
-
}
|