clear-skies 1.22.10__py3-none-any.whl → 2.0.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clear_skies-2.0.23.dist-info/METADATA +76 -0
- clear_skies-2.0.23.dist-info/RECORD +265 -0
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info}/WHEEL +1 -1
- clearskies/__init__.py +37 -21
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +8 -39
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +14 -8
- clearskies/authentication/authorization_pass_through.py +14 -10
- clearskies/authentication/jwks.py +135 -58
- clearskies/authentication/public.py +3 -26
- clearskies/authentication/secret_bearer.py +515 -44
- clearskies/autodoc/formats/oai3_json/__init__.py +2 -2
- clearskies/autodoc/formats/oai3_json/oai3_json.py +11 -9
- clearskies/autodoc/formats/oai3_json/parameter.py +6 -3
- clearskies/autodoc/formats/oai3_json/request.py +7 -5
- clearskies/autodoc/formats/oai3_json/response.py +7 -4
- clearskies/autodoc/formats/oai3_json/schema/object.py +10 -1
- clearskies/autodoc/request/__init__.py +2 -0
- clearskies/autodoc/request/header.py +4 -6
- clearskies/autodoc/request/json_body.py +4 -6
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +16 -4
- clearskies/autodoc/request/url_parameter.py +4 -6
- clearskies/autodoc/request/url_path.py +4 -6
- clearskies/autodoc/schema/__init__.py +4 -2
- clearskies/autodoc/schema/array.py +5 -6
- clearskies/autodoc/schema/boolean.py +4 -10
- clearskies/autodoc/schema/date.py +0 -3
- clearskies/autodoc/schema/datetime.py +1 -4
- clearskies/autodoc/schema/double.py +0 -3
- clearskies/autodoc/schema/enum.py +4 -2
- clearskies/autodoc/schema/integer.py +4 -9
- clearskies/autodoc/schema/long.py +0 -3
- clearskies/autodoc/schema/number.py +4 -9
- clearskies/autodoc/schema/object.py +5 -7
- clearskies/autodoc/schema/password.py +0 -3
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +4 -10
- clearskies/backends/__init__.py +55 -20
- clearskies/backends/api_backend.py +1118 -280
- clearskies/backends/backend.py +54 -85
- clearskies/backends/cursor_backend.py +246 -191
- clearskies/backends/memory_backend.py +514 -208
- clearskies/backends/secrets_backend.py +68 -31
- clearskies/column.py +1221 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +306 -0
- clearskies/columns/belongs_to_id.py +478 -0
- clearskies/columns/belongs_to_model.py +129 -0
- clearskies/columns/belongs_to_self.py +109 -0
- clearskies/columns/boolean.py +110 -0
- clearskies/columns/category_tree.py +273 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +126 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +92 -0
- clearskies/columns/created_by_authorization_data.py +114 -0
- clearskies/columns/created_by_header.py +103 -0
- clearskies/columns/created_by_ip.py +90 -0
- clearskies/columns/created_by_routing_data.py +102 -0
- clearskies/columns/created_by_user_agent.py +89 -0
- clearskies/columns/date.py +232 -0
- clearskies/columns/datetime.py +284 -0
- clearskies/columns/email.py +78 -0
- clearskies/columns/float.py +149 -0
- clearskies/columns/has_many.py +529 -0
- clearskies/columns/has_many_self.py +62 -0
- clearskies/columns/has_one.py +21 -0
- clearskies/columns/integer.py +158 -0
- clearskies/columns/json.py +126 -0
- clearskies/columns/many_to_many_ids.py +335 -0
- clearskies/columns/many_to_many_ids_with_data.py +274 -0
- clearskies/columns/many_to_many_models.py +156 -0
- clearskies/columns/many_to_many_pivots.py +132 -0
- clearskies/columns/phone.py +162 -0
- clearskies/columns/select.py +95 -0
- clearskies/columns/string.py +102 -0
- clearskies/columns/timestamp.py +164 -0
- clearskies/columns/updated.py +107 -0
- clearskies/columns/uuid.py +83 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +170 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +15 -0
- clearskies/configs/any_dict.py +24 -0
- clearskies/configs/any_dict_or_callable.py +25 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +18 -0
- clearskies/configs/boolean_or_callable.py +20 -0
- clearskies/configs/callable_config.py +20 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +26 -0
- clearskies/configs/datetime.py +20 -0
- clearskies/configs/datetime_or_callable.py +21 -0
- clearskies/configs/email.py +10 -0
- clearskies/configs/email_list.py +17 -0
- clearskies/configs/email_list_or_callable.py +17 -0
- clearskies/configs/email_or_email_list_or_callable.py +59 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +29 -0
- clearskies/configs/float.py +18 -0
- clearskies/configs/float_or_callable.py +20 -0
- clearskies/configs/headers.py +28 -0
- clearskies/configs/integer.py +18 -0
- clearskies/configs/integer_or_callable.py +20 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +32 -0
- clearskies/configs/list_any_dict_or_callable.py +33 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +67 -0
- clearskies/configs/model_columns.py +58 -0
- clearskies/configs/model_destination_name.py +26 -0
- clearskies/configs/model_to_id_column.py +45 -0
- clearskies/configs/readable_model_column.py +11 -0
- clearskies/configs/readable_model_columns.py +11 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +11 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +28 -0
- clearskies/configs/select_list.py +49 -0
- clearskies/configs/string.py +31 -0
- clearskies/configs/string_dict.py +34 -0
- clearskies/configs/string_list.py +47 -0
- clearskies/configs/string_list_or_callable.py +48 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +20 -0
- clearskies/configs/timezone.py +20 -0
- clearskies/configs/url.py +25 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +11 -0
- clearskies/configs/writeable_model_columns.py +11 -0
- clearskies/configurable.py +78 -0
- clearskies/contexts/__init__.py +8 -8
- clearskies/contexts/cli.py +129 -43
- clearskies/contexts/context.py +93 -56
- clearskies/contexts/wsgi.py +79 -33
- clearskies/contexts/wsgi_ref.py +87 -0
- clearskies/cursors/__init__.py +7 -0
- clearskies/cursors/cursor.py +166 -0
- clearskies/cursors/from_environment/__init__.py +5 -0
- clearskies/cursors/from_environment/mysql.py +51 -0
- clearskies/cursors/from_environment/postgresql.py +49 -0
- clearskies/cursors/from_environment/sqlite.py +35 -0
- clearskies/cursors/mysql.py +61 -0
- clearskies/cursors/postgresql.py +61 -0
- clearskies/cursors/sqlite.py +62 -0
- clearskies/decorators.py +33 -0
- clearskies/decorators.pyi +10 -0
- clearskies/di/__init__.py +11 -7
- clearskies/di/additional_config.py +115 -4
- clearskies/di/additional_config_auto_import.py +12 -0
- clearskies/di/di.py +714 -125
- clearskies/di/inject/__init__.py +23 -0
- clearskies/di/inject/akeyless_sdk.py +16 -0
- clearskies/di/inject/by_class.py +24 -0
- clearskies/di/inject/by_name.py +22 -0
- clearskies/di/inject/di.py +16 -0
- clearskies/di/inject/environment.py +15 -0
- clearskies/di/inject/input_output.py +19 -0
- clearskies/di/inject/now.py +16 -0
- clearskies/di/inject/requests.py +16 -0
- clearskies/di/inject/secrets.py +15 -0
- clearskies/di/inject/utcnow.py +16 -0
- clearskies/di/inject/uuid.py +16 -0
- clearskies/di/injectable.py +32 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/end.py +219 -0
- clearskies/endpoint.py +1303 -0
- clearskies/endpoint_group.py +333 -0
- clearskies/endpoints/__init__.py +25 -0
- clearskies/endpoints/advanced_search.py +519 -0
- clearskies/endpoints/callable.py +382 -0
- clearskies/endpoints/create.py +201 -0
- clearskies/endpoints/delete.py +133 -0
- clearskies/endpoints/get.py +267 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +567 -0
- clearskies/endpoints/restful_api.py +417 -0
- clearskies/endpoints/schema.py +185 -0
- clearskies/endpoints/simple_search.py +279 -0
- clearskies/endpoints/update.py +188 -0
- clearskies/environment.py +7 -3
- clearskies/exceptions/__init__.py +19 -0
- clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/functional/__init__.py +2 -2
- clearskies/functional/json.py +47 -0
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +19 -11
- clearskies/functional/validations.py +61 -9
- clearskies/input_outputs/__init__.py +9 -7
- clearskies/input_outputs/cli.py +135 -160
- clearskies/input_outputs/exceptions/__init__.py +6 -1
- clearskies/input_outputs/headers.py +54 -0
- clearskies/input_outputs/input_output.py +77 -123
- clearskies/input_outputs/programmatic.py +62 -0
- clearskies/input_outputs/wsgi.py +36 -48
- clearskies/model.py +1874 -193
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +228 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +193 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +4 -31
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
- clearskies/secrets/akeyless.py +421 -155
- clearskies/secrets/exceptions/__init__.py +7 -1
- clearskies/secrets/exceptions/not_found_error.py +2 -0
- clearskies/secrets/exceptions/permissions_error.py +2 -0
- clearskies/secrets/secrets.py +12 -11
- clearskies/security_header.py +17 -0
- clearskies/security_headers/__init__.py +8 -8
- clearskies/security_headers/cache_control.py +47 -109
- clearskies/security_headers/cors.py +38 -92
- clearskies/security_headers/csp.py +76 -150
- clearskies/security_headers/hsts.py +14 -15
- clearskies/typing.py +11 -0
- clearskies/validator.py +36 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +61 -0
- clearskies/validators/before_column.py +15 -0
- clearskies/validators/in_the_future.py +29 -0
- clearskies/validators/in_the_future_at_least.py +13 -0
- clearskies/validators/in_the_future_at_most.py +12 -0
- clearskies/validators/in_the_past.py +29 -0
- clearskies/validators/in_the_past_at_least.py +12 -0
- clearskies/validators/in_the_past_at_most.py +12 -0
- clearskies/validators/maximum_length.py +25 -0
- clearskies/validators/maximum_value.py +28 -0
- clearskies/validators/minimum_length.py +25 -0
- clearskies/validators/minimum_value.py +28 -0
- clearskies/{input_requirements → validators}/required.py +18 -9
- clearskies/validators/timedelta.py +58 -0
- clearskies/validators/unique.py +28 -0
- clear_skies-1.22.10.dist-info/METADATA +0 -47
- clear_skies-1.22.10.dist-info/RECORD +0 -213
- clearskies/application.py +0 -29
- clearskies/authentication/auth0_jwks.py +0 -118
- clearskies/authentication/auth_exception.py +0 -2
- clearskies/authentication/jwks_jwcrypto.py +0 -51
- clearskies/backends/api_get_only_backend.py +0 -48
- clearskies/backends/example_backend.py +0 -43
- clearskies/backends/file_backend.py +0 -48
- clearskies/backends/json_backend.py +0 -7
- clearskies/backends/restful_api_advanced_search_backend.py +0 -103
- clearskies/binding_config.py +0 -16
- clearskies/column_types/__init__.py +0 -203
- clearskies/column_types/audit.py +0 -249
- clearskies/column_types/belongs_to.py +0 -271
- clearskies/column_types/boolean.py +0 -60
- clearskies/column_types/category_tree.py +0 -304
- clearskies/column_types/column.py +0 -373
- clearskies/column_types/created.py +0 -26
- clearskies/column_types/created_by_authorization_data.py +0 -26
- clearskies/column_types/created_by_header.py +0 -24
- clearskies/column_types/created_by_ip.py +0 -17
- clearskies/column_types/created_by_routing_data.py +0 -25
- clearskies/column_types/created_by_user_agent.py +0 -17
- clearskies/column_types/created_micro.py +0 -26
- clearskies/column_types/datetime.py +0 -109
- clearskies/column_types/datetime_micro.py +0 -13
- clearskies/column_types/email.py +0 -18
- clearskies/column_types/float.py +0 -43
- clearskies/column_types/has_many.py +0 -179
- clearskies/column_types/has_one.py +0 -58
- clearskies/column_types/integer.py +0 -41
- clearskies/column_types/json.py +0 -25
- clearskies/column_types/many_to_many.py +0 -278
- clearskies/column_types/many_to_many_with_data.py +0 -162
- clearskies/column_types/phone.py +0 -48
- clearskies/column_types/select.py +0 -11
- clearskies/column_types/string.py +0 -24
- clearskies/column_types/timestamp.py +0 -73
- clearskies/column_types/updated.py +0 -26
- clearskies/column_types/updated_micro.py +0 -26
- clearskies/column_types/uuid.py +0 -25
- clearskies/columns.py +0 -123
- clearskies/condition_parser.py +0 -172
- clearskies/contexts/build_context.py +0 -54
- clearskies/contexts/convert_to_application.py +0 -190
- clearskies/contexts/extract_handler.py +0 -37
- clearskies/contexts/test.py +0 -94
- clearskies/decorators/__init__.py +0 -39
- clearskies/decorators/auth0_jwks.py +0 -22
- clearskies/decorators/authorization.py +0 -10
- clearskies/decorators/binding_classes.py +0 -9
- clearskies/decorators/binding_modules.py +0 -9
- clearskies/decorators/bindings.py +0 -9
- clearskies/decorators/create.py +0 -10
- clearskies/decorators/delete.py +0 -10
- clearskies/decorators/docs.py +0 -14
- clearskies/decorators/get.py +0 -10
- clearskies/decorators/jwks.py +0 -26
- clearskies/decorators/merge.py +0 -124
- clearskies/decorators/patch.py +0 -10
- clearskies/decorators/post.py +0 -10
- clearskies/decorators/public.py +0 -11
- clearskies/decorators/response_headers.py +0 -10
- clearskies/decorators/return_raw_response.py +0 -9
- clearskies/decorators/schema.py +0 -10
- clearskies/decorators/secret_bearer.py +0 -24
- clearskies/decorators/security_headers.py +0 -10
- clearskies/di/standard_dependencies.py +0 -151
- clearskies/di/test_module/__init__.py +0 -6
- clearskies/di/test_module/another_module/__init__.py +0 -2
- clearskies/di/test_module/module_class.py +0 -5
- clearskies/handlers/__init__.py +0 -41
- clearskies/handlers/advanced_search.py +0 -271
- clearskies/handlers/base.py +0 -479
- clearskies/handlers/callable.py +0 -191
- clearskies/handlers/create.py +0 -35
- clearskies/handlers/crud_by_method.py +0 -18
- clearskies/handlers/database_connector.py +0 -32
- clearskies/handlers/delete.py +0 -61
- clearskies/handlers/exceptions/__init__.py +0 -5
- clearskies/handlers/exceptions/not_found.py +0 -3
- clearskies/handlers/get.py +0 -156
- clearskies/handlers/health_check.py +0 -59
- clearskies/handlers/input_processing.py +0 -79
- clearskies/handlers/list.py +0 -530
- clearskies/handlers/mygrations.py +0 -82
- clearskies/handlers/request_method_routing.py +0 -47
- clearskies/handlers/restful_api.py +0 -218
- clearskies/handlers/routing.py +0 -62
- clearskies/handlers/schema_helper.py +0 -128
- clearskies/handlers/simple_routing.py +0 -206
- clearskies/handlers/simple_routing_route.py +0 -192
- clearskies/handlers/simple_search.py +0 -136
- clearskies/handlers/update.py +0 -96
- clearskies/handlers/write.py +0 -193
- clearskies/input_requirements/__init__.py +0 -78
- clearskies/input_requirements/after.py +0 -36
- clearskies/input_requirements/before.py +0 -36
- clearskies/input_requirements/in_the_future_at_least.py +0 -19
- clearskies/input_requirements/in_the_future_at_most.py +0 -19
- clearskies/input_requirements/in_the_past_at_least.py +0 -19
- clearskies/input_requirements/in_the_past_at_most.py +0 -19
- clearskies/input_requirements/maximum_length.py +0 -19
- clearskies/input_requirements/maximum_value.py +0 -19
- clearskies/input_requirements/minimum_length.py +0 -22
- clearskies/input_requirements/minimum_value.py +0 -19
- clearskies/input_requirements/requirement.py +0 -25
- clearskies/input_requirements/time_delta.py +0 -38
- clearskies/input_requirements/unique.py +0 -18
- clearskies/mocks/__init__.py +0 -7
- clearskies/mocks/input_output.py +0 -124
- clearskies/mocks/models.py +0 -142
- clearskies/models.py +0 -350
- clearskies/security_headers/base.py +0 -12
- clearskies/tests/simple_api/models/__init__.py +0 -2
- clearskies/tests/simple_api/models/status.py +0 -23
- clearskies/tests/simple_api/models/user.py +0 -21
- clearskies/tests/simple_api/users_api.py +0 -64
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info/licenses}/LICENSE +0 -0
- /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
- /clearskies/{secrets/exceptions → exceptions}/not_found.py +0 -0
- /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
- /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
clearskies/column_types/float.py
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
from .column import Column
|
|
2
|
-
from ..autodoc.schema import Number as AutoDocNumber
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Float(Column):
|
|
6
|
-
_auto_doc_class = AutoDocNumber
|
|
7
|
-
|
|
8
|
-
def __init__(self, di):
|
|
9
|
-
super().__init__(di)
|
|
10
|
-
|
|
11
|
-
def from_backend(self, value):
|
|
12
|
-
return float(value)
|
|
13
|
-
|
|
14
|
-
def to_backend(self, data):
|
|
15
|
-
if self.name not in data or data[self.name] is None:
|
|
16
|
-
return data
|
|
17
|
-
|
|
18
|
-
return {
|
|
19
|
-
**data,
|
|
20
|
-
self.name: float(data[self.name]),
|
|
21
|
-
}
|
|
22
|
-
|
|
23
|
-
def check_input(self, model, data):
|
|
24
|
-
if not self.name in data:
|
|
25
|
-
return ""
|
|
26
|
-
if isinstance(data[self.name], int) or isinstance(data[self.name], float) or data[self.name] == None:
|
|
27
|
-
return ""
|
|
28
|
-
return f"Invalid input: {self.name} must be an integer or float"
|
|
29
|
-
|
|
30
|
-
def build_condition(self, value, operator=None, column_prefix=""):
|
|
31
|
-
if not operator:
|
|
32
|
-
operator = "="
|
|
33
|
-
return f"{column_prefix}{self.name}{operator}{value}"
|
|
34
|
-
|
|
35
|
-
def is_allowed_operator(self, operator, relationship_reference=None):
|
|
36
|
-
return operator in ["=", "<", ">", "<=", ">="]
|
|
37
|
-
|
|
38
|
-
def input_error_for_value(self, value, operator=None):
|
|
39
|
-
return (
|
|
40
|
-
"value should be an integer or float"
|
|
41
|
-
if (type(value) != int and type(value) != float and value is not None)
|
|
42
|
-
else ""
|
|
43
|
-
)
|
|
@@ -1,179 +0,0 @@
|
|
|
1
|
-
from .column import Column
|
|
2
|
-
import re
|
|
3
|
-
from collections import OrderedDict
|
|
4
|
-
from ..autodoc.schema import Array as AutoDocArray
|
|
5
|
-
from ..autodoc.schema import Object as AutoDocObject
|
|
6
|
-
from ..autodoc.schema import String as AutoDocString
|
|
7
|
-
from ..functional import validations
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
class HasMany(Column):
|
|
11
|
-
"""
|
|
12
|
-
Controls a has-many relationship.
|
|
13
|
-
|
|
14
|
-
This is a readonly column. When used in a model context it will return an iterable with the related child records.
|
|
15
|
-
When used in an API context, it will convert the child records into a list of objects.
|
|
16
|
-
|
|
17
|
-
It assumes that the foreign id in the child table is `[parent_model_class_name]_id` in all lower case.
|
|
18
|
-
e.g., if the parent model class is named Status, then it assumes an id in the child class called `status_id`.
|
|
19
|
-
"""
|
|
20
|
-
|
|
21
|
-
required_configs = [
|
|
22
|
-
"child_models_class",
|
|
23
|
-
]
|
|
24
|
-
|
|
25
|
-
my_configs = [
|
|
26
|
-
"foreign_column_name",
|
|
27
|
-
"child_columns",
|
|
28
|
-
"is_readable",
|
|
29
|
-
"readable_child_columns",
|
|
30
|
-
"parent_id_column_name",
|
|
31
|
-
"where",
|
|
32
|
-
]
|
|
33
|
-
|
|
34
|
-
def __init__(self, di):
|
|
35
|
-
super().__init__(di)
|
|
36
|
-
|
|
37
|
-
@property
|
|
38
|
-
def is_writeable(self):
|
|
39
|
-
return False
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
def is_readable(self):
|
|
43
|
-
is_readable = self.config("is_readable", True)
|
|
44
|
-
# default is_readable to False
|
|
45
|
-
return True if (is_readable and is_readable is not None) else False
|
|
46
|
-
|
|
47
|
-
def configure(self, name, configuration, model_class):
|
|
48
|
-
if "child_models_class" not in configuration:
|
|
49
|
-
raise KeyError(
|
|
50
|
-
f"Missing required configuration 'child_models_class' for column '{name}' in model class "
|
|
51
|
-
+ f"'{model_class.__name__}'"
|
|
52
|
-
)
|
|
53
|
-
self.validate_models_class(configuration["child_models_class"])
|
|
54
|
-
if not configuration.get("parent_id_column_name"):
|
|
55
|
-
configuration["parent_id_column_name"] = model_class.id_column_name
|
|
56
|
-
|
|
57
|
-
# if readable_child_columns is set then load up the child models/columns now, because we'll need it in the
|
|
58
|
-
# _check_configuration step, but we don't want to load it there because we can't save it back into the config
|
|
59
|
-
if "foreign_column_name" not in configuration:
|
|
60
|
-
configuration["foreign_column_name"] = (
|
|
61
|
-
re.sub(r"(?<!^)(?=[A-Z])", "_", model_class.__name__.replace("_", "")).lower() + "_id"
|
|
62
|
-
)
|
|
63
|
-
|
|
64
|
-
# continue normally now...
|
|
65
|
-
super().configure(name, configuration, model_class)
|
|
66
|
-
|
|
67
|
-
def _check_configuration(self, configuration):
|
|
68
|
-
super()._check_configuration(configuration)
|
|
69
|
-
error_prefix = f"Configuration error for '{self.name}' in '{self.model_class.__name__}':"
|
|
70
|
-
|
|
71
|
-
if configuration.get("is_readable"):
|
|
72
|
-
child_columns = self.di.build(configuration["child_models_class"], cache=True).raw_columns_configuration()
|
|
73
|
-
if not "readable_child_columns" in configuration:
|
|
74
|
-
raise ValueError(f"{error_prefix} must provide 'readable_child_columns' if is_readable is set")
|
|
75
|
-
readable_child_columns = configuration["readable_child_columns"]
|
|
76
|
-
if not hasattr(readable_child_columns, "__iter__"):
|
|
77
|
-
raise ValueError(
|
|
78
|
-
f"{error_prefix} 'readable_child_columns' should be an iterable "
|
|
79
|
-
+ "with the list of child columns to output."
|
|
80
|
-
)
|
|
81
|
-
if isinstance(readable_child_columns, str):
|
|
82
|
-
raise ValueError(
|
|
83
|
-
f"{error_prefix} 'readable_child_columns' should be an iterable "
|
|
84
|
-
+ "with the list of child columns to output."
|
|
85
|
-
)
|
|
86
|
-
for column_name in readable_child_columns:
|
|
87
|
-
if column_name not in child_columns:
|
|
88
|
-
raise ValueError(
|
|
89
|
-
f"{error_prefix} 'readable_child_columns' references column named '{column_name}' but this"
|
|
90
|
-
+ "column does not exist in the model class."
|
|
91
|
-
)
|
|
92
|
-
|
|
93
|
-
wheres = configuration.get("where")
|
|
94
|
-
if wheres:
|
|
95
|
-
if not isinstance(wheres, list):
|
|
96
|
-
raise ValueError(
|
|
97
|
-
f"{error_prefix} 'where' must be a list of where conditions or callables that return where conditions"
|
|
98
|
-
)
|
|
99
|
-
for index, where in enumerate(wheres):
|
|
100
|
-
if callable(where) or isinstance(where, str):
|
|
101
|
-
continue
|
|
102
|
-
raise ValueError(
|
|
103
|
-
f"{error_prefix} 'where' must be a list of where conditions or callables that return where conditions, but the item in entry #${index+1} was neither a string nor a callable"
|
|
104
|
-
)
|
|
105
|
-
|
|
106
|
-
def _finalize_configuration(self, configuration):
|
|
107
|
-
return {
|
|
108
|
-
**super()._finalize_configuration(configuration),
|
|
109
|
-
**{
|
|
110
|
-
"where": configuration.get("where", []),
|
|
111
|
-
},
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
def get_child_columns(self):
|
|
115
|
-
if "child_columns" not in self.configuration:
|
|
116
|
-
self.configuration["child_columns"] = self.child_models.columns()
|
|
117
|
-
return self.configuration["child_columns"]
|
|
118
|
-
|
|
119
|
-
def can_provide(self, column_name):
|
|
120
|
-
return column_name == self.name
|
|
121
|
-
|
|
122
|
-
def provide(self, data, column_name):
|
|
123
|
-
foreign_column_name = self.config("foreign_column_name")
|
|
124
|
-
id_column_name = self.config("parent_id_column_name")
|
|
125
|
-
children = self.child_models.where(f"{foreign_column_name}={data[id_column_name]}")
|
|
126
|
-
|
|
127
|
-
wheres = self.config("where", silent=True)
|
|
128
|
-
if not wheres:
|
|
129
|
-
return children
|
|
130
|
-
|
|
131
|
-
for index, where in enumerate(wheres):
|
|
132
|
-
if callable(where):
|
|
133
|
-
children = self.di.call_function(where, model=children, children=children, parent_data=data)
|
|
134
|
-
if not validations.is_model(children):
|
|
135
|
-
raise ValueError(
|
|
136
|
-
f"Configuration error for column '{self.name}' in model '{self.model_class.__name__}': when 'where' is a callable, it must return a models class, but when the callable in where entry #{index+1} was called, it did not return the models class"
|
|
137
|
-
)
|
|
138
|
-
else:
|
|
139
|
-
children = children.where(where)
|
|
140
|
-
return children
|
|
141
|
-
|
|
142
|
-
def to_json(self, model):
|
|
143
|
-
children = []
|
|
144
|
-
columns = self.get_child_columns()
|
|
145
|
-
for child in model.__getattr__(self.name):
|
|
146
|
-
json = OrderedDict()
|
|
147
|
-
child_id_column_name = child.id_column_name
|
|
148
|
-
json = {
|
|
149
|
-
**json,
|
|
150
|
-
**columns[child_id_column_name].to_json(child),
|
|
151
|
-
}
|
|
152
|
-
for column_name in self.config("readable_child_columns"):
|
|
153
|
-
json = {
|
|
154
|
-
**json,
|
|
155
|
-
**columns[column_name].to_json(child),
|
|
156
|
-
}
|
|
157
|
-
children.append(json)
|
|
158
|
-
return {self.name: children}
|
|
159
|
-
|
|
160
|
-
@property
|
|
161
|
-
def child_models(self):
|
|
162
|
-
return self.di.build(self.config("child_models_class"), cache=True)
|
|
163
|
-
|
|
164
|
-
def documentation(self, name=None, example=None, value=None):
|
|
165
|
-
columns = self.get_child_columns()
|
|
166
|
-
child_id_column_name = self.child_models.get_id_column_name()
|
|
167
|
-
child_properties = [columns[child_id_column_name].documentation()]
|
|
168
|
-
|
|
169
|
-
for column_name in self.config("readable_child_columns"):
|
|
170
|
-
child_docs = columns[column_name].documentation()
|
|
171
|
-
if type(child_docs) != list:
|
|
172
|
-
child_docs = [child_docs]
|
|
173
|
-
child_properties.extend(child_docs)
|
|
174
|
-
|
|
175
|
-
child_object = AutoDocObject(
|
|
176
|
-
self.camel_to_nice(self.child_models.model_class().__name__),
|
|
177
|
-
child_properties,
|
|
178
|
-
)
|
|
179
|
-
return AutoDocArray(name if name is not None else self.name, child_object, value=value)
|
|
@@ -1,58 +0,0 @@
|
|
|
1
|
-
from .has_many import HasMany
|
|
2
|
-
import re
|
|
3
|
-
from collections import OrderedDict
|
|
4
|
-
from ..autodoc.schema import Array as AutoDocArray
|
|
5
|
-
from ..autodoc.schema import Object as AutoDocObject
|
|
6
|
-
from ..autodoc.schema import String as AutoDocString
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class HasOne(HasMany):
|
|
10
|
-
"""
|
|
11
|
-
Controls a has-one relationship.
|
|
12
|
-
|
|
13
|
-
This is a readonly column. When used in a model context it will return the related record.
|
|
14
|
-
When used in an API context, it will convert the child record into an object.
|
|
15
|
-
|
|
16
|
-
It assumes that the foreign id in the child table is `[parent_model_class_name]_id` in all lower case.
|
|
17
|
-
e.g., if the parent model class is named Status, then it assumes an id in the child class called `status_id`.
|
|
18
|
-
"""
|
|
19
|
-
|
|
20
|
-
def can_provide(self, column_name):
|
|
21
|
-
return column_name == self.name
|
|
22
|
-
|
|
23
|
-
def provide(self, data, column_name):
|
|
24
|
-
foreign_column_name = self.config("foreign_column_name")
|
|
25
|
-
id_column_name = self.config("parent_id_column_name")
|
|
26
|
-
return self.child_models.find(f"{foreign_column_name}={data[id_column_name]}")
|
|
27
|
-
|
|
28
|
-
def to_json(self, model):
|
|
29
|
-
json = OrderedDict()
|
|
30
|
-
columns = self.get_child_columns()
|
|
31
|
-
child = model.__getattr__(self.name)
|
|
32
|
-
child_id_column_name = child.id_column_name
|
|
33
|
-
json = {
|
|
34
|
-
**json,
|
|
35
|
-
**columns[child_id_column_name].to_json(child),
|
|
36
|
-
}
|
|
37
|
-
for column_name in self.config("readable_child_columns"):
|
|
38
|
-
json = {
|
|
39
|
-
**json,
|
|
40
|
-
**columns[column_name].to_json(child),
|
|
41
|
-
}
|
|
42
|
-
return {self.name: json}
|
|
43
|
-
|
|
44
|
-
def documentation(self, name=None, example=None, value=None):
|
|
45
|
-
columns = self.get_child_columns()
|
|
46
|
-
child_id_column_name = self.child_models.get_id_column_name()
|
|
47
|
-
child_properties = [columns[child_id_column_name].documentation()]
|
|
48
|
-
|
|
49
|
-
for column_name in self.config("readable_child_columns"):
|
|
50
|
-
child_docs = columns[column_name].documentation()
|
|
51
|
-
if type(child_docs) != list:
|
|
52
|
-
child_docs = [child_docs]
|
|
53
|
-
child_properties.extend(child_docs)
|
|
54
|
-
|
|
55
|
-
return AutoDocObject(
|
|
56
|
-
self.camel_to_nice(self.child_models.model_class().__name__),
|
|
57
|
-
child_properties,
|
|
58
|
-
)
|
|
@@ -1,41 +0,0 @@
|
|
|
1
|
-
from .column import Column
|
|
2
|
-
from ..autodoc.schema import Integer as AutoDocInteger
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Integer(Column):
|
|
6
|
-
_auto_doc_class = AutoDocInteger
|
|
7
|
-
|
|
8
|
-
def __init__(self, di):
|
|
9
|
-
super().__init__(di)
|
|
10
|
-
|
|
11
|
-
def to_backend(self, data):
|
|
12
|
-
if self.name not in data or data[self.name] is None:
|
|
13
|
-
return data
|
|
14
|
-
|
|
15
|
-
return {
|
|
16
|
-
**data,
|
|
17
|
-
self.name: int(data[self.name]),
|
|
18
|
-
}
|
|
19
|
-
|
|
20
|
-
def from_backend(self, value):
|
|
21
|
-
return int(value)
|
|
22
|
-
|
|
23
|
-
def input_error_for_value(self, value, operator=None):
|
|
24
|
-
if operator == "in":
|
|
25
|
-
if type(value) != list:
|
|
26
|
-
return f'{self.name} must be an integer when searching with the "IN" operator'
|
|
27
|
-
for val in value:
|
|
28
|
-
if type(val) != int:
|
|
29
|
-
return f"All items in {self.name} must be integers"
|
|
30
|
-
return ""
|
|
31
|
-
return f"{self.name} must be an integer" if type(value) != int else ""
|
|
32
|
-
|
|
33
|
-
def build_condition(self, value, operator=None, column_prefix=""):
|
|
34
|
-
if operator == "in":
|
|
35
|
-
return f"{column_prefix}{self.name} IN (" + ",".join([str(val) for val in value]) + ")"
|
|
36
|
-
if not operator:
|
|
37
|
-
operator = "="
|
|
38
|
-
return f"{column_prefix}{self.name}{operator}{value}"
|
|
39
|
-
|
|
40
|
-
def is_allowed_operator(self, operator, relationship_reference=None):
|
|
41
|
-
return operator in ["=", "<", ">", "<=", ">=", "in"]
|
clearskies/column_types/json.py
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
import json
|
|
2
|
-
from .column import Column
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class JSON(Column):
|
|
6
|
-
def __init__(self, di):
|
|
7
|
-
super().__init__(di)
|
|
8
|
-
|
|
9
|
-
def from_backend(self, value):
|
|
10
|
-
if type(value) == list or type(value) == dict:
|
|
11
|
-
return value
|
|
12
|
-
if not value:
|
|
13
|
-
return None
|
|
14
|
-
try:
|
|
15
|
-
return json.loads(value)
|
|
16
|
-
except json.JSONDecodeError:
|
|
17
|
-
return None
|
|
18
|
-
|
|
19
|
-
def to_backend(self, data):
|
|
20
|
-
if self.name in data:
|
|
21
|
-
data[self.name] = json.dumps(data[self.name]) if data[self.name] else ""
|
|
22
|
-
return data
|
|
23
|
-
|
|
24
|
-
def to_json(self, model):
|
|
25
|
-
return {self.name: model.get(self.name, silent=True)}
|
|
@@ -1,278 +0,0 @@
|
|
|
1
|
-
from .string import String
|
|
2
|
-
import re
|
|
3
|
-
from ..autodoc.schema import Array as AutoDocArray
|
|
4
|
-
from ..autodoc.schema import Object as AutoDocObject
|
|
5
|
-
from ..autodoc.schema import String as AutoDocString
|
|
6
|
-
from collections import OrderedDict
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class ManyToMany(String):
|
|
10
|
-
"""
|
|
11
|
-
Controls a many-to-many relationship.
|
|
12
|
-
|
|
13
|
-
This column connects to models via a many-to-many relationship, meaning that a record in either table can
|
|
14
|
-
be associated with multiple records in the other table. Image you had two models: users and teams, where
|
|
15
|
-
a user can be on more than one team. To keep track of the mapping, a "pivot" table is required which tracks
|
|
16
|
-
relationships. In the case of users/teams, you might imagine a table called "users_teams" which has 3 columns:
|
|
17
|
-
|
|
18
|
-
- id
|
|
19
|
-
- user_id
|
|
20
|
-
- team_id
|
|
21
|
-
|
|
22
|
-
You would then create a many-to-many relationship in both your users model and your teams model that would
|
|
23
|
-
look something like:
|
|
24
|
-
|
|
25
|
-
```
|
|
26
|
-
class User:
|
|
27
|
-
def columns_configuration(self):
|
|
28
|
-
return OrderedDict([
|
|
29
|
-
clearskies.column_types.has_many('teams', related_models_class=Teams, pivot_models_class=UsersTeams),
|
|
30
|
-
')
|
|
31
|
-
|
|
32
|
-
class Team:
|
|
33
|
-
def columns_configuration(self):
|
|
34
|
-
return OrderedDict([
|
|
35
|
-
clearskies.column_types.has_many('users', related_models_class=Users, pivot_models_class=UsersTeams),
|
|
36
|
-
')
|
|
37
|
-
```
|
|
38
|
-
|
|
39
|
-
Note that `related_models_class` and pivot_models_class receive the model*s* class, not the _model_ class.
|
|
40
|
-
|
|
41
|
-
You can attach records to eachother by saving a list of ids via the column name, i.e.:
|
|
42
|
-
|
|
43
|
-
```
|
|
44
|
-
user.save({'teams': [1, 2, 3]})
|
|
45
|
-
team.save({'users': [4, 5, 6]})
|
|
46
|
-
```
|
|
47
|
-
|
|
48
|
-
The many_to_many column will let you easily pull out the related models:
|
|
49
|
-
|
|
50
|
-
```
|
|
51
|
-
print(user.teams)
|
|
52
|
-
# prints [<__main__.Team object>, <__main__.Team object>, <__main__.Team object>]
|
|
53
|
-
```
|
|
54
|
-
|
|
55
|
-
as well as their ids:
|
|
56
|
-
|
|
57
|
-
```
|
|
58
|
-
print(user.teams_ids)
|
|
59
|
-
# prints [1, 2, 3]
|
|
60
|
-
```
|
|
61
|
-
"""
|
|
62
|
-
|
|
63
|
-
required_configs = [
|
|
64
|
-
"pivot_models_class",
|
|
65
|
-
"related_models_class",
|
|
66
|
-
]
|
|
67
|
-
|
|
68
|
-
my_configs = [
|
|
69
|
-
"foreign_column_name_in_pivot",
|
|
70
|
-
"own_column_name_in_pivot",
|
|
71
|
-
"pivot_table",
|
|
72
|
-
"readable_related_columns",
|
|
73
|
-
"is_readable",
|
|
74
|
-
]
|
|
75
|
-
|
|
76
|
-
def __init__(self, di):
|
|
77
|
-
super().__init__(di)
|
|
78
|
-
|
|
79
|
-
@property
|
|
80
|
-
def is_readable(self):
|
|
81
|
-
is_readable = self.config("is_readable", True)
|
|
82
|
-
# default is_readable to False
|
|
83
|
-
return True if (is_readable and is_readable is not None) else False
|
|
84
|
-
|
|
85
|
-
def _check_configuration(self, configuration):
|
|
86
|
-
super()._check_configuration(configuration)
|
|
87
|
-
self.validate_models_class(configuration["pivot_models_class"])
|
|
88
|
-
self.validate_models_class(configuration["related_models_class"])
|
|
89
|
-
if self.name[-3:] == "_id" or self.name[-4:] == "_ids":
|
|
90
|
-
raise ValueError(
|
|
91
|
-
f"Invalid name for column '{self.name}' in '{self.model_class.__name__}' - "
|
|
92
|
-
+ "ManyToMany column should not end in '_id' or '_ids'"
|
|
93
|
-
)
|
|
94
|
-
|
|
95
|
-
if configuration.get("is_readable"):
|
|
96
|
-
related_columns = self.di.build(
|
|
97
|
-
configuration["related_models_class"], cache=True
|
|
98
|
-
).raw_columns_configuration()
|
|
99
|
-
error_prefix = f"Configuration error for '{self.name}' in '{self.model_class.__name__}':"
|
|
100
|
-
if not "readable_related_columns" in configuration:
|
|
101
|
-
raise ValueError(f"{error_prefix} must provide 'readable_related_columns' if is_readable is set")
|
|
102
|
-
readable_related_columns = configuration["readable_related_columns"]
|
|
103
|
-
if not hasattr(readable_related_columns, "__iter__"):
|
|
104
|
-
raise ValueError(
|
|
105
|
-
f"{error_prefix} 'readable_related_columns' should be an iterable "
|
|
106
|
-
+ "with the list of child columns to output."
|
|
107
|
-
)
|
|
108
|
-
if isinstance(readable_related_columns, str):
|
|
109
|
-
raise ValueError(
|
|
110
|
-
f"{error_prefix} 'readable_related_columns' should be an iterable "
|
|
111
|
-
+ "with the list of child columns to output."
|
|
112
|
-
)
|
|
113
|
-
for column_name in readable_related_columns:
|
|
114
|
-
if column_name not in related_columns:
|
|
115
|
-
raise ValueError(
|
|
116
|
-
f"{error_prefix} 'readable_related_columns' references column named '{column_name}' but this"
|
|
117
|
-
+ "column does not exist in the model class."
|
|
118
|
-
)
|
|
119
|
-
|
|
120
|
-
def _finalize_configuration(self, configuration):
|
|
121
|
-
pivot_models = self.di.build(configuration["pivot_models_class"], cache=True)
|
|
122
|
-
related_models = self.di.build(configuration["related_models_class"], cache=True)
|
|
123
|
-
|
|
124
|
-
if not configuration.get("foreign_column_name_in_pivot"):
|
|
125
|
-
model_class = related_models.model_class()
|
|
126
|
-
foreign_column_name = re.sub(r"(?<!^)(?=[A-Z])", "_", model_class.__name__.replace("_", "")).lower() + "_id"
|
|
127
|
-
else:
|
|
128
|
-
foreign_column_name = configuration["foreign_column_name_in_pivot"]
|
|
129
|
-
|
|
130
|
-
if not configuration.get("own_column_name_in_pivot"):
|
|
131
|
-
own_column_name = (
|
|
132
|
-
re.sub(r"(?<!^)(?=[A-Z])", "_", self.model_class.__name__.replace("_", "")).lower() + "_id"
|
|
133
|
-
)
|
|
134
|
-
else:
|
|
135
|
-
own_column_name = configuration["own_column_name_in_pivot"]
|
|
136
|
-
|
|
137
|
-
return {
|
|
138
|
-
**super()._finalize_configuration(configuration),
|
|
139
|
-
**{
|
|
140
|
-
"foreign_column_name_in_pivot": foreign_column_name,
|
|
141
|
-
"own_column_name_in_pivot": own_column_name,
|
|
142
|
-
"pivot_table": pivot_models.get_table_name(),
|
|
143
|
-
"own_id_column_name": self.model_class.id_column_name,
|
|
144
|
-
"related_id_column_name": related_models.get_id_column_name(),
|
|
145
|
-
},
|
|
146
|
-
}
|
|
147
|
-
|
|
148
|
-
def input_error_for_value(self, value, operator=None):
|
|
149
|
-
if type(value) != list:
|
|
150
|
-
return f"{self.name} should be a list of ids"
|
|
151
|
-
related_models = self.related_models
|
|
152
|
-
related_id_column_name = self.config("related_id_column_name")
|
|
153
|
-
for id_to_check in value:
|
|
154
|
-
if type(id_to_check) != str:
|
|
155
|
-
return f"Invalid selection for {self.name}: all values must be strings"
|
|
156
|
-
if not len(related_models.where(f"{related_id_column_name}={id_to_check}")):
|
|
157
|
-
return f"Invalid selection for {self.name}: record {id_to_check} does not exist"
|
|
158
|
-
return ""
|
|
159
|
-
|
|
160
|
-
def can_provide(self, column_name):
|
|
161
|
-
return column_name == self.name or column_name == f"{self.name}_ids"
|
|
162
|
-
|
|
163
|
-
def provide(self, data, column_name):
|
|
164
|
-
foreign_column_name_in_pivot = self.config("foreign_column_name_in_pivot")
|
|
165
|
-
own_column_name_in_pivot = self.config("own_column_name_in_pivot")
|
|
166
|
-
own_id_column_name = self.config("own_id_column_name")
|
|
167
|
-
pivot_table = self.config("pivot_table")
|
|
168
|
-
related_id_column_name = self.config("related_id_column_name")
|
|
169
|
-
models = self.related_models
|
|
170
|
-
join = f"JOIN {pivot_table} ON {pivot_table}.{foreign_column_name_in_pivot}={models.get_table_name()}.{related_id_column_name}"
|
|
171
|
-
related_models = models.join(join).where(f"{pivot_table}.{own_column_name_in_pivot}={data[own_id_column_name]}")
|
|
172
|
-
if column_name == self.name:
|
|
173
|
-
return [model for model in related_models]
|
|
174
|
-
return [model.__getattr__(related_id_column_name) for model in related_models]
|
|
175
|
-
|
|
176
|
-
def to_backend(self, data):
|
|
177
|
-
# we can't persist our mapping data to the database directly, so remove anything here
|
|
178
|
-
# and take care of things in post_save
|
|
179
|
-
if self.name in data:
|
|
180
|
-
del data[self.name]
|
|
181
|
-
return data
|
|
182
|
-
|
|
183
|
-
def post_save(self, data, model, id):
|
|
184
|
-
# if our incoming data is not in the data array or is None, then nothing has been set and we do not want
|
|
185
|
-
# to make any changes
|
|
186
|
-
if self.name not in data or data[self.name] is None:
|
|
187
|
-
return data
|
|
188
|
-
|
|
189
|
-
# figure out what ids need to be created or deleted from the pivot table.
|
|
190
|
-
if not model.exists:
|
|
191
|
-
old_ids = set()
|
|
192
|
-
else:
|
|
193
|
-
old_ids = set(getattr(model, f"{self.name}_ids"))
|
|
194
|
-
|
|
195
|
-
new_ids = set(data[self.name])
|
|
196
|
-
to_delete = old_ids - new_ids
|
|
197
|
-
to_create = new_ids - old_ids
|
|
198
|
-
if to_delete:
|
|
199
|
-
pivot_models = self.pivot_models
|
|
200
|
-
foreign_column_name = self.config("foreign_column_name_in_pivot")
|
|
201
|
-
for model_to_delete in pivot_models.where(
|
|
202
|
-
f"{foreign_column_name} IN (" + ",".join(map(str, to_delete)) + ")"
|
|
203
|
-
):
|
|
204
|
-
model_to_delete.delete()
|
|
205
|
-
if to_create:
|
|
206
|
-
pivot_models = self.pivot_models
|
|
207
|
-
foreign_column_name = self.config("foreign_column_name_in_pivot")
|
|
208
|
-
own_column_name = self.config("own_column_name_in_pivot")
|
|
209
|
-
for to_insert in new_ids - old_ids:
|
|
210
|
-
pivot_models.create(
|
|
211
|
-
{
|
|
212
|
-
foreign_column_name: to_insert,
|
|
213
|
-
own_column_name: id,
|
|
214
|
-
}
|
|
215
|
-
)
|
|
216
|
-
|
|
217
|
-
return data
|
|
218
|
-
|
|
219
|
-
@property
|
|
220
|
-
def pivot_models(self):
|
|
221
|
-
return self.di.build(self.config("pivot_models_class"), cache=True)
|
|
222
|
-
|
|
223
|
-
@property
|
|
224
|
-
def related_models(self):
|
|
225
|
-
return self.di.build(self.config("related_models_class"), cache=True)
|
|
226
|
-
|
|
227
|
-
@property
|
|
228
|
-
def related_columns(self):
|
|
229
|
-
return self.related_models.model_columns
|
|
230
|
-
|
|
231
|
-
def add_search(self, models, value, operator=None, relationship_reference=None):
|
|
232
|
-
foreign_column_name_in_pivot = self.config("foreign_column_name_in_pivot")
|
|
233
|
-
own_column_name_in_pivot = self.config("own_column_name_in_pivot")
|
|
234
|
-
own_id_column_name = self.config("own_id_column_name")
|
|
235
|
-
pivot_table = self.config("pivot_table")
|
|
236
|
-
my_table_name = self.model_class.table_name()
|
|
237
|
-
related_table_name = self.related_models.get_table_name()
|
|
238
|
-
join_pivot = (
|
|
239
|
-
f"JOIN {pivot_table} ON {pivot_table}.{own_column_name_in_pivot}={my_table_name}.{own_id_column_name}"
|
|
240
|
-
)
|
|
241
|
-
# no reason we can't support searching by both an id or a list of ids
|
|
242
|
-
values = value if type(value) == list else [value]
|
|
243
|
-
search = " IN (" + ", ".join([str(val) for val in value]) + ")"
|
|
244
|
-
return models.join(join_pivot).where(f"{pivot_table}.{foreign_column_name_in_pivot}{search}")
|
|
245
|
-
|
|
246
|
-
def to_json(self, model):
|
|
247
|
-
records = []
|
|
248
|
-
columns = self.related_columns
|
|
249
|
-
related_id_column_name = self.config("related_id_column_name")
|
|
250
|
-
for related in model.__getattr__(self.name):
|
|
251
|
-
json = OrderedDict()
|
|
252
|
-
if related_id_column_name not in self.config("readable_related_columns"):
|
|
253
|
-
json[related_id_column_name] = columns[related_id_column_name].to_json(related)
|
|
254
|
-
for column_name in self.config("readable_related_columns"):
|
|
255
|
-
column_data = columns[column_name].to_json(related)
|
|
256
|
-
if type(column_data) == dict:
|
|
257
|
-
json = {**json, **column_data}
|
|
258
|
-
else:
|
|
259
|
-
json[column_name] = column_data
|
|
260
|
-
records.append(json)
|
|
261
|
-
return {self.name: records}
|
|
262
|
-
|
|
263
|
-
def documentation(self, name=None, example=None, value=None):
|
|
264
|
-
columns = self.related_columns
|
|
265
|
-
related_id_column_name = self.config("related_id_column_name")
|
|
266
|
-
related_properties = [columns[related_id_column_name].documentation()]
|
|
267
|
-
|
|
268
|
-
for column_name in self.config("readable_related_columns"):
|
|
269
|
-
related_docs = columns[column_name].documentation()
|
|
270
|
-
if type(related_docs) != list:
|
|
271
|
-
related_docs = [related_docs]
|
|
272
|
-
related_properties.extend(child_docs)
|
|
273
|
-
|
|
274
|
-
related_object = AutoDocObject(
|
|
275
|
-
self.camel_to_nice(self.related_models.model_class().__name__),
|
|
276
|
-
related_properties,
|
|
277
|
-
)
|
|
278
|
-
return AutoDocArray(name if name is not None else self.name, related_object, value=value)
|