clear-skies 1.19.22__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.19.22.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 +9 -38
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +14 -8
- clearskies/authentication/authorization_pass_through.py +22 -0
- 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 +56 -17
- clearskies/backends/api_backend.py +1128 -166
- 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 +117 -3
- clearskies/di/additional_config_auto_import.py +12 -0
- clearskies/di/di.py +717 -126
- 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 -152
- 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 +1894 -199
- 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.19.22.dist-info/METADATA +0 -46
- clear_skies-1.19.22.dist-info/RECORD +0 -206
- 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 -39
- 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 -138
- clearskies/binding_config.py +0 -16
- clearskies/column_types/__init__.py +0 -184
- clearskies/column_types/audit.py +0 -235
- clearskies/column_types/belongs_to.py +0 -250
- clearskies/column_types/boolean.py +0 -60
- clearskies/column_types/category_tree.py +0 -226
- 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 -108
- clearskies/column_types/datetime_micro.py +0 -12
- clearskies/column_types/email.py +0 -18
- clearskies/column_types/float.py +0 -43
- clearskies/column_types/has_many.py +0 -139
- 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/select.py +0 -11
- clearskies/column_types/string.py +0 -24
- clearskies/column_types/updated.py +0 -24
- clearskies/column_types/updated_micro.py +0 -24
- 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 -140
- 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 -473
- clearskies/handlers/callable.py +0 -189
- 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 -204
- 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 -68
- 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/minimum_length.py +0 -22
- 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 -345
- 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.19.22.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/list.py
DELETED
|
@@ -1,530 +0,0 @@
|
|
|
1
|
-
from .base import Base
|
|
2
|
-
from collections import OrderedDict
|
|
3
|
-
from .. import autodoc
|
|
4
|
-
from ..functional import string
|
|
5
|
-
import inspect
|
|
6
|
-
from ..column_types import BelongsTo
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class List(Base):
|
|
10
|
-
_model = None
|
|
11
|
-
_columns = None
|
|
12
|
-
_searchable_columns = None
|
|
13
|
-
_readable_columns = None
|
|
14
|
-
_prepared_models = None
|
|
15
|
-
expected_request_methods = "GET"
|
|
16
|
-
|
|
17
|
-
_configuration_defaults = {
|
|
18
|
-
"model": None,
|
|
19
|
-
"model_class": None,
|
|
20
|
-
"readable_columns": None,
|
|
21
|
-
"searchable_columns": None,
|
|
22
|
-
"sortable_columns": [],
|
|
23
|
-
"where": [],
|
|
24
|
-
"join": [],
|
|
25
|
-
"group_by": "",
|
|
26
|
-
"default_sort_column": "",
|
|
27
|
-
"default_sort_direction": "asc",
|
|
28
|
-
"default_limit": 100,
|
|
29
|
-
"max_limit": 200,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
def __init__(self, di):
|
|
33
|
-
super().__init__(di)
|
|
34
|
-
|
|
35
|
-
def handle(self, input_output):
|
|
36
|
-
models = self._prepared_models.clone()
|
|
37
|
-
for where in self.configuration("where"):
|
|
38
|
-
if callable(where):
|
|
39
|
-
models = self._di.call_function(
|
|
40
|
-
where, models=models, input_output=input_output, routing_data=input_output.routing_data()
|
|
41
|
-
)
|
|
42
|
-
models = models.where_for_request(
|
|
43
|
-
models,
|
|
44
|
-
input_output.routing_data(),
|
|
45
|
-
input_output.get_authorization_data(),
|
|
46
|
-
input_output,
|
|
47
|
-
)
|
|
48
|
-
limit = self.configuration("default_limit")
|
|
49
|
-
authorization = self._configuration.get("authorization", None)
|
|
50
|
-
if authorization and hasattr(authorization, "filter_models"):
|
|
51
|
-
models = authorization.filter_models(models, input_output.get_authorization_data(), input_output)
|
|
52
|
-
request_data = self.map_input_to_internal_names(input_output.request_data(False))
|
|
53
|
-
query_parameters = self.map_input_to_internal_names(input_output.get_query_parameters())
|
|
54
|
-
pagination_data = {}
|
|
55
|
-
for key in self._model.allowed_pagination_keys():
|
|
56
|
-
if key in request_data and key in query_parameters:
|
|
57
|
-
original_name = self.auto_case_internal_column_name(key)
|
|
58
|
-
return self.error(
|
|
59
|
-
input_output,
|
|
60
|
-
f"Ambiguous request: key '{original_name}' is present in both the JSON body and URL data",
|
|
61
|
-
)
|
|
62
|
-
if key in request_data:
|
|
63
|
-
pagination_data[key] = request_data[key]
|
|
64
|
-
del request_data[key]
|
|
65
|
-
if key in query_parameters:
|
|
66
|
-
pagination_data[key] = query_parameters[key]
|
|
67
|
-
del query_parameters[key]
|
|
68
|
-
if request_data or query_parameters or pagination_data:
|
|
69
|
-
error = self.check_request_data(request_data, query_parameters, pagination_data)
|
|
70
|
-
if error:
|
|
71
|
-
return self.error(input_output, error, 400)
|
|
72
|
-
[models, limit] = self.configure_models_from_request_data(
|
|
73
|
-
models, request_data, query_parameters, pagination_data
|
|
74
|
-
)
|
|
75
|
-
if not models.query_sorts:
|
|
76
|
-
models = models.sort_by(
|
|
77
|
-
self.configuration("default_sort_column"),
|
|
78
|
-
self.configuration("default_sort_direction"),
|
|
79
|
-
primary_table=models.table_name(),
|
|
80
|
-
)
|
|
81
|
-
|
|
82
|
-
return self.success(
|
|
83
|
-
input_output,
|
|
84
|
-
[self._model_as_json(model, input_output) for model in models],
|
|
85
|
-
number_results=len(models),
|
|
86
|
-
limit=limit,
|
|
87
|
-
next_page=models.next_page_data(),
|
|
88
|
-
)
|
|
89
|
-
|
|
90
|
-
def configure_models_from_request_data(self, models, request_data, query_parameters, pagination_data):
|
|
91
|
-
limit = int(query_parameters.get("limit", self.configuration("default_limit")))
|
|
92
|
-
models = models.limit(limit)
|
|
93
|
-
if pagination_data:
|
|
94
|
-
models = models.pagination(**pagination_data)
|
|
95
|
-
sort = query_parameters.get("sort")
|
|
96
|
-
direction = query_parameters.get("direction")
|
|
97
|
-
if sort and direction:
|
|
98
|
-
models = self._add_join(sort, models)
|
|
99
|
-
[sort_column, sort_table] = self._resolve_references_for_query(sort)
|
|
100
|
-
models = models.sort_by(sort_column, direction, primary_table=sort_table)
|
|
101
|
-
|
|
102
|
-
return [models, limit]
|
|
103
|
-
|
|
104
|
-
@property
|
|
105
|
-
def allowed_request_keys(self):
|
|
106
|
-
return ["sort", "direction", "limit"]
|
|
107
|
-
|
|
108
|
-
@property
|
|
109
|
-
def internal_request_keys(self):
|
|
110
|
-
return ["sort", "direction", "limit"]
|
|
111
|
-
|
|
112
|
-
def map_input_to_internal_names(self, input):
|
|
113
|
-
internal_request_keys = [*self.internal_request_keys, *self._model.allowed_pagination_keys()]
|
|
114
|
-
for key in internal_request_keys:
|
|
115
|
-
mapped_key = self.auto_case_internal_column_name(key)
|
|
116
|
-
if mapped_key != key and mapped_key in input:
|
|
117
|
-
input[key] = input[mapped_key]
|
|
118
|
-
del input[mapped_key]
|
|
119
|
-
# any non-internal fields are assumed to be column names and need to go
|
|
120
|
-
# through the full mapping
|
|
121
|
-
for key in set(self.allowed_request_keys) - set(internal_request_keys):
|
|
122
|
-
mapped_key = self.auto_case_column_name(key, True)
|
|
123
|
-
if mapped_key != key and mapped_key in input:
|
|
124
|
-
input[key] = input[mapped_key]
|
|
125
|
-
del input[mapped_key]
|
|
126
|
-
|
|
127
|
-
# finally, if we have a sort key set then convert the value to the properly cased column name
|
|
128
|
-
if "sort" in input:
|
|
129
|
-
# we can't just take the sort value and convert it to internal casing because camel/title case
|
|
130
|
-
# to snake_case can be ambiguous (while snake_case to camel/title is not)
|
|
131
|
-
sort_column_map = {}
|
|
132
|
-
for internal_name in self.configuration("sortable_columns"):
|
|
133
|
-
external_name = self.auto_case_column_name(internal_name, True)
|
|
134
|
-
sort_column_map[external_name] = internal_name
|
|
135
|
-
# sometimes the sort may be a list of directives
|
|
136
|
-
if type(input["sort"]) == list:
|
|
137
|
-
for index, sort_entry in enumerate(input["sort"]):
|
|
138
|
-
if input["sort"][index]["column"] in sort_column_map:
|
|
139
|
-
input["sort"][index]["column"] = sort_column_map[input["sort"][index]["column"]]
|
|
140
|
-
else:
|
|
141
|
-
if input["sort"] in sort_column_map:
|
|
142
|
-
input["sort"] = sort_column_map[input["sort"]]
|
|
143
|
-
|
|
144
|
-
return input
|
|
145
|
-
|
|
146
|
-
def check_request_data(self, request_data, query_parameters, pagination_data):
|
|
147
|
-
if pagination_data:
|
|
148
|
-
error = self._model.validate_pagination_kwargs(pagination_data, self.auto_case_internal_column_name)
|
|
149
|
-
if error:
|
|
150
|
-
return error
|
|
151
|
-
for key in request_data.keys():
|
|
152
|
-
if key not in self.allowed_request_keys or key in ["sort", "direction", "limit"]:
|
|
153
|
-
return f"Invalid request parameter found in request body: '{key}'"
|
|
154
|
-
for key in query_parameters.keys():
|
|
155
|
-
if key not in self.allowed_request_keys:
|
|
156
|
-
return f"Invalid request parameter found in URL data: '{key}'"
|
|
157
|
-
if key in request_data:
|
|
158
|
-
return f"Ambiguous request: '{key}' was found in both the request body and URL data"
|
|
159
|
-
limit = query_parameters.get("limit")
|
|
160
|
-
if limit is not None and type(limit) != int and type(limit) != float and type(limit) != str:
|
|
161
|
-
return "Invalid request: 'limit' should be an integer"
|
|
162
|
-
if limit:
|
|
163
|
-
try:
|
|
164
|
-
limit = int(limit)
|
|
165
|
-
except ValueError:
|
|
166
|
-
return "Invalid request: 'limit' should be an integer"
|
|
167
|
-
if limit and limit > self.configuration("max_limit"):
|
|
168
|
-
return f"Invalid request: 'limit' must be at most {self.configuration('max_limit')}"
|
|
169
|
-
allowed_sort_columns = self.configuration("sortable_columns")
|
|
170
|
-
if not allowed_sort_columns:
|
|
171
|
-
allowed_sort_columns = self._columns
|
|
172
|
-
sort = self._from_either(request_data, query_parameters, "sort")
|
|
173
|
-
direction = self._from_either(request_data, query_parameters, "direction")
|
|
174
|
-
if sort and type(sort) != str:
|
|
175
|
-
return "Invalid request: 'sort' should be a string"
|
|
176
|
-
if direction and type(direction) != str:
|
|
177
|
-
return "Invalid request: 'direction' should be a string"
|
|
178
|
-
if sort or direction:
|
|
179
|
-
if (sort and not direction) or (direction and not sort):
|
|
180
|
-
return "You must specify 'sort' and 'direction' together in the request - not just one of them"
|
|
181
|
-
if sort not in allowed_sort_columns:
|
|
182
|
-
return f"Invalid request: invalid sort column"
|
|
183
|
-
if direction.lower() not in ["asc", "desc"]:
|
|
184
|
-
return "Invalid request: direction must be 'asc' or 'desc'"
|
|
185
|
-
return self.check_search_in_request_data(request_data, query_parameters)
|
|
186
|
-
|
|
187
|
-
def check_search_in_request_data(self, request_data, query_parameters):
|
|
188
|
-
return None
|
|
189
|
-
|
|
190
|
-
def _unpack_column_name_with_reference(self, column_name):
|
|
191
|
-
if "." not in column_name:
|
|
192
|
-
return [column_name, ""]
|
|
193
|
-
return column_name.split(".", 1)
|
|
194
|
-
|
|
195
|
-
def configure(self, configuration):
|
|
196
|
-
super().configure(configuration)
|
|
197
|
-
# performance optimizations! First, take any of our configuration options that affect
|
|
198
|
-
# the search results and create a models class with those built in
|
|
199
|
-
self._prepared_models = self._model
|
|
200
|
-
for where in self.configuration("where"):
|
|
201
|
-
if type(where) == str:
|
|
202
|
-
self._prepared_models = self._prepared_models.where(where)
|
|
203
|
-
for join in self.configuration("join"):
|
|
204
|
-
self._prepared_models = self._prepared_models.join(join)
|
|
205
|
-
if self.configuration("group_by"):
|
|
206
|
-
self._prepared_models = self._prepared_models.group_by(self.configuration("group_by"))
|
|
207
|
-
self._prepared_models = self._prepared_models.limit(self.configuration("default_limit"))
|
|
208
|
-
|
|
209
|
-
if self._prepared_models.supports_n_plus_one():
|
|
210
|
-
for column in self._get_readable_columns().values():
|
|
211
|
-
self._prepared_models = column.configure_n_plus_one(self._prepared_models)
|
|
212
|
-
|
|
213
|
-
def _check_configuration(self, configuration):
|
|
214
|
-
super()._check_configuration(configuration)
|
|
215
|
-
error_prefix = "Configuration error for %s:" % (self.__class__.__name__)
|
|
216
|
-
has_model_class = ("model_class" in configuration) and configuration["model_class"] is not None
|
|
217
|
-
has_model = ("model" in configuration) and configuration["model"] is not None
|
|
218
|
-
if not has_model and not has_model_class:
|
|
219
|
-
raise KeyError(f"{error_prefix} you must specify 'model' or 'model_class'")
|
|
220
|
-
if has_model and has_model_class:
|
|
221
|
-
raise KeyError(f"{error_prefix} you specified both 'model' and 'model_class', but can only provide one")
|
|
222
|
-
if has_model and inspect.isclass(configuration["model"]):
|
|
223
|
-
raise ValueError(
|
|
224
|
-
"{error_prefix} you must provide a model instance in the 'model' configuration setting, but a class was provided instead"
|
|
225
|
-
)
|
|
226
|
-
self._model = self._di.build(configuration["model_class"]) if has_model_class else configuration["model"]
|
|
227
|
-
self._columns = self._model.columns(overrides=configuration.get("column_overrides"))
|
|
228
|
-
model_class_name = self._model.__class__.__name__
|
|
229
|
-
# checks for searchable_columns and readable_columns
|
|
230
|
-
self._check_columns_in_configuration(configuration, "readable_columns")
|
|
231
|
-
# the List base class doesn't use searchable columns so just ignore this check for List
|
|
232
|
-
if type(self) != List:
|
|
233
|
-
self._check_columns_in_configuration(configuration, "searchable_columns")
|
|
234
|
-
|
|
235
|
-
if "default_sort_column" not in configuration:
|
|
236
|
-
raise ValueError(f"{error_prefix} missing required configuration 'default_sort_column'")
|
|
237
|
-
|
|
238
|
-
# sortable_columns, wheres, and joins should all be iterables
|
|
239
|
-
for config_name, contents in {
|
|
240
|
-
"sortable_columns": "column names",
|
|
241
|
-
"where": "conditions",
|
|
242
|
-
"join": "joins",
|
|
243
|
-
}.items():
|
|
244
|
-
if config_name not in configuration:
|
|
245
|
-
continue
|
|
246
|
-
if not hasattr(configuration[config_name], "__iter__") or type(configuration[config_name]) == str:
|
|
247
|
-
raise ValueError(
|
|
248
|
-
f"{error_prefix} '{config_name}' should be an iterable of {contents} "
|
|
249
|
-
+ f", not {str(type(configuration[config_name]))}"
|
|
250
|
-
)
|
|
251
|
-
|
|
252
|
-
# checks for sortable_columns
|
|
253
|
-
if configuration.get("sortable_columns"):
|
|
254
|
-
self._check_columns_in_configuration(configuration, "searchable_columns")
|
|
255
|
-
|
|
256
|
-
# common checks for group_by and default_sort_column
|
|
257
|
-
for config_name in ["group_by", "default_sort_column"]:
|
|
258
|
-
value = configuration.get(config_name)
|
|
259
|
-
if not value:
|
|
260
|
-
continue
|
|
261
|
-
# we're being lazy for now and not checking complicated values
|
|
262
|
-
if "." in value:
|
|
263
|
-
continue
|
|
264
|
-
if value not in self._columns:
|
|
265
|
-
raise ValueError(
|
|
266
|
-
f"{error_prefix} '{config_name}' references column named {column_name} "
|
|
267
|
-
+ f"but this column does not exist for model '{model_class_name}'"
|
|
268
|
-
)
|
|
269
|
-
|
|
270
|
-
for config_name in ["default_page_length", "max_page_length"]:
|
|
271
|
-
if config_name in configuration and type(configuration[config_name]) != int:
|
|
272
|
-
raise ValueError(
|
|
273
|
-
f"{error_prefix} '{config_name}' should be an int, not {str(type(configuration[config_name]))}"
|
|
274
|
-
)
|
|
275
|
-
|
|
276
|
-
def _check_columns_in_configuration(self, configuration, config_name):
|
|
277
|
-
error_prefix = "Configuration error for %s:" % (self.__class__.__name__)
|
|
278
|
-
model_class_name = self._model.__class__.__name__
|
|
279
|
-
if not configuration.get(config_name):
|
|
280
|
-
raise ValueError(f"{error_prefix} missing required configuration '{config_name}'")
|
|
281
|
-
if not hasattr(configuration[config_name], "__iter__"):
|
|
282
|
-
raise ValueError(
|
|
283
|
-
f"{error_prefix} '{config_name}' should be an iterable of column names "
|
|
284
|
-
+ f", not {str(type(configuration[config_name]))}"
|
|
285
|
-
)
|
|
286
|
-
for column_name in configuration[config_name]:
|
|
287
|
-
relationship_reference = None
|
|
288
|
-
if config_name == "searchable_columns" or config_name == "sortable_columns":
|
|
289
|
-
[column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
|
|
290
|
-
if column_name not in self._columns:
|
|
291
|
-
raise ValueError(
|
|
292
|
-
f"{error_prefix} '{config_name}' references column named {column_name} "
|
|
293
|
-
+ f"but this column does not exist for model '{model_class_name}'"
|
|
294
|
-
)
|
|
295
|
-
column = self._columns[column_name]
|
|
296
|
-
if config_name == "readable_columns" and not column.is_readable:
|
|
297
|
-
raise ValueError(
|
|
298
|
-
f"{error_prefix} '{config_name}' references column named {column_name} "
|
|
299
|
-
+ f"but this column does not exist for model '{model_class_name}'"
|
|
300
|
-
)
|
|
301
|
-
if relationship_reference:
|
|
302
|
-
if not isinstance(column, BelongsTo):
|
|
303
|
-
raise ValueError(
|
|
304
|
-
f"{error_prefix} '{config_name}' references {column_name}.{relationship_reference}. "
|
|
305
|
-
+ f"For this to work, {column_name} must be a belongs to relatiionship, but it isn't."
|
|
306
|
-
)
|
|
307
|
-
if relationship_reference not in column.parent_columns:
|
|
308
|
-
parent_class = column.config("parent_models_class").__name__
|
|
309
|
-
raise ValueError(
|
|
310
|
-
f"{error_prefix} '{config_name}' references {column_name}.{relationship_reference}, "
|
|
311
|
-
+ f"but {relationship_reference} is not a valid column in the BelongsTo model class, {parent_class}."
|
|
312
|
-
)
|
|
313
|
-
|
|
314
|
-
def _resolve_references_for_query(self, column_name):
|
|
315
|
-
"""
|
|
316
|
-
Takes the column name and returns the name and table.
|
|
317
|
-
|
|
318
|
-
If it's just a column name, we assume the table is the table for our model class.
|
|
319
|
-
If it's something like `belongs_to_column.column_name`, then it will find the appropriate
|
|
320
|
-
table reference.
|
|
321
|
-
"""
|
|
322
|
-
if not column_name:
|
|
323
|
-
return [None, None]
|
|
324
|
-
[column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
|
|
325
|
-
if not relationship_reference:
|
|
326
|
-
return [column_name, self._model.table_name()]
|
|
327
|
-
|
|
328
|
-
belongs_to_column = self._columns[column_name]
|
|
329
|
-
return [relationship_reference, belongs_to_column.join_table_alias()]
|
|
330
|
-
|
|
331
|
-
def _add_join(self, column_name, models):
|
|
332
|
-
"""
|
|
333
|
-
Adds a join to the query for the given column name in the case where it references a column in a belongs to.
|
|
334
|
-
|
|
335
|
-
If column_name is something like `belongs_to_column.column_name`, this will add have the belongs to column
|
|
336
|
-
add it's typical join condition, so that further sorting/searching can work.
|
|
337
|
-
|
|
338
|
-
If column_name is empty, or doesn't contain a period, then this does nothing.
|
|
339
|
-
"""
|
|
340
|
-
if not column_name:
|
|
341
|
-
return models
|
|
342
|
-
[column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
|
|
343
|
-
if not relationship_reference:
|
|
344
|
-
return models
|
|
345
|
-
return self._columns[column_name].add_join(models)
|
|
346
|
-
|
|
347
|
-
def _from_either(self, request_data, query_parameters, key, default=None, ignore_none=True):
|
|
348
|
-
"""
|
|
349
|
-
Returns the key from either object. Assumes it is not present in both
|
|
350
|
-
"""
|
|
351
|
-
if key in request_data:
|
|
352
|
-
if request_data[key] is not None or not ignore_none:
|
|
353
|
-
return request_data[key]
|
|
354
|
-
if key in query_parameters:
|
|
355
|
-
if query_parameters[key] is not None or not ignore_none:
|
|
356
|
-
return query_parameters[key]
|
|
357
|
-
return default
|
|
358
|
-
|
|
359
|
-
def _get_columns(self, column_type):
|
|
360
|
-
resolved_columns = OrderedDict()
|
|
361
|
-
for column_name in self.configuration(f"{column_type}_columns"):
|
|
362
|
-
if column_type == "searchable":
|
|
363
|
-
[column_name, relationship_reference] = self._unpack_column_name_with_reference(column_name)
|
|
364
|
-
if column_name not in self._columns:
|
|
365
|
-
class_name = self.__class__.__name__
|
|
366
|
-
model_class = self._model.__class__.__name__
|
|
367
|
-
raise ValueError(
|
|
368
|
-
f"Handler {class_name} was configured with {column_type} column '{column_name}' but this "
|
|
369
|
-
+ f"column doesn't exist for model {model_class}"
|
|
370
|
-
)
|
|
371
|
-
resolved_columns[column_name] = self._columns[column_name]
|
|
372
|
-
return resolved_columns
|
|
373
|
-
|
|
374
|
-
def _get_readable_columns(self):
|
|
375
|
-
if self._readable_columns is None:
|
|
376
|
-
self._readable_columns = self._get_columns("readable")
|
|
377
|
-
return self._readable_columns
|
|
378
|
-
|
|
379
|
-
def _get_searchable_columns(self):
|
|
380
|
-
if self._searchable_columns is None:
|
|
381
|
-
self._searchable_columns = self._get_columns("searchable")
|
|
382
|
-
return self._searchable_columns
|
|
383
|
-
|
|
384
|
-
def documentation(self):
|
|
385
|
-
nice_model = string.camel_case_to_words(self._model.__class__.__name__)
|
|
386
|
-
schema_model_name = string.camel_case_to_snake_case(self._model.__class__.__name__)
|
|
387
|
-
data_schema = self.documentation_data_schema()
|
|
388
|
-
|
|
389
|
-
authentication = self.configuration("authentication")
|
|
390
|
-
standard_error_responses = []
|
|
391
|
-
if not getattr(authentication, "is_public", False):
|
|
392
|
-
standard_error_responses.append(self.documentation_access_denied_response())
|
|
393
|
-
if getattr(authentication, "can_authorize", False):
|
|
394
|
-
standard_error_responses.append(self.documentation_unauthorized_response())
|
|
395
|
-
|
|
396
|
-
return [
|
|
397
|
-
autodoc.request.Request(
|
|
398
|
-
f"Fetch the list of current {nice_model} records",
|
|
399
|
-
[
|
|
400
|
-
self.documentation_success_response(
|
|
401
|
-
autodoc.schema.Array(
|
|
402
|
-
self.auto_case_internal_column_name("data"),
|
|
403
|
-
autodoc.schema.Object(nice_model, children=data_schema, model_name=schema_model_name),
|
|
404
|
-
),
|
|
405
|
-
description=f"The matching {nice_model} records",
|
|
406
|
-
include_pagination=True,
|
|
407
|
-
),
|
|
408
|
-
*standard_error_responses,
|
|
409
|
-
self.documentation_generic_error_response(),
|
|
410
|
-
],
|
|
411
|
-
relative_path=self.configuration("base_url"),
|
|
412
|
-
request_methods=self.expected_request_methods,
|
|
413
|
-
parameters=self.documentation_request_parameters(),
|
|
414
|
-
root_properties={
|
|
415
|
-
"security": self.documentation_request_security(),
|
|
416
|
-
},
|
|
417
|
-
),
|
|
418
|
-
]
|
|
419
|
-
|
|
420
|
-
def documentation_request_parameters(self):
|
|
421
|
-
return [
|
|
422
|
-
*self.documentation_url_pagination_parameters(),
|
|
423
|
-
*self.documentation_url_sort_parameters(),
|
|
424
|
-
]
|
|
425
|
-
|
|
426
|
-
def documentation_models(self):
|
|
427
|
-
schema_model_name = string.camel_case_to_snake_case(self._model.__class__.__name__)
|
|
428
|
-
|
|
429
|
-
return {
|
|
430
|
-
schema_model_name: autodoc.schema.Object(
|
|
431
|
-
self.auto_case_internal_column_name("data"),
|
|
432
|
-
children=self.documentation_data_schema(),
|
|
433
|
-
),
|
|
434
|
-
}
|
|
435
|
-
|
|
436
|
-
def documentation_id_url_parameter(self):
|
|
437
|
-
id_column_name = self.id_column_name
|
|
438
|
-
if id_column_name in self._columns:
|
|
439
|
-
id_column_schema = self._columns[id_column_name].documentation()
|
|
440
|
-
else:
|
|
441
|
-
id_column_schema = autodoc.schema.Integer(self.auto_case_internal_column_name("id"))
|
|
442
|
-
return autodoc.request.URLPath(
|
|
443
|
-
id_column_schema,
|
|
444
|
-
description="The id of the record to fetch",
|
|
445
|
-
required=True,
|
|
446
|
-
)
|
|
447
|
-
|
|
448
|
-
def documentation_url_pagination_parameters(self):
|
|
449
|
-
url_parameters = [
|
|
450
|
-
autodoc.request.URLParameter(
|
|
451
|
-
autodoc.schema.Integer(self.auto_case_internal_column_name("limit")),
|
|
452
|
-
description="The number of records to return",
|
|
453
|
-
),
|
|
454
|
-
]
|
|
455
|
-
|
|
456
|
-
for parameter in self._model.documentation_pagination_parameters(self.auto_case_internal_column_name):
|
|
457
|
-
(schema, description) = parameter
|
|
458
|
-
url_parameters.append(autodoc.request.URLParameter(schema, description=description))
|
|
459
|
-
|
|
460
|
-
return url_parameters
|
|
461
|
-
|
|
462
|
-
def documentation_url_sort_parameters(self):
|
|
463
|
-
sort_columns = self.configuration("sortable_columns")
|
|
464
|
-
if not sort_columns:
|
|
465
|
-
sort_columns = self._columns.keys()
|
|
466
|
-
sort_columns = [self.auto_case_column_name(internal_name, True) for internal_name in sort_columns]
|
|
467
|
-
directions = [self.auto_case_column_name(internal_name, True) for internal_name in ["asc", "desc"]]
|
|
468
|
-
|
|
469
|
-
return [
|
|
470
|
-
autodoc.request.URLParameter(
|
|
471
|
-
autodoc.schema.Enum(
|
|
472
|
-
self.auto_case_internal_column_name("sort"),
|
|
473
|
-
sort_columns,
|
|
474
|
-
autodoc.schema.String(self.auto_case_internal_column_name("sort")),
|
|
475
|
-
example=self.auto_case_column_name("name", True),
|
|
476
|
-
),
|
|
477
|
-
description=f"Column to sort by",
|
|
478
|
-
),
|
|
479
|
-
autodoc.request.URLParameter(
|
|
480
|
-
autodoc.schema.Enum(
|
|
481
|
-
self.auto_case_internal_column_name("direction"),
|
|
482
|
-
directions,
|
|
483
|
-
autodoc.schema.String(self.auto_case_internal_column_name("direction")),
|
|
484
|
-
example=self.auto_case_column_name("asc", True),
|
|
485
|
-
),
|
|
486
|
-
description=f"Direction to sort",
|
|
487
|
-
),
|
|
488
|
-
]
|
|
489
|
-
|
|
490
|
-
def documentation_json_pagination_parameters(self):
|
|
491
|
-
json_parameters = [
|
|
492
|
-
autodoc.request.JSONBody(
|
|
493
|
-
autodoc.schema.Integer(self.auto_case_internal_column_name("limit")),
|
|
494
|
-
description="The number of records to return",
|
|
495
|
-
),
|
|
496
|
-
]
|
|
497
|
-
|
|
498
|
-
for parameter in self._model.documentation_pagination_parameters(self.auto_case_internal_column_name):
|
|
499
|
-
(schema, description) = parameter
|
|
500
|
-
json_parameters.append(autodoc.request.JSONBody(schema, description=description))
|
|
501
|
-
|
|
502
|
-
return json_parameters
|
|
503
|
-
|
|
504
|
-
def documentation_json_sort_parameters(self):
|
|
505
|
-
sort_columns = self.configuration("sortable_columns")
|
|
506
|
-
if not sort_columns:
|
|
507
|
-
sort_columns = self._columns.keys()
|
|
508
|
-
sort_columns = [self.auto_case_column_name(internal_name, True) for internal_name in sort_columns]
|
|
509
|
-
directions = [self.auto_case_column_name(internal_name, True) for internal_name in ["asc", "desc"]]
|
|
510
|
-
|
|
511
|
-
return [
|
|
512
|
-
autodoc.request.JSONBody(
|
|
513
|
-
autodoc.schema.Enum(
|
|
514
|
-
self.auto_case_internal_column_name("sort"),
|
|
515
|
-
sort_columns,
|
|
516
|
-
autodoc.schema.String(self.auto_case_internal_column_name("sort")),
|
|
517
|
-
example=self.auto_case_column_name("name", True),
|
|
518
|
-
),
|
|
519
|
-
description=f"Column to sort by",
|
|
520
|
-
),
|
|
521
|
-
autodoc.request.JSONBody(
|
|
522
|
-
autodoc.schema.Enum(
|
|
523
|
-
self.auto_case_internal_column_name("direction"),
|
|
524
|
-
directions,
|
|
525
|
-
autodoc.schema.String(self.auto_case_internal_column_name("direction")),
|
|
526
|
-
example=self.auto_case_column_name("asc", True),
|
|
527
|
-
),
|
|
528
|
-
description=f"Direction to sort",
|
|
529
|
-
),
|
|
530
|
-
]
|
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
from .base import Base
|
|
2
|
-
from io import StringIO
|
|
3
|
-
import sys
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
class Capturing:
|
|
7
|
-
def __init__(self, sys):
|
|
8
|
-
self.sys = sys
|
|
9
|
-
self.output = []
|
|
10
|
-
|
|
11
|
-
def __enter__(self):
|
|
12
|
-
self._stdout = sys.stdout
|
|
13
|
-
sys.stdout = self._stringio = StringIO()
|
|
14
|
-
return self
|
|
15
|
-
|
|
16
|
-
def __exit__(self, *args):
|
|
17
|
-
self.output.extend(self._stringio.getvalue().splitlines())
|
|
18
|
-
del self._stringio
|
|
19
|
-
sys.stdout = self._stdout
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
class Mygrations(Base):
|
|
23
|
-
_connection = None
|
|
24
|
-
_sys = None
|
|
25
|
-
|
|
26
|
-
_configuration_defaults = {
|
|
27
|
-
"command": None,
|
|
28
|
-
"allow_input": False,
|
|
29
|
-
"sql": None,
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
def __init__(self, di, connection_no_autocommit, sys):
|
|
33
|
-
super().__init__(di)
|
|
34
|
-
self._connection = connection_no_autocommit
|
|
35
|
-
self._sys = sys
|
|
36
|
-
|
|
37
|
-
def handle(self, input_output):
|
|
38
|
-
from mygrations.core.commands import execute, commands
|
|
39
|
-
|
|
40
|
-
command = self._from_input_or_config("command", input_output)
|
|
41
|
-
if not command:
|
|
42
|
-
return self.error(
|
|
43
|
-
input_output,
|
|
44
|
-
"Must provide 'command' in handler configuration, or in user input after setting the allow_input flag in the handler configuration",
|
|
45
|
-
400,
|
|
46
|
-
)
|
|
47
|
-
sql = self._from_input_or_config("sql", input_output)
|
|
48
|
-
if not sql:
|
|
49
|
-
return self.error(
|
|
50
|
-
input_output,
|
|
51
|
-
"Must provide 'sql' in handler configuration, or in user input after setting the allow_input flag in the handler configuration",
|
|
52
|
-
400,
|
|
53
|
-
)
|
|
54
|
-
|
|
55
|
-
if command not in commands:
|
|
56
|
-
return self.error(
|
|
57
|
-
input_output,
|
|
58
|
-
"Invalid mygrations command. See allowed list: https://github.com/cmancone/mygrations#command-line-usage",
|
|
59
|
-
400,
|
|
60
|
-
)
|
|
61
|
-
|
|
62
|
-
[output, success] = execute(command, {"connection": self._connection, "sql_files": sql}, print_results=False)
|
|
63
|
-
if success:
|
|
64
|
-
return self.success(input_output, output)
|
|
65
|
-
else:
|
|
66
|
-
return self.error(input_output, "\n".join(output), 400)
|
|
67
|
-
|
|
68
|
-
def _from_input_or_config(self, key, input_output):
|
|
69
|
-
if self.configuration("allow_input"):
|
|
70
|
-
input_data = input_output.json_body(required=False)
|
|
71
|
-
if input_data is not None and key in input_data:
|
|
72
|
-
return input_data[key]
|
|
73
|
-
return self.configuration(key)
|
|
74
|
-
|
|
75
|
-
def _check_configuration(self, configuration):
|
|
76
|
-
super()._check_configuration(configuration)
|
|
77
|
-
try:
|
|
78
|
-
import mygrations
|
|
79
|
-
except ModuleNotFoundError:
|
|
80
|
-
raise ModuleNotFoundError(
|
|
81
|
-
"You must install mygrations to use the mygrations handler. See https://github.com/cmancone/mygrations#installation"
|
|
82
|
-
)
|
|
@@ -1,47 +0,0 @@
|
|
|
1
|
-
from .routing import Routing
|
|
2
|
-
from abc import abstractmethod
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class RequestMethodRouting(Routing):
|
|
6
|
-
def __init__(self, di):
|
|
7
|
-
super().__init__(di)
|
|
8
|
-
|
|
9
|
-
@abstractmethod
|
|
10
|
-
def method_handler_map(self):
|
|
11
|
-
pass
|
|
12
|
-
|
|
13
|
-
def handler_classes(self, configuration):
|
|
14
|
-
return self.method_handler_map().values()
|
|
15
|
-
|
|
16
|
-
def handle(self, input_output):
|
|
17
|
-
request_method = input_output.get_request_method()
|
|
18
|
-
method_handler_map = self.method_handler_map()
|
|
19
|
-
if not request_method in method_handler_map:
|
|
20
|
-
return self.error(input_output, "Invalid request method", 400)
|
|
21
|
-
handler = self.build_handler(method_handler_map[request_method])
|
|
22
|
-
return handler(input_output)
|
|
23
|
-
|
|
24
|
-
def documentation(self):
|
|
25
|
-
docs = []
|
|
26
|
-
for method, handler in self.method_handler_map.items():
|
|
27
|
-
doc = self.build_handler(method_handler_map[request_method]).documentation()
|
|
28
|
-
if not doc:
|
|
29
|
-
continue
|
|
30
|
-
doc.set_request_methods(method)
|
|
31
|
-
docs.append(doc)
|
|
32
|
-
return docs
|
|
33
|
-
|
|
34
|
-
def documentation_security_schemes(self):
|
|
35
|
-
schemes = {}
|
|
36
|
-
for method, handler in self.method_handler_map.items():
|
|
37
|
-
schemes = {
|
|
38
|
-
**schemes,
|
|
39
|
-
**self.build_handler(method_handler_map[request_method]).documentation_security_schemes(),
|
|
40
|
-
}
|
|
41
|
-
return schemes
|
|
42
|
-
|
|
43
|
-
def documentation_models(self):
|
|
44
|
-
models = {}
|
|
45
|
-
for method, handler in self.method_handler_map.items():
|
|
46
|
-
models = {**models, **self.build_handler(method_handler_map[request_method]).documentation_models()}
|
|
47
|
-
return models
|