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,192 +0,0 @@
|
|
|
1
|
-
import logging
|
|
2
|
-
import urllib.parse
|
|
3
|
-
import re
|
|
4
|
-
import json
|
|
5
|
-
from ..autodoc.request import URLPath
|
|
6
|
-
from ..autodoc.schema import String
|
|
7
|
-
from . import simple_routing
|
|
8
|
-
|
|
9
|
-
logger = logging.getLogger(__name__)
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
class SimpleRoutingRoute:
|
|
13
|
-
_di = None
|
|
14
|
-
_handler = None
|
|
15
|
-
_methods = None
|
|
16
|
-
_path = None
|
|
17
|
-
_path_parts = None
|
|
18
|
-
_resource_paths = None
|
|
19
|
-
_routes_to_simple_routing = False
|
|
20
|
-
_bindings = None
|
|
21
|
-
_path_parameter_with_slashes = None
|
|
22
|
-
_has_sub_paths = None
|
|
23
|
-
|
|
24
|
-
def __init__(self, di):
|
|
25
|
-
self._di = di
|
|
26
|
-
|
|
27
|
-
def configure(
|
|
28
|
-
self,
|
|
29
|
-
handler_class,
|
|
30
|
-
handler_config,
|
|
31
|
-
path=None,
|
|
32
|
-
methods=None,
|
|
33
|
-
authentication=None,
|
|
34
|
-
response_headers=None,
|
|
35
|
-
security_headers=None,
|
|
36
|
-
path_parameter_with_slashes=None,
|
|
37
|
-
bindings=None,
|
|
38
|
-
has_sub_paths=True,
|
|
39
|
-
):
|
|
40
|
-
if authentication is not None and not handler_config.get("authentication"):
|
|
41
|
-
handler_config["authentication"] = authentication
|
|
42
|
-
response_headers = response_headers if response_headers is not None else {}
|
|
43
|
-
if "response_headers" in handler_config:
|
|
44
|
-
if type(handler_config["response_headers"]) != dict:
|
|
45
|
-
raise ValueError("Invalid configuration: 'response_headers' must be a dictionary")
|
|
46
|
-
response_headers = {**response_headers, **handler_config["response_headers"]}
|
|
47
|
-
self._path = path
|
|
48
|
-
if handler_config.get("base_url"):
|
|
49
|
-
self._path = path.rstrip("/") + "/" + handler_config.get("base_url").lstrip("/")
|
|
50
|
-
self._path_parts = self._path.strip("/").split("/") if self._path is not None else []
|
|
51
|
-
self._resource_paths = self._extract_resource_paths(self._path_parts)
|
|
52
|
-
self._bindings = bindings if bindings else {}
|
|
53
|
-
self._path_parameter_with_slashes = path_parameter_with_slashes if path_parameter_with_slashes else []
|
|
54
|
-
self._has_sub_paths = has_sub_paths
|
|
55
|
-
if methods is not None:
|
|
56
|
-
self._methods = [methods.upper()] if isinstance(methods, str) else [met.upper() for met in methods]
|
|
57
|
-
sub_handler_config = {
|
|
58
|
-
**handler_config,
|
|
59
|
-
**{
|
|
60
|
-
"base_url": ("/" + path.strip("/")) if path is not None else "/",
|
|
61
|
-
},
|
|
62
|
-
}
|
|
63
|
-
if response_headers:
|
|
64
|
-
sub_handler_config["response_headers"] = response_headers
|
|
65
|
-
security_headers = security_headers if security_headers is not None else []
|
|
66
|
-
if "security_headers" in handler_config:
|
|
67
|
-
security_headers = [*security_headers, *handler_config["security_headers"]]
|
|
68
|
-
sub_handler_config["security_headers"] = security_headers
|
|
69
|
-
self._handler = self._di.build(handler_class, cache=False)
|
|
70
|
-
self._handler.configure(sub_handler_config)
|
|
71
|
-
self._routes_to_simple_routing = issubclass(handler_class, simple_routing.SimpleRouting)
|
|
72
|
-
|
|
73
|
-
def _extract_resource_paths(self, path_parts):
|
|
74
|
-
resource_paths = {}
|
|
75
|
-
for index, part in enumerate(path_parts):
|
|
76
|
-
if not part:
|
|
77
|
-
continue
|
|
78
|
-
if part[0] != "{":
|
|
79
|
-
continue
|
|
80
|
-
if part[-1] != "}":
|
|
81
|
-
raise ValueError(
|
|
82
|
-
f"Invalid route configuration for URL '{path}': section '{part}'"
|
|
83
|
-
+ " starts with a '{' but does not end with one"
|
|
84
|
-
)
|
|
85
|
-
match = re.match("{(\\w[\\w\\d_]{0,})\\}", part)
|
|
86
|
-
if not match:
|
|
87
|
-
raise ValueError(
|
|
88
|
-
f"Invalid route configuration for URL '{path}', section '{part}': resource identifiers must start with a letter and contain only letters, numbers, and underscores"
|
|
89
|
-
)
|
|
90
|
-
resource_paths[index] = match.group(1)
|
|
91
|
-
return resource_paths
|
|
92
|
-
|
|
93
|
-
def matches(self, full_path, request_method, is_cors=False):
|
|
94
|
-
"""Returns None if the route doesn't match, or a dictionary with route data for a match.
|
|
95
|
-
|
|
96
|
-
You can't just match true/false against the return value, because of the route matches
|
|
97
|
-
but has no route data, it returns an empty dictionary. Check explicitly for None
|
|
98
|
-
to understand if there was no route match at all.
|
|
99
|
-
"""
|
|
100
|
-
# if we're routing to a simple router then defer to it
|
|
101
|
-
incoming = f"Incoming request: [{request_method}] {full_path}. Check against route with url '{self._path}'. Results: "
|
|
102
|
-
if not self._methods:
|
|
103
|
-
incoming += " configured for any method except OPTIONS"
|
|
104
|
-
elif isinstance(self._methods, str):
|
|
105
|
-
incoming += f" with method '{self._methods}'"
|
|
106
|
-
else:
|
|
107
|
-
incoming += " with any of the following methods: " + ", ".join(self._methods)
|
|
108
|
-
if self._routes_to_simple_routing:
|
|
109
|
-
return self._handler.can_handle(full_path, request_method, is_cors=is_cors)
|
|
110
|
-
# If we're routing for CORS then ignore the request method (since it won't match)
|
|
111
|
-
if not is_cors and self._methods is not None and request_method not in self._methods:
|
|
112
|
-
logger.debug(
|
|
113
|
-
f"{incoming} Skipped because this route is not specifically configured for CORS, and this is an OPTIONS request."
|
|
114
|
-
)
|
|
115
|
-
return None
|
|
116
|
-
if self._resource_paths:
|
|
117
|
-
results = self._resource_path_match(full_path, self._path_parts, self._resource_paths)
|
|
118
|
-
if not results:
|
|
119
|
-
logger.debug(f"{incoming} Not a match.")
|
|
120
|
-
else:
|
|
121
|
-
logger.debug(f"{incoming} Matched and extracted route data: " + json.dumps(results))
|
|
122
|
-
return results
|
|
123
|
-
if self._path is not None:
|
|
124
|
-
full_path = full_path.strip("/")
|
|
125
|
-
my_path = self._path.strip("/")
|
|
126
|
-
my_path_length = len(my_path)
|
|
127
|
-
full_path_length = len(full_path)
|
|
128
|
-
if my_path_length > full_path_length:
|
|
129
|
-
logger.debug(f"{incoming} Not a match. I'm too long to bother checking.")
|
|
130
|
-
return None
|
|
131
|
-
if full_path[:my_path_length] != my_path:
|
|
132
|
-
logger.debug(f"{incoming} Not a match. Our prefixes just don't match.")
|
|
133
|
-
return None
|
|
134
|
-
if not self._has_sub_paths and full_path_length > my_path_length:
|
|
135
|
-
logger.debug(f"{incoming} Not a match. It's a partial match but I'm not allowed to do that.")
|
|
136
|
-
return None
|
|
137
|
-
# make sure we don't get confused by partial matches. `user` should match `user/` and `user/5`,
|
|
138
|
-
# but it shouldn't match `users/`
|
|
139
|
-
if full_path_length > my_path_length and full_path[my_path_length] != "/" and my_path != "":
|
|
140
|
-
logger.debug(f"{incoming} Not a match. I only partially matched the URL but not as a sub-directory.")
|
|
141
|
-
return None
|
|
142
|
-
logger.debug(f"{incoming} Match!")
|
|
143
|
-
return {}
|
|
144
|
-
|
|
145
|
-
def _resource_path_match(self, requested_path, path_parts, resource_paths):
|
|
146
|
-
"""Returns None if the route doesn't match, or a dictionary with route data for the match."""
|
|
147
|
-
requested_parts = requested_path.strip("/").split("/")
|
|
148
|
-
route_data = {}
|
|
149
|
-
path_length = len(path_parts)
|
|
150
|
-
# it's okay if the requested path is longer than the configured path, since there may
|
|
151
|
-
# be sub-routes that we don't know about. However, we won't ever have a match if
|
|
152
|
-
# the requested path is shorter than the configured path.
|
|
153
|
-
if len(requested_parts) < path_length:
|
|
154
|
-
return None
|
|
155
|
-
for index in range(path_length):
|
|
156
|
-
if index in resource_paths:
|
|
157
|
-
if resource_paths[index] in self._path_parameter_with_slashes:
|
|
158
|
-
route_data[resource_paths[index]] = urllib.parse.unquote("/".join(requested_parts[index:]))
|
|
159
|
-
else:
|
|
160
|
-
route_data[resource_paths[index]] = urllib.parse.unquote(requested_parts[index])
|
|
161
|
-
else:
|
|
162
|
-
if requested_parts[index] != path_parts[index]:
|
|
163
|
-
return None
|
|
164
|
-
return route_data
|
|
165
|
-
|
|
166
|
-
def __call__(self, input_output):
|
|
167
|
-
# including calling parameters that came from the route matching
|
|
168
|
-
return self._handler(input_output)
|
|
169
|
-
|
|
170
|
-
def cors(self, input_output):
|
|
171
|
-
# including calling parameters that came from the route matching
|
|
172
|
-
return self._handler.cors(input_output)
|
|
173
|
-
|
|
174
|
-
def documentation(self):
|
|
175
|
-
docs = []
|
|
176
|
-
for doc in self._handler.documentation():
|
|
177
|
-
if self._methods is not None:
|
|
178
|
-
doc.set_request_methods(self._methods)
|
|
179
|
-
|
|
180
|
-
# do we have any resource paths to document?
|
|
181
|
-
for path_name in self._resource_paths.values():
|
|
182
|
-
description = f"The {path_name} to show results for"
|
|
183
|
-
doc.add_parameter(URLPath(String(path_name), description=description, required=True))
|
|
184
|
-
|
|
185
|
-
docs.append(doc)
|
|
186
|
-
return docs
|
|
187
|
-
|
|
188
|
-
def documentation_models(self):
|
|
189
|
-
return self._handler.documentation_models()
|
|
190
|
-
|
|
191
|
-
def documentation_security_schemes(self):
|
|
192
|
-
return self._handler.documentation_security_schemes()
|
|
@@ -1,136 +0,0 @@
|
|
|
1
|
-
from .list import List
|
|
2
|
-
from .. import autodoc
|
|
3
|
-
from ..functional import string
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class SimpleSearch(List):
|
|
7
|
-
search_control_columns = ["sort", "direction", "limit"]
|
|
8
|
-
|
|
9
|
-
@property
|
|
10
|
-
def allowed_request_keys(self):
|
|
11
|
-
return [
|
|
12
|
-
*self.search_control_columns,
|
|
13
|
-
# the list comprehension seems unnecessary, but that is because we require searchable
|
|
14
|
-
# columns to be an iterable, but that doesn't guarantee that it converts automatically
|
|
15
|
-
# into a list.
|
|
16
|
-
*[key for key in self.configuration("searchable_columns")],
|
|
17
|
-
]
|
|
18
|
-
|
|
19
|
-
def check_search_in_request_data(self, request_data, query_parameters):
|
|
20
|
-
for input_source_label, input_data in [("request body", request_data), ("URL data", query_parameters)]:
|
|
21
|
-
for column_name, value in input_data.items():
|
|
22
|
-
if column_name in self.search_control_columns:
|
|
23
|
-
continue
|
|
24
|
-
if column_name not in self.configuration("searchable_columns"):
|
|
25
|
-
return f"Invalid request. An invalid search column, '{column_name}', was found in the {input_source_label}"
|
|
26
|
-
[column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
|
|
27
|
-
value_error = self._columns[column_name].check_search_value(
|
|
28
|
-
value, relationship_reference=relationship_reference
|
|
29
|
-
)
|
|
30
|
-
if value_error:
|
|
31
|
-
return (
|
|
32
|
-
f"Invalid request. {value_error} for search column '{column_name}' in the {input_source_label}"
|
|
33
|
-
)
|
|
34
|
-
|
|
35
|
-
def configure_models_from_request_data(self, models, request_data, query_parameters, pagination_data):
|
|
36
|
-
[models, limit] = super().configure_models_from_request_data(
|
|
37
|
-
models, request_data, query_parameters, pagination_data
|
|
38
|
-
)
|
|
39
|
-
# we can play fast and loose with the possiblity of duplicate keys because our input checking already
|
|
40
|
-
# disallows that
|
|
41
|
-
for input_source in [request_data, query_parameters]:
|
|
42
|
-
for column_name, value in input_source.items():
|
|
43
|
-
if column_name in self.search_control_columns:
|
|
44
|
-
continue
|
|
45
|
-
if column_name == "id":
|
|
46
|
-
column_name = self.id_column_name
|
|
47
|
-
models = self._add_join(column_name, models)
|
|
48
|
-
[column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
|
|
49
|
-
column = self._columns[column_name]
|
|
50
|
-
models = column.add_search(models, value, relationship_reference=relationship_reference)
|
|
51
|
-
|
|
52
|
-
return [models, limit]
|
|
53
|
-
|
|
54
|
-
def _check_configuration(self, configuration):
|
|
55
|
-
super()._check_configuration(configuration)
|
|
56
|
-
self._check_columns_in_configuration(configuration, "searchable_columns")
|
|
57
|
-
|
|
58
|
-
def _documentation_request(self, request_method, parameters):
|
|
59
|
-
nice_model = string.camel_case_to_words(self._model.__class__.__name__)
|
|
60
|
-
schema_model_name = string.camel_case_to_snake_case(self._model.__class__.__name__)
|
|
61
|
-
data_schema = self.documentation_data_schema()
|
|
62
|
-
|
|
63
|
-
authentication = self.configuration("authentication")
|
|
64
|
-
standard_error_responses = []
|
|
65
|
-
if not getattr(authentication, "is_public", False):
|
|
66
|
-
standard_error_responses.append(self.documentation_access_denied_response())
|
|
67
|
-
if getattr(authentication, "can_authorize", False):
|
|
68
|
-
standard_error_responses.append(self.documentation_unauthorized_response())
|
|
69
|
-
|
|
70
|
-
return autodoc.request.Request(
|
|
71
|
-
f"Fetch the list of current {nice_model} records",
|
|
72
|
-
[
|
|
73
|
-
self.documentation_success_response(
|
|
74
|
-
autodoc.schema.Array(
|
|
75
|
-
self.auto_case_internal_column_name("data"),
|
|
76
|
-
autodoc.schema.Object(nice_model, children=data_schema, model_name=schema_model_name),
|
|
77
|
-
),
|
|
78
|
-
description=f"The matching {nice_model} records",
|
|
79
|
-
include_pagination=True,
|
|
80
|
-
),
|
|
81
|
-
*standard_error_responses,
|
|
82
|
-
self.documentation_generic_error_response(),
|
|
83
|
-
],
|
|
84
|
-
relative_path=self.configuration("base_url"),
|
|
85
|
-
request_methods=request_method,
|
|
86
|
-
parameters=parameters,
|
|
87
|
-
root_properties={
|
|
88
|
-
"security": self.documentation_request_security(),
|
|
89
|
-
},
|
|
90
|
-
)
|
|
91
|
-
|
|
92
|
-
def documentation(self):
|
|
93
|
-
return [
|
|
94
|
-
self._documentation_request(
|
|
95
|
-
"GET",
|
|
96
|
-
[
|
|
97
|
-
*self.documentation_url_pagination_parameters(),
|
|
98
|
-
*self.documentation_url_sort_parameters(),
|
|
99
|
-
*self.documentation_url_search_parameters(),
|
|
100
|
-
],
|
|
101
|
-
),
|
|
102
|
-
self._documentation_request(
|
|
103
|
-
"POST",
|
|
104
|
-
[
|
|
105
|
-
*self.documentation_url_pagination_parameters(),
|
|
106
|
-
*self.documentation_url_sort_parameters(),
|
|
107
|
-
*self.documentation_json_search_parameters(),
|
|
108
|
-
],
|
|
109
|
-
),
|
|
110
|
-
]
|
|
111
|
-
|
|
112
|
-
def documentation_url_search_parameters(self):
|
|
113
|
-
docs = []
|
|
114
|
-
for column in self._get_searchable_columns().values():
|
|
115
|
-
column_doc = column.documentation()
|
|
116
|
-
column_doc.name = self.auto_case_internal_column_name(column_doc.name)
|
|
117
|
-
docs.append(
|
|
118
|
-
autodoc.request.URLParameter(
|
|
119
|
-
column_doc,
|
|
120
|
-
description=f"Search by {column_doc.name} (via exact match)",
|
|
121
|
-
)
|
|
122
|
-
)
|
|
123
|
-
return docs
|
|
124
|
-
|
|
125
|
-
def documentation_json_search_parameters(self):
|
|
126
|
-
docs = []
|
|
127
|
-
for column in self._get_searchable_columns().values():
|
|
128
|
-
column_doc = column.documentation()
|
|
129
|
-
column_doc.name = self.auto_case_internal_column_name(column_doc.name)
|
|
130
|
-
docs.append(
|
|
131
|
-
autodoc.request.JSONBody(
|
|
132
|
-
column_doc,
|
|
133
|
-
description=f"Search by {column_doc.name} (via exact match)",
|
|
134
|
-
)
|
|
135
|
-
)
|
|
136
|
-
return docs
|
clearskies/handlers/update.py
DELETED
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
from .write import Write
|
|
2
|
-
from .exceptions import InputError
|
|
3
|
-
from collections import OrderedDict
|
|
4
|
-
from ..functional import string
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class Update(Write):
|
|
8
|
-
def __init__(self, di):
|
|
9
|
-
super().__init__(di)
|
|
10
|
-
|
|
11
|
-
_configuration_defaults = {
|
|
12
|
-
"model": None,
|
|
13
|
-
"model_class": None,
|
|
14
|
-
"columns": None,
|
|
15
|
-
"column_overrides": None,
|
|
16
|
-
"writeable_columns": None,
|
|
17
|
-
"readable_columns": None,
|
|
18
|
-
"output_map": None,
|
|
19
|
-
"where": [],
|
|
20
|
-
"input_error_callable": None,
|
|
21
|
-
"include_id_in_path": False,
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
def get_model_id(self, input_output, input_data):
|
|
25
|
-
routing_data = input_output.routing_data()
|
|
26
|
-
if self.id_column_name in routing_data:
|
|
27
|
-
return routing_data[self.id_column_name]
|
|
28
|
-
if "id" in routing_data:
|
|
29
|
-
return routing_data["id"]
|
|
30
|
-
raise ValueError("I didn't receive the ID in my routing data. I am probably misconfigured.")
|
|
31
|
-
|
|
32
|
-
def handle(self, input_output):
|
|
33
|
-
input_data = self.request_data(input_output)
|
|
34
|
-
model_id = self.get_model_id(input_output, input_data)
|
|
35
|
-
if not model_id:
|
|
36
|
-
return self.error(input_output, "Not Found", 404)
|
|
37
|
-
id_column_name = self.id_column_name
|
|
38
|
-
models = self._model.where(f"{id_column_name}={model_id}")
|
|
39
|
-
for where in self.configuration("where"):
|
|
40
|
-
if type(where) == str:
|
|
41
|
-
models = models.where(where)
|
|
42
|
-
else:
|
|
43
|
-
models = self._di.call_function(
|
|
44
|
-
where, models=models, input_output=input_output, routing_data=input_output.routing_data()
|
|
45
|
-
)
|
|
46
|
-
models = models.where_for_request(
|
|
47
|
-
models,
|
|
48
|
-
input_output.routing_data(),
|
|
49
|
-
input_output.get_authorization_data(),
|
|
50
|
-
input_output,
|
|
51
|
-
overrides=self.configuration("column_overrides"),
|
|
52
|
-
)
|
|
53
|
-
authorization = self._configuration.get("authorization", None)
|
|
54
|
-
if authorization and hasattr(authorization, "filter_models"):
|
|
55
|
-
models = authorization.filter_models(models, input_output.get_authorization_data(), input_output)
|
|
56
|
-
model = models.first()
|
|
57
|
-
if not model.exists:
|
|
58
|
-
return self.error(input_output, "Not Found", 404)
|
|
59
|
-
if "id" in input_data:
|
|
60
|
-
del input_data["id"]
|
|
61
|
-
|
|
62
|
-
input_errors = {
|
|
63
|
-
**self._extra_column_errors(input_data),
|
|
64
|
-
**self._find_input_errors(model, input_data, input_output),
|
|
65
|
-
}
|
|
66
|
-
if input_errors:
|
|
67
|
-
raise InputError(input_errors)
|
|
68
|
-
model.save(input_data, columns=self._get_writeable_columns())
|
|
69
|
-
|
|
70
|
-
return self.success(input_output, self._model_as_json(model, input_output))
|
|
71
|
-
|
|
72
|
-
def _check_configuration(self, configuration):
|
|
73
|
-
super()._check_configuration(configuration)
|
|
74
|
-
error_prefix = "Configuration error for %s:" % (self.__class__.__name__)
|
|
75
|
-
if "where" in configuration:
|
|
76
|
-
if not hasattr(configuration["where"], "__iter__") or type(configuration["where"]) == str:
|
|
77
|
-
raise ValueError(
|
|
78
|
-
f"{error_prefix} 'where' should be an iterable of coditions or callables "
|
|
79
|
-
+ ", not "
|
|
80
|
-
+ str(type(configuration["where"])),
|
|
81
|
-
)
|
|
82
|
-
for index, where in enumerate(configuration["where"]):
|
|
83
|
-
if type(where) != str and not callable(where):
|
|
84
|
-
raise ValueError(
|
|
85
|
-
f"{error_prefix} 'where' entry should be a string with a condition or a callable that filters models "
|
|
86
|
-
+ f", but entry #{index+1} is neither of these",
|
|
87
|
-
)
|
|
88
|
-
|
|
89
|
-
def documentation(self):
|
|
90
|
-
nice_model = string.camel_case_to_words(self._model.__class__.__name__)
|
|
91
|
-
id_label = "id" if self.configuration("id_column_name") else self.id_column_name
|
|
92
|
-
return self._documentation(
|
|
93
|
-
description="Update the " + nice_model + " with an " + id_label + " of {" + id_label + "}",
|
|
94
|
-
response_description=f"The updated {nice_model}",
|
|
95
|
-
include_id_in_path=self.configuration("include_id_in_path"),
|
|
96
|
-
)
|
clearskies/handlers/write.py
DELETED
|
@@ -1,193 +0,0 @@
|
|
|
1
|
-
from .base import Base
|
|
2
|
-
from .input_processing import InputProcessing
|
|
3
|
-
from .exceptions import InputError
|
|
4
|
-
from collections import OrderedDict
|
|
5
|
-
from abc import abstractmethod
|
|
6
|
-
from .. import autodoc
|
|
7
|
-
from ..functional import string
|
|
8
|
-
import inspect
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
class Write(Base, InputProcessing):
|
|
12
|
-
_di = None
|
|
13
|
-
_model = None
|
|
14
|
-
_columns = None
|
|
15
|
-
_authentication = None
|
|
16
|
-
_writeable_columns = None
|
|
17
|
-
_readable_columns = None
|
|
18
|
-
|
|
19
|
-
_configuration_defaults = {
|
|
20
|
-
"model": None,
|
|
21
|
-
"model_class": None,
|
|
22
|
-
"columns": None,
|
|
23
|
-
"column_overrides": None,
|
|
24
|
-
"writeable_columns": None,
|
|
25
|
-
"output_map": None,
|
|
26
|
-
"readable_columns": None,
|
|
27
|
-
"input_error_callable": None,
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
def __init__(self, di):
|
|
31
|
-
super().__init__(di)
|
|
32
|
-
|
|
33
|
-
@abstractmethod
|
|
34
|
-
def handle(self, input_output):
|
|
35
|
-
pass
|
|
36
|
-
|
|
37
|
-
def _check_configuration(self, configuration):
|
|
38
|
-
super()._check_configuration(configuration)
|
|
39
|
-
error_prefix = "Configuration error for %s:" % (self.__class__.__name__)
|
|
40
|
-
has_model_class = ("model_class" in configuration) and configuration["model_class"] is not None
|
|
41
|
-
has_model = ("model" in configuration) and configuration["model"] is not None
|
|
42
|
-
if not has_model and not has_model_class:
|
|
43
|
-
raise KeyError(f"{error_prefix} you must specify 'model' or 'model_class'")
|
|
44
|
-
if has_model and has_model_class:
|
|
45
|
-
raise KeyError(f"{error_prefix} you specified both 'model' and 'model_class', but can only provide one")
|
|
46
|
-
if has_model and inspect.isclass(configuration["model"]):
|
|
47
|
-
raise ValueError(
|
|
48
|
-
"{error_prefix} you must provide a model instance in the 'model' configuration setting, but a class was provided instead"
|
|
49
|
-
)
|
|
50
|
-
if "input_error_callable" in configuration and not callable(configuration.get("input_error_callable")):
|
|
51
|
-
raise ValueError(
|
|
52
|
-
"{error_prefix} you must provide a callable for the 'input_error_callable' configuration but the provided value is not callable"
|
|
53
|
-
)
|
|
54
|
-
self._model = self._di.build(configuration["model_class"]) if has_model_class else configuration["model"]
|
|
55
|
-
self._columns = self._model.columns(overrides=configuration.get("column_overrides"))
|
|
56
|
-
has_columns = "columns" in configuration and configuration["columns"] is not None
|
|
57
|
-
has_writeable = "writeable_columns" in configuration and configuration["writeable_columns"] is not None
|
|
58
|
-
has_readable = "readable_columns" in configuration and configuration["readable_columns"] is not None
|
|
59
|
-
if not has_columns and not has_writeable:
|
|
60
|
-
raise KeyError(f"{error_prefix} you must specify 'columns' OR 'writeable_columns'")
|
|
61
|
-
if not has_columns and not has_readable:
|
|
62
|
-
raise KeyError(f"{error_prefix} you must specify 'columns' OR 'readable_columns'")
|
|
63
|
-
if has_columns and has_writeable:
|
|
64
|
-
raise KeyError(f"{error_prefix} you must specify 'columns' OR 'writeable_columns', not both")
|
|
65
|
-
if has_columns and has_readable:
|
|
66
|
-
raise KeyError(f"{error_prefix} you must specify 'columns' OR 'readable_columns', not both")
|
|
67
|
-
if has_writeable and not has_readable:
|
|
68
|
-
raise KeyError(f"{error_prefix} you must specify 'readable_columns' if you specify 'writeable_columns'")
|
|
69
|
-
if has_readable and not has_writeable:
|
|
70
|
-
raise KeyError(f"{error_prefix} you must specify 'writeable_columns' if you specify 'readable_columns'")
|
|
71
|
-
|
|
72
|
-
for config_name in ["columns", "writeable_columns", "readable_columns"]:
|
|
73
|
-
if config_name not in configuration or configuration[config_name] is not None:
|
|
74
|
-
continue
|
|
75
|
-
if hasattr(configuration[config_name], "__iter__"):
|
|
76
|
-
continue
|
|
77
|
-
raise ValueError(
|
|
78
|
-
f"{error_prefix} '{config_name}' should be a list of column names "
|
|
79
|
-
+ f", not {str(type(configuration[config_name]))}"
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
if has_columns and not configuration["columns"]:
|
|
83
|
-
raise KeyError(f"{error_prefix} you must specify at least one column for 'columns'")
|
|
84
|
-
if has_writeable and not configuration["writeable_columns"]:
|
|
85
|
-
raise KeyError(f"{error_prefix} you must specify at least one column for 'writeable_columns'")
|
|
86
|
-
if has_readable and not configuration["readable_columns"]:
|
|
87
|
-
raise KeyError(f"{error_prefix} you must specify at least one column for 'readable_columns'")
|
|
88
|
-
writeable_columns = configuration["writeable_columns"] if has_writeable else configuration["columns"]
|
|
89
|
-
for column_name in writeable_columns:
|
|
90
|
-
if column_name not in self._columns:
|
|
91
|
-
raise KeyError(f"{error_prefix} specified writeable column '{column_name}' does not exist")
|
|
92
|
-
if not self._columns[column_name].is_writeable:
|
|
93
|
-
raise KeyError(f"{error_prefix} specified writeable column '{column_name}' is not writeable")
|
|
94
|
-
readable_columns = configuration["readable_columns"] if has_readable else configuration["columns"]
|
|
95
|
-
for column_name in readable_columns:
|
|
96
|
-
if column_name not in self._columns:
|
|
97
|
-
raise KeyError(f"{error_prefix} specified readable column '{column_name}' does not exist")
|
|
98
|
-
|
|
99
|
-
def _get_rw_columns(self, rw_type):
|
|
100
|
-
column_names = self.configuration("columns")
|
|
101
|
-
if column_names is None:
|
|
102
|
-
column_names = self.configuration(f"{rw_type}_columns")
|
|
103
|
-
wr_columns = OrderedDict()
|
|
104
|
-
for column_name in column_names:
|
|
105
|
-
if column_name not in self._columns:
|
|
106
|
-
class_name = self.__class__.__name__
|
|
107
|
-
model_class = self._model.__class__.__name__
|
|
108
|
-
raise ValueError(
|
|
109
|
-
f"Configuration error for {self.__class__.__name__}: handler was configured with {rw_type} "
|
|
110
|
-
+ f"column '{column_name}' but this column doesn't exist for model {model_class}"
|
|
111
|
-
)
|
|
112
|
-
wr_columns[column_name] = self._columns[column_name]
|
|
113
|
-
return wr_columns
|
|
114
|
-
|
|
115
|
-
def _get_readable_columns(self):
|
|
116
|
-
if self._readable_columns is None:
|
|
117
|
-
self._readable_columns = self._get_rw_columns("readable")
|
|
118
|
-
return self._readable_columns
|
|
119
|
-
|
|
120
|
-
def documentation_models(self):
|
|
121
|
-
schema_model_name = string.camel_case_to_snake_case(self._model.__class__.__name__)
|
|
122
|
-
|
|
123
|
-
return {
|
|
124
|
-
schema_model_name: autodoc.schema.Object(
|
|
125
|
-
"data",
|
|
126
|
-
children=self.documentation_data_schema(),
|
|
127
|
-
),
|
|
128
|
-
}
|
|
129
|
-
|
|
130
|
-
def _documentation(self, description="", response_description="", include_id_in_path=False):
|
|
131
|
-
nice_model = string.camel_case_to_words(self._model.__class__.__name__)
|
|
132
|
-
data_schema = self.documentation_data_schema()
|
|
133
|
-
schema_model_name = string.camel_case_to_snake_case(self._model.__class__.__name__)
|
|
134
|
-
|
|
135
|
-
authentication = self.configuration("authentication")
|
|
136
|
-
standard_error_responses = [
|
|
137
|
-
self.documentation_input_error_response(),
|
|
138
|
-
]
|
|
139
|
-
if not getattr(authentication, "is_public", False):
|
|
140
|
-
standard_error_responses.append(self.documentation_access_denied_response())
|
|
141
|
-
if getattr(authentication, "can_authorize", False):
|
|
142
|
-
standard_error_responses.append(self.documentation_unauthorized_response())
|
|
143
|
-
|
|
144
|
-
id_label = "id" if self.configuration("id_column_name") else self.id_column_name
|
|
145
|
-
|
|
146
|
-
url = self.configuration("base_url")
|
|
147
|
-
if include_id_in_path:
|
|
148
|
-
url = url.rstrip("/") + "/{" + id_label + "}"
|
|
149
|
-
|
|
150
|
-
return [
|
|
151
|
-
autodoc.request.Request(
|
|
152
|
-
description,
|
|
153
|
-
[
|
|
154
|
-
self.documentation_success_response(
|
|
155
|
-
autodoc.schema.Object(
|
|
156
|
-
self.auto_case_internal_column_name("data"),
|
|
157
|
-
children=data_schema,
|
|
158
|
-
model_name=schema_model_name,
|
|
159
|
-
),
|
|
160
|
-
description=description,
|
|
161
|
-
),
|
|
162
|
-
*standard_error_responses,
|
|
163
|
-
self.documentation_not_found(),
|
|
164
|
-
],
|
|
165
|
-
relative_path=url,
|
|
166
|
-
parameters=[
|
|
167
|
-
*self.documentation_write_parameters(nice_model, include_id_in_path=include_id_in_path),
|
|
168
|
-
],
|
|
169
|
-
root_properties={
|
|
170
|
-
"security": self.documentation_request_security(),
|
|
171
|
-
},
|
|
172
|
-
)
|
|
173
|
-
]
|
|
174
|
-
|
|
175
|
-
def documentation_write_parameters(self, model_name, include_id_in_path=False):
|
|
176
|
-
id_label = "id" if self.configuration("id_column_name") else self.id_column_name
|
|
177
|
-
parameters = [
|
|
178
|
-
autodoc.request.JSONBody(
|
|
179
|
-
column.documentation(name=self.auto_case_column_name(column.name, True)),
|
|
180
|
-
description=f"Set '{column.name}' for the {model_name}",
|
|
181
|
-
required=column.is_required,
|
|
182
|
-
)
|
|
183
|
-
for column in self._get_writeable_columns().values()
|
|
184
|
-
]
|
|
185
|
-
if include_id_in_path:
|
|
186
|
-
parameters.append(
|
|
187
|
-
autodoc.request.URLPath(
|
|
188
|
-
autodoc.schema.String(id_label),
|
|
189
|
-
description=f"The {id_label} of the record in question.",
|
|
190
|
-
required=True,
|
|
191
|
-
)
|
|
192
|
-
)
|
|
193
|
-
return parameters
|