clear-skies 1.22.10__py3-none-any.whl → 2.0.23__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- clear_skies-2.0.23.dist-info/METADATA +76 -0
- clear_skies-2.0.23.dist-info/RECORD +265 -0
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info}/WHEEL +1 -1
- clearskies/__init__.py +37 -21
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +8 -39
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +14 -8
- clearskies/authentication/authorization_pass_through.py +14 -10
- clearskies/authentication/jwks.py +135 -58
- clearskies/authentication/public.py +3 -26
- clearskies/authentication/secret_bearer.py +515 -44
- clearskies/autodoc/formats/oai3_json/__init__.py +2 -2
- clearskies/autodoc/formats/oai3_json/oai3_json.py +11 -9
- clearskies/autodoc/formats/oai3_json/parameter.py +6 -3
- clearskies/autodoc/formats/oai3_json/request.py +7 -5
- clearskies/autodoc/formats/oai3_json/response.py +7 -4
- clearskies/autodoc/formats/oai3_json/schema/object.py +10 -1
- clearskies/autodoc/request/__init__.py +2 -0
- clearskies/autodoc/request/header.py +4 -6
- clearskies/autodoc/request/json_body.py +4 -6
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +16 -4
- clearskies/autodoc/request/url_parameter.py +4 -6
- clearskies/autodoc/request/url_path.py +4 -6
- clearskies/autodoc/schema/__init__.py +4 -2
- clearskies/autodoc/schema/array.py +5 -6
- clearskies/autodoc/schema/boolean.py +4 -10
- clearskies/autodoc/schema/date.py +0 -3
- clearskies/autodoc/schema/datetime.py +1 -4
- clearskies/autodoc/schema/double.py +0 -3
- clearskies/autodoc/schema/enum.py +4 -2
- clearskies/autodoc/schema/integer.py +4 -9
- clearskies/autodoc/schema/long.py +0 -3
- clearskies/autodoc/schema/number.py +4 -9
- clearskies/autodoc/schema/object.py +5 -7
- clearskies/autodoc/schema/password.py +0 -3
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +4 -10
- clearskies/backends/__init__.py +55 -20
- clearskies/backends/api_backend.py +1118 -280
- clearskies/backends/backend.py +54 -85
- clearskies/backends/cursor_backend.py +246 -191
- clearskies/backends/memory_backend.py +514 -208
- clearskies/backends/secrets_backend.py +68 -31
- clearskies/column.py +1221 -0
- clearskies/columns/__init__.py +71 -0
- clearskies/columns/audit.py +306 -0
- clearskies/columns/belongs_to_id.py +478 -0
- clearskies/columns/belongs_to_model.py +129 -0
- clearskies/columns/belongs_to_self.py +109 -0
- clearskies/columns/boolean.py +110 -0
- clearskies/columns/category_tree.py +273 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +126 -0
- clearskies/columns/category_tree_descendants.py +48 -0
- clearskies/columns/created.py +92 -0
- clearskies/columns/created_by_authorization_data.py +114 -0
- clearskies/columns/created_by_header.py +103 -0
- clearskies/columns/created_by_ip.py +90 -0
- clearskies/columns/created_by_routing_data.py +102 -0
- clearskies/columns/created_by_user_agent.py +89 -0
- clearskies/columns/date.py +232 -0
- clearskies/columns/datetime.py +284 -0
- clearskies/columns/email.py +78 -0
- clearskies/columns/float.py +149 -0
- clearskies/columns/has_many.py +529 -0
- clearskies/columns/has_many_self.py +62 -0
- clearskies/columns/has_one.py +21 -0
- clearskies/columns/integer.py +158 -0
- clearskies/columns/json.py +126 -0
- clearskies/columns/many_to_many_ids.py +335 -0
- clearskies/columns/many_to_many_ids_with_data.py +274 -0
- clearskies/columns/many_to_many_models.py +156 -0
- clearskies/columns/many_to_many_pivots.py +132 -0
- clearskies/columns/phone.py +162 -0
- clearskies/columns/select.py +95 -0
- clearskies/columns/string.py +102 -0
- clearskies/columns/timestamp.py +164 -0
- clearskies/columns/updated.py +107 -0
- clearskies/columns/uuid.py +83 -0
- clearskies/configs/README.md +105 -0
- clearskies/configs/__init__.py +170 -0
- clearskies/configs/actions.py +43 -0
- clearskies/configs/any.py +15 -0
- clearskies/configs/any_dict.py +24 -0
- clearskies/configs/any_dict_or_callable.py +25 -0
- clearskies/configs/authentication.py +23 -0
- clearskies/configs/authorization.py +23 -0
- clearskies/configs/boolean.py +18 -0
- clearskies/configs/boolean_or_callable.py +20 -0
- clearskies/configs/callable_config.py +20 -0
- clearskies/configs/columns.py +34 -0
- clearskies/configs/conditions.py +30 -0
- clearskies/configs/config.py +26 -0
- clearskies/configs/datetime.py +20 -0
- clearskies/configs/datetime_or_callable.py +21 -0
- clearskies/configs/email.py +10 -0
- clearskies/configs/email_list.py +17 -0
- clearskies/configs/email_list_or_callable.py +17 -0
- clearskies/configs/email_or_email_list_or_callable.py +59 -0
- clearskies/configs/endpoint.py +23 -0
- clearskies/configs/endpoint_list.py +29 -0
- clearskies/configs/float.py +18 -0
- clearskies/configs/float_or_callable.py +20 -0
- clearskies/configs/headers.py +28 -0
- clearskies/configs/integer.py +18 -0
- clearskies/configs/integer_or_callable.py +20 -0
- clearskies/configs/joins.py +30 -0
- clearskies/configs/list_any_dict.py +32 -0
- clearskies/configs/list_any_dict_or_callable.py +33 -0
- clearskies/configs/model_class.py +35 -0
- clearskies/configs/model_column.py +67 -0
- clearskies/configs/model_columns.py +58 -0
- clearskies/configs/model_destination_name.py +26 -0
- clearskies/configs/model_to_id_column.py +45 -0
- clearskies/configs/readable_model_column.py +11 -0
- clearskies/configs/readable_model_columns.py +11 -0
- clearskies/configs/schema.py +23 -0
- clearskies/configs/searchable_model_columns.py +11 -0
- clearskies/configs/security_headers.py +39 -0
- clearskies/configs/select.py +28 -0
- clearskies/configs/select_list.py +49 -0
- clearskies/configs/string.py +31 -0
- clearskies/configs/string_dict.py +34 -0
- clearskies/configs/string_list.py +47 -0
- clearskies/configs/string_list_or_callable.py +48 -0
- clearskies/configs/string_or_callable.py +18 -0
- clearskies/configs/timedelta.py +20 -0
- clearskies/configs/timezone.py +20 -0
- clearskies/configs/url.py +25 -0
- clearskies/configs/validators.py +45 -0
- clearskies/configs/writeable_model_column.py +11 -0
- clearskies/configs/writeable_model_columns.py +11 -0
- clearskies/configurable.py +78 -0
- clearskies/contexts/__init__.py +8 -8
- clearskies/contexts/cli.py +129 -43
- clearskies/contexts/context.py +93 -56
- clearskies/contexts/wsgi.py +79 -33
- clearskies/contexts/wsgi_ref.py +87 -0
- clearskies/cursors/__init__.py +7 -0
- clearskies/cursors/cursor.py +166 -0
- clearskies/cursors/from_environment/__init__.py +5 -0
- clearskies/cursors/from_environment/mysql.py +51 -0
- clearskies/cursors/from_environment/postgresql.py +49 -0
- clearskies/cursors/from_environment/sqlite.py +35 -0
- clearskies/cursors/mysql.py +61 -0
- clearskies/cursors/postgresql.py +61 -0
- clearskies/cursors/sqlite.py +62 -0
- clearskies/decorators.py +33 -0
- clearskies/decorators.pyi +10 -0
- clearskies/di/__init__.py +11 -7
- clearskies/di/additional_config.py +115 -4
- clearskies/di/additional_config_auto_import.py +12 -0
- clearskies/di/di.py +714 -125
- clearskies/di/inject/__init__.py +23 -0
- clearskies/di/inject/akeyless_sdk.py +16 -0
- clearskies/di/inject/by_class.py +24 -0
- clearskies/di/inject/by_name.py +22 -0
- clearskies/di/inject/di.py +16 -0
- clearskies/di/inject/environment.py +15 -0
- clearskies/di/inject/input_output.py +19 -0
- clearskies/di/inject/now.py +16 -0
- clearskies/di/inject/requests.py +16 -0
- clearskies/di/inject/secrets.py +15 -0
- clearskies/di/inject/utcnow.py +16 -0
- clearskies/di/inject/uuid.py +16 -0
- clearskies/di/injectable.py +32 -0
- clearskies/di/injectable_properties.py +131 -0
- clearskies/end.py +219 -0
- clearskies/endpoint.py +1303 -0
- clearskies/endpoint_group.py +333 -0
- clearskies/endpoints/__init__.py +25 -0
- clearskies/endpoints/advanced_search.py +519 -0
- clearskies/endpoints/callable.py +382 -0
- clearskies/endpoints/create.py +201 -0
- clearskies/endpoints/delete.py +133 -0
- clearskies/endpoints/get.py +267 -0
- clearskies/endpoints/health_check.py +181 -0
- clearskies/endpoints/list.py +567 -0
- clearskies/endpoints/restful_api.py +417 -0
- clearskies/endpoints/schema.py +185 -0
- clearskies/endpoints/simple_search.py +279 -0
- clearskies/endpoints/update.py +188 -0
- clearskies/environment.py +7 -3
- clearskies/exceptions/__init__.py +19 -0
- clearskies/{handlers/exceptions/input_error.py → exceptions/input_errors.py} +1 -1
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/functional/__init__.py +2 -2
- clearskies/functional/json.py +47 -0
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +19 -11
- clearskies/functional/validations.py +61 -9
- clearskies/input_outputs/__init__.py +9 -7
- clearskies/input_outputs/cli.py +135 -160
- clearskies/input_outputs/exceptions/__init__.py +6 -1
- clearskies/input_outputs/headers.py +54 -0
- clearskies/input_outputs/input_output.py +77 -123
- clearskies/input_outputs/programmatic.py +62 -0
- clearskies/input_outputs/wsgi.py +36 -48
- clearskies/model.py +1874 -193
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +228 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +193 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +4 -31
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +15 -4
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +11 -5
- clearskies/secrets/akeyless.py +421 -155
- clearskies/secrets/exceptions/__init__.py +7 -1
- clearskies/secrets/exceptions/not_found_error.py +2 -0
- clearskies/secrets/exceptions/permissions_error.py +2 -0
- clearskies/secrets/secrets.py +12 -11
- clearskies/security_header.py +17 -0
- clearskies/security_headers/__init__.py +8 -8
- clearskies/security_headers/cache_control.py +47 -109
- clearskies/security_headers/cors.py +38 -92
- clearskies/security_headers/csp.py +76 -150
- clearskies/security_headers/hsts.py +14 -15
- clearskies/typing.py +11 -0
- clearskies/validator.py +36 -0
- clearskies/validators/__init__.py +33 -0
- clearskies/validators/after_column.py +61 -0
- clearskies/validators/before_column.py +15 -0
- clearskies/validators/in_the_future.py +29 -0
- clearskies/validators/in_the_future_at_least.py +13 -0
- clearskies/validators/in_the_future_at_most.py +12 -0
- clearskies/validators/in_the_past.py +29 -0
- clearskies/validators/in_the_past_at_least.py +12 -0
- clearskies/validators/in_the_past_at_most.py +12 -0
- clearskies/validators/maximum_length.py +25 -0
- clearskies/validators/maximum_value.py +28 -0
- clearskies/validators/minimum_length.py +25 -0
- clearskies/validators/minimum_value.py +28 -0
- clearskies/{input_requirements → validators}/required.py +18 -9
- clearskies/validators/timedelta.py +58 -0
- clearskies/validators/unique.py +28 -0
- clear_skies-1.22.10.dist-info/METADATA +0 -47
- clear_skies-1.22.10.dist-info/RECORD +0 -213
- clearskies/application.py +0 -29
- clearskies/authentication/auth0_jwks.py +0 -118
- clearskies/authentication/auth_exception.py +0 -2
- clearskies/authentication/jwks_jwcrypto.py +0 -51
- clearskies/backends/api_get_only_backend.py +0 -48
- clearskies/backends/example_backend.py +0 -43
- clearskies/backends/file_backend.py +0 -48
- clearskies/backends/json_backend.py +0 -7
- clearskies/backends/restful_api_advanced_search_backend.py +0 -103
- clearskies/binding_config.py +0 -16
- clearskies/column_types/__init__.py +0 -203
- clearskies/column_types/audit.py +0 -249
- clearskies/column_types/belongs_to.py +0 -271
- clearskies/column_types/boolean.py +0 -60
- clearskies/column_types/category_tree.py +0 -304
- clearskies/column_types/column.py +0 -373
- clearskies/column_types/created.py +0 -26
- clearskies/column_types/created_by_authorization_data.py +0 -26
- clearskies/column_types/created_by_header.py +0 -24
- clearskies/column_types/created_by_ip.py +0 -17
- clearskies/column_types/created_by_routing_data.py +0 -25
- clearskies/column_types/created_by_user_agent.py +0 -17
- clearskies/column_types/created_micro.py +0 -26
- clearskies/column_types/datetime.py +0 -109
- clearskies/column_types/datetime_micro.py +0 -13
- clearskies/column_types/email.py +0 -18
- clearskies/column_types/float.py +0 -43
- clearskies/column_types/has_many.py +0 -179
- clearskies/column_types/has_one.py +0 -58
- clearskies/column_types/integer.py +0 -41
- clearskies/column_types/json.py +0 -25
- clearskies/column_types/many_to_many.py +0 -278
- clearskies/column_types/many_to_many_with_data.py +0 -162
- clearskies/column_types/phone.py +0 -48
- clearskies/column_types/select.py +0 -11
- clearskies/column_types/string.py +0 -24
- clearskies/column_types/timestamp.py +0 -73
- clearskies/column_types/updated.py +0 -26
- clearskies/column_types/updated_micro.py +0 -26
- clearskies/column_types/uuid.py +0 -25
- clearskies/columns.py +0 -123
- clearskies/condition_parser.py +0 -172
- clearskies/contexts/build_context.py +0 -54
- clearskies/contexts/convert_to_application.py +0 -190
- clearskies/contexts/extract_handler.py +0 -37
- clearskies/contexts/test.py +0 -94
- clearskies/decorators/__init__.py +0 -39
- clearskies/decorators/auth0_jwks.py +0 -22
- clearskies/decorators/authorization.py +0 -10
- clearskies/decorators/binding_classes.py +0 -9
- clearskies/decorators/binding_modules.py +0 -9
- clearskies/decorators/bindings.py +0 -9
- clearskies/decorators/create.py +0 -10
- clearskies/decorators/delete.py +0 -10
- clearskies/decorators/docs.py +0 -14
- clearskies/decorators/get.py +0 -10
- clearskies/decorators/jwks.py +0 -26
- clearskies/decorators/merge.py +0 -124
- clearskies/decorators/patch.py +0 -10
- clearskies/decorators/post.py +0 -10
- clearskies/decorators/public.py +0 -11
- clearskies/decorators/response_headers.py +0 -10
- clearskies/decorators/return_raw_response.py +0 -9
- clearskies/decorators/schema.py +0 -10
- clearskies/decorators/secret_bearer.py +0 -24
- clearskies/decorators/security_headers.py +0 -10
- clearskies/di/standard_dependencies.py +0 -151
- clearskies/di/test_module/__init__.py +0 -6
- clearskies/di/test_module/another_module/__init__.py +0 -2
- clearskies/di/test_module/module_class.py +0 -5
- clearskies/handlers/__init__.py +0 -41
- clearskies/handlers/advanced_search.py +0 -271
- clearskies/handlers/base.py +0 -479
- clearskies/handlers/callable.py +0 -191
- clearskies/handlers/create.py +0 -35
- clearskies/handlers/crud_by_method.py +0 -18
- clearskies/handlers/database_connector.py +0 -32
- clearskies/handlers/delete.py +0 -61
- clearskies/handlers/exceptions/__init__.py +0 -5
- clearskies/handlers/exceptions/not_found.py +0 -3
- clearskies/handlers/get.py +0 -156
- clearskies/handlers/health_check.py +0 -59
- clearskies/handlers/input_processing.py +0 -79
- clearskies/handlers/list.py +0 -530
- clearskies/handlers/mygrations.py +0 -82
- clearskies/handlers/request_method_routing.py +0 -47
- clearskies/handlers/restful_api.py +0 -218
- clearskies/handlers/routing.py +0 -62
- clearskies/handlers/schema_helper.py +0 -128
- clearskies/handlers/simple_routing.py +0 -206
- clearskies/handlers/simple_routing_route.py +0 -192
- clearskies/handlers/simple_search.py +0 -136
- clearskies/handlers/update.py +0 -96
- clearskies/handlers/write.py +0 -193
- clearskies/input_requirements/__init__.py +0 -78
- clearskies/input_requirements/after.py +0 -36
- clearskies/input_requirements/before.py +0 -36
- clearskies/input_requirements/in_the_future_at_least.py +0 -19
- clearskies/input_requirements/in_the_future_at_most.py +0 -19
- clearskies/input_requirements/in_the_past_at_least.py +0 -19
- clearskies/input_requirements/in_the_past_at_most.py +0 -19
- clearskies/input_requirements/maximum_length.py +0 -19
- clearskies/input_requirements/maximum_value.py +0 -19
- clearskies/input_requirements/minimum_length.py +0 -22
- clearskies/input_requirements/minimum_value.py +0 -19
- clearskies/input_requirements/requirement.py +0 -25
- clearskies/input_requirements/time_delta.py +0 -38
- clearskies/input_requirements/unique.py +0 -18
- clearskies/mocks/__init__.py +0 -7
- clearskies/mocks/input_output.py +0 -124
- clearskies/mocks/models.py +0 -142
- clearskies/models.py +0 -350
- clearskies/security_headers/base.py +0 -12
- clearskies/tests/simple_api/models/__init__.py +0 -2
- clearskies/tests/simple_api/models/status.py +0 -23
- clearskies/tests/simple_api/models/user.py +0 -21
- clearskies/tests/simple_api/users_api.py +0 -64
- {clear_skies-1.22.10.dist-info → clear_skies-2.0.23.dist-info/licenses}/LICENSE +0 -0
- /clearskies/{contexts/bash.py → autodoc/py.typed} +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authentication.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/authorization.py +0 -0
- /clearskies/{handlers/exceptions → exceptions}/client_error.py +0 -0
- /clearskies/{secrets/exceptions → exceptions}/not_found.py +0 -0
- /clearskies/{tests/__init__.py → input_outputs/py.typed} +0 -0
- /clearskies/{tests/simple_api/__init__.py → py.typed} +0 -0
|
@@ -1,373 +0,0 @@
|
|
|
1
|
-
from abc import ABC
|
|
2
|
-
import re
|
|
3
|
-
from ..autodoc.schema import String as AutoDocString
|
|
4
|
-
from .. import input_requirements
|
|
5
|
-
from .. import binding_config
|
|
6
|
-
import inspect
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
class Column(ABC):
|
|
10
|
-
_auto_doc_class = AutoDocString
|
|
11
|
-
_is_unique = None
|
|
12
|
-
_is_required = None
|
|
13
|
-
configuration = None
|
|
14
|
-
common_configs = [
|
|
15
|
-
"input_requirements",
|
|
16
|
-
"class",
|
|
17
|
-
"is_writeable",
|
|
18
|
-
"is_temporary",
|
|
19
|
-
"on_change",
|
|
20
|
-
"default",
|
|
21
|
-
"setable",
|
|
22
|
-
"created_by_source_type",
|
|
23
|
-
"created_by_source_key",
|
|
24
|
-
]
|
|
25
|
-
|
|
26
|
-
def __init__(self, di):
|
|
27
|
-
self.di = di
|
|
28
|
-
|
|
29
|
-
my_configs = []
|
|
30
|
-
required_configs = []
|
|
31
|
-
|
|
32
|
-
@property
|
|
33
|
-
def is_writeable(self):
|
|
34
|
-
is_writeable = self.config("is_writeable", True)
|
|
35
|
-
return True if (is_writeable or is_writeable is None) else False
|
|
36
|
-
|
|
37
|
-
@property
|
|
38
|
-
def is_readable(self):
|
|
39
|
-
return True
|
|
40
|
-
|
|
41
|
-
@property
|
|
42
|
-
def is_unique(self):
|
|
43
|
-
if self._is_unique is None:
|
|
44
|
-
requirements = self.config("input_requirements")
|
|
45
|
-
self._is_unique = False
|
|
46
|
-
for requirement in requirements:
|
|
47
|
-
if isinstance(requirement, input_requirements.Unique):
|
|
48
|
-
self._is_unique = True
|
|
49
|
-
return self._is_unique
|
|
50
|
-
|
|
51
|
-
@property
|
|
52
|
-
def is_temporary(self):
|
|
53
|
-
return bool(self.config("is_temporary", silent=True))
|
|
54
|
-
|
|
55
|
-
@property
|
|
56
|
-
def is_required(self):
|
|
57
|
-
if self._is_required is None:
|
|
58
|
-
requirements = self.config("input_requirements")
|
|
59
|
-
self._is_required = False
|
|
60
|
-
for requirement in requirements:
|
|
61
|
-
if isinstance(requirement, input_requirements.Required):
|
|
62
|
-
self._is_required = True
|
|
63
|
-
return self._is_required
|
|
64
|
-
|
|
65
|
-
def model_column_configurations(self):
|
|
66
|
-
nargs = len(inspect.getfullargspec(self.model_class.__init__).args) - 1
|
|
67
|
-
fake_model = self.model_class(*([""] * nargs))
|
|
68
|
-
return fake_model.all_columns()
|
|
69
|
-
|
|
70
|
-
def configure(self, name, configuration, model_class):
|
|
71
|
-
if not name:
|
|
72
|
-
raise ValueError(f"Missing name for column in '{model_class.__name__}'")
|
|
73
|
-
self.model_class = model_class
|
|
74
|
-
self.name = name
|
|
75
|
-
self._check_configuration(configuration)
|
|
76
|
-
configuration = self._finalize_configuration(configuration)
|
|
77
|
-
self.configuration = configuration
|
|
78
|
-
|
|
79
|
-
def _check_configuration(self, configuration):
|
|
80
|
-
"""Check the configuration and throw exceptions as needed"""
|
|
81
|
-
for key in self.required_configs:
|
|
82
|
-
if key not in configuration:
|
|
83
|
-
raise KeyError(
|
|
84
|
-
f"Missing required configuration '{key}' for column '{self.name}' in '{self.model_class.__name__}'"
|
|
85
|
-
)
|
|
86
|
-
for key in configuration.keys():
|
|
87
|
-
if key not in self.common_configs and key not in self.my_configs and key not in self.required_configs:
|
|
88
|
-
raise KeyError(
|
|
89
|
-
f"Configuration '{key}' not allowed for column '{self.name}' in '{self.model_class.__name__}'"
|
|
90
|
-
)
|
|
91
|
-
if "is_writeable" in configuration and type(configuration["is_writeable"]) != bool:
|
|
92
|
-
raise ValueError("'is_writeable' must be a boolean")
|
|
93
|
-
if configuration.get("on_change"):
|
|
94
|
-
self._check_actions(configuration.get("on_change"), "on_change")
|
|
95
|
-
|
|
96
|
-
self._check_created_by_source(configuration)
|
|
97
|
-
|
|
98
|
-
def _finalize_configuration(self, configuration):
|
|
99
|
-
"""Make any changes to the configuration/fill in defaults"""
|
|
100
|
-
if not "input_requirements" in configuration:
|
|
101
|
-
configuration["input_requirements"] = []
|
|
102
|
-
return configuration
|
|
103
|
-
|
|
104
|
-
def _check_created_by_source(self, configuration):
|
|
105
|
-
source_type = configuration.get("created_by_source_type")
|
|
106
|
-
source_key = configuration.get("created_by_source_key")
|
|
107
|
-
if not source_type and not source_key:
|
|
108
|
-
return
|
|
109
|
-
|
|
110
|
-
error_prefix = f"Misconfiguration for column '{self.name}' in '{self.model_class.__name__}': "
|
|
111
|
-
if not source_type or not source_key:
|
|
112
|
-
raise ValueError(
|
|
113
|
-
f"{error_prefix} must provide both 'created_by_source_type' and 'created_by_source_key' but only one was provided."
|
|
114
|
-
)
|
|
115
|
-
|
|
116
|
-
if not isinstance(source_type, str):
|
|
117
|
-
raise ValueError(
|
|
118
|
-
f"{error_prefix} 'created_by_source_type' must be a string but is a '"
|
|
119
|
-
+ source_type.__class__.__name__
|
|
120
|
-
+ "'"
|
|
121
|
-
)
|
|
122
|
-
if not isinstance(source_key, str):
|
|
123
|
-
raise ValueError(
|
|
124
|
-
f"{error_prefix} 'created_by_source_key' must be a string but is a '"
|
|
125
|
-
+ source_key.__class__.__name__
|
|
126
|
-
+ "'"
|
|
127
|
-
)
|
|
128
|
-
|
|
129
|
-
allowed_types = ["authorization_data"]
|
|
130
|
-
if source_type not in allowed_types:
|
|
131
|
-
raise ValueError(
|
|
132
|
-
f"{error_prefix} 'created_by_source_type' must be one of '" + "', '".join(allowed_types) + "'"
|
|
133
|
-
)
|
|
134
|
-
if configuration.get("setable"):
|
|
135
|
-
raise ValueError(f"{error_prefix} you cannot set both 'setable' and 'created_by_source_type'")
|
|
136
|
-
|
|
137
|
-
def _check_actions(self, actions, trigger_name):
|
|
138
|
-
"""Check that the given actions are actually understandable by the system"""
|
|
139
|
-
if type(actions) != list:
|
|
140
|
-
raise ValueError(
|
|
141
|
-
"The actions provided to a trigger should be a list of callables/binding configs, but something "
|
|
142
|
-
+ f"else was provided for the '{trigger_name}' trigger in '{self.model_class.__name__}'"
|
|
143
|
-
)
|
|
144
|
-
for index, action in enumerate(actions):
|
|
145
|
-
# if it's callable we're good. This includes functions, lambdas, callable objects,
|
|
146
|
-
# and classes that will be callable when instantiated
|
|
147
|
-
if callable(action):
|
|
148
|
-
continue
|
|
149
|
-
# the above pretty much covers everything. The only thing that we support otherwise
|
|
150
|
-
# is a binding config containing a callable class.
|
|
151
|
-
if type(action) == binding_config.BindingConfig:
|
|
152
|
-
if callable(action.object_class):
|
|
153
|
-
continue
|
|
154
|
-
|
|
155
|
-
raise ValueError(
|
|
156
|
-
f"Invalid action: action #{index+1} for trigger '{trigger_name} in '{self.model_class.__name__}'"
|
|
157
|
-
)
|
|
158
|
-
|
|
159
|
-
def config(self, key, silent=False):
|
|
160
|
-
if not key in self.configuration:
|
|
161
|
-
if silent:
|
|
162
|
-
return None
|
|
163
|
-
raise KeyError(f"column '{self.__class__.__name__}' does not have a configuration named '{key}'")
|
|
164
|
-
|
|
165
|
-
return self.configuration[key]
|
|
166
|
-
|
|
167
|
-
def additional_write_columns(self, is_create=False):
|
|
168
|
-
additional_write_columns = {}
|
|
169
|
-
for requirement in self.config("input_requirements"):
|
|
170
|
-
additional_write_columns = {
|
|
171
|
-
**additional_write_columns,
|
|
172
|
-
**requirement.additional_write_columns(is_create=is_create),
|
|
173
|
-
}
|
|
174
|
-
return additional_write_columns
|
|
175
|
-
|
|
176
|
-
def from_backend(self, value):
|
|
177
|
-
"""
|
|
178
|
-
Takes the database representation and returns a python representation
|
|
179
|
-
|
|
180
|
-
For instance, for an SQL date field, this will return a Python DateTime object
|
|
181
|
-
"""
|
|
182
|
-
return value
|
|
183
|
-
|
|
184
|
-
def to_backend(self, data):
|
|
185
|
-
"""
|
|
186
|
-
Makes any changes needed to save the data to the backend.
|
|
187
|
-
|
|
188
|
-
This typically means formatting changes - converting DateTime objects to database
|
|
189
|
-
date strings, etc...
|
|
190
|
-
"""
|
|
191
|
-
return data
|
|
192
|
-
|
|
193
|
-
def to_json(self, model):
|
|
194
|
-
"""
|
|
195
|
-
Grabs the column out of the model and converts it into a representation that can be turned into JSON
|
|
196
|
-
"""
|
|
197
|
-
return {self.name: model.get(self.name, silent=True)}
|
|
198
|
-
|
|
199
|
-
def input_errors(self, model, data):
|
|
200
|
-
error = self.check_input(model, data)
|
|
201
|
-
if error:
|
|
202
|
-
return {self.name: error}
|
|
203
|
-
|
|
204
|
-
for requirement in self.config("input_requirements"):
|
|
205
|
-
error = requirement.check(model, data)
|
|
206
|
-
if error:
|
|
207
|
-
return {self.name: error}
|
|
208
|
-
|
|
209
|
-
return {}
|
|
210
|
-
|
|
211
|
-
def check_input(self, model, data):
|
|
212
|
-
if self.name not in data or not data[self.name]:
|
|
213
|
-
return ""
|
|
214
|
-
return self.input_error_for_value(data[self.name])
|
|
215
|
-
|
|
216
|
-
def pre_save(self, data, model):
|
|
217
|
-
"""
|
|
218
|
-
Make any changes needed to the data before starting the save process
|
|
219
|
-
|
|
220
|
-
The difference between this and transform_for_database is that transform_for_database only affects
|
|
221
|
-
the data as it is going into the database, while this affects the data that will get persisted
|
|
222
|
-
in the object as well. So for instance, for a "created" field, pre_save may fill in the current
|
|
223
|
-
date with a Python DateTime object when the record is being saved, and then transform_for_database may
|
|
224
|
-
turn that into an SQL-compatible date string.
|
|
225
|
-
|
|
226
|
-
The difference between this and post_save is that this happens before the database is updated.
|
|
227
|
-
As a result, if you need the model id to make your changes, it has to happen in post_save, not pre_save
|
|
228
|
-
"""
|
|
229
|
-
if not model.exists:
|
|
230
|
-
source_type = self.configuration.get("created_by_source_type")
|
|
231
|
-
if source_type:
|
|
232
|
-
if source_type == "authorization_data":
|
|
233
|
-
authorization_data = self.di.build("input_output", cache=True).get_authorization_data()
|
|
234
|
-
data[self.name] = authorization_data.get(self.config("created_by_source_key"), "N/A")
|
|
235
|
-
if "setable" in self.configuration:
|
|
236
|
-
setable = self.configuration["setable"]
|
|
237
|
-
if callable(setable):
|
|
238
|
-
data[self.name] = self.di.call_function(setable, data=data, model=model)
|
|
239
|
-
else:
|
|
240
|
-
data[self.name] = setable
|
|
241
|
-
if not model.exists and "default" in self.configuration and self.name not in data:
|
|
242
|
-
data[self.name] = self.configuration["default"]
|
|
243
|
-
return data
|
|
244
|
-
|
|
245
|
-
def post_save(self, data, model, id):
|
|
246
|
-
"""
|
|
247
|
-
Make any changes needed after saving to the database
|
|
248
|
-
|
|
249
|
-
data is the data being saved and id is the id of the record. Note that while the database is updated
|
|
250
|
-
before this is called, the model isn't, so there will be a difference between what is in the database
|
|
251
|
-
and what is in the object.
|
|
252
|
-
"""
|
|
253
|
-
return data
|
|
254
|
-
|
|
255
|
-
def save_finished(self, model):
|
|
256
|
-
"""
|
|
257
|
-
Make any necessary changes needed after a save has completely finished.
|
|
258
|
-
|
|
259
|
-
This is typically used for configurable triggers set by the developer. Column-specific behavior
|
|
260
|
-
that needs to always happen is placed in pre_save or post_save because those affect the save
|
|
261
|
-
process itself.
|
|
262
|
-
"""
|
|
263
|
-
on_change_actions = self.config("on_change", silent=True)
|
|
264
|
-
if on_change_actions and model.was_changed(self.name):
|
|
265
|
-
self.execute_actions(on_change_actions, model)
|
|
266
|
-
|
|
267
|
-
def values_match(self, value_1, value_2):
|
|
268
|
-
"""
|
|
269
|
-
Compares two values to see if they are the same
|
|
270
|
-
"""
|
|
271
|
-
return value_1 == value_2
|
|
272
|
-
|
|
273
|
-
def pre_delete(self, model):
|
|
274
|
-
"""
|
|
275
|
-
Make any changes needed to the data before starting the delete process
|
|
276
|
-
"""
|
|
277
|
-
pass
|
|
278
|
-
|
|
279
|
-
def post_delete(self, model):
|
|
280
|
-
"""
|
|
281
|
-
Make any changes needed to the data before finishing the delete process
|
|
282
|
-
"""
|
|
283
|
-
pass
|
|
284
|
-
|
|
285
|
-
def can_provide(self, column_name):
|
|
286
|
-
"""
|
|
287
|
-
This works together with self.provide to load ancillary data
|
|
288
|
-
|
|
289
|
-
For instance, a foreign key will have an "id" column such as `user_id` but it can also load up
|
|
290
|
-
the user model, which you expect to happen by requesting `model.user`. If a model receives a
|
|
291
|
-
request for a column name that it doesn't recognize, it will loop through all the columns and
|
|
292
|
-
call `can_provide` with the column name. We then have to return True or False to denote whether
|
|
293
|
-
or not we can provide the thing being requested. If we return True then the model will then
|
|
294
|
-
call `column.provide` with the data from the model and the requested column name
|
|
295
|
-
"""
|
|
296
|
-
return False
|
|
297
|
-
|
|
298
|
-
def provide(self, data, column_name):
|
|
299
|
-
"""
|
|
300
|
-
This is called if the column declares that it can provide something, and should return the value
|
|
301
|
-
|
|
302
|
-
See can_provide for more details on the flow here
|
|
303
|
-
"""
|
|
304
|
-
pass
|
|
305
|
-
|
|
306
|
-
def execute_actions(self, actions, model):
|
|
307
|
-
for action in actions:
|
|
308
|
-
if type(action) == binding_config.BindingConfig:
|
|
309
|
-
action = self.di.build(action)
|
|
310
|
-
elif inspect.isclass(action):
|
|
311
|
-
action = self.di.build(action)
|
|
312
|
-
if hasattr(action, "__call__"):
|
|
313
|
-
self.di.call_function(action.__call__, model=model)
|
|
314
|
-
else:
|
|
315
|
-
self.di.call_function(action.__call__, model=model)
|
|
316
|
-
|
|
317
|
-
def add_search(self, models, value, operator=None, relationship_reference=None):
|
|
318
|
-
return models.where(self.build_condition(value, operator=operator))
|
|
319
|
-
|
|
320
|
-
def build_condition(self, value, operator=None, column_prefix=""):
|
|
321
|
-
"""
|
|
322
|
-
This is called by the read (and related) handlers to turn user input into a condition.
|
|
323
|
-
|
|
324
|
-
Note that this may look like it is vulnerable to SQLi, but it isn't. These conditions aren't passed directly
|
|
325
|
-
into a query. Rather, they are parsed by the condition parser before being sent into the backend.
|
|
326
|
-
The condition parser can safely reconstruct the original pieces, and the backend can then use the data
|
|
327
|
-
safely (and remember, the backend may not be an SQL anyway)
|
|
328
|
-
|
|
329
|
-
As a result, this is perfectly safe for any user input, assuming normal system flow.
|
|
330
|
-
"""
|
|
331
|
-
return f"{column_prefix}{self.name}={value}"
|
|
332
|
-
|
|
333
|
-
def is_allowed_operator(self, operator, relationship_reference=None):
|
|
334
|
-
"""
|
|
335
|
-
This is called when processing user data to decide if the end-user is specifying an allowed operator
|
|
336
|
-
"""
|
|
337
|
-
return operator == "="
|
|
338
|
-
|
|
339
|
-
def configure_n_plus_one(self, models):
|
|
340
|
-
return models
|
|
341
|
-
|
|
342
|
-
def check_search_value(self, value, operator=None, relationship_reference=None):
|
|
343
|
-
return self.input_error_for_value(value, operator=operator)
|
|
344
|
-
|
|
345
|
-
def input_error_for_value(self, value, operator=None):
|
|
346
|
-
return ""
|
|
347
|
-
|
|
348
|
-
def where_for_request(self, models, routing_data, authorization_data, input_output):
|
|
349
|
-
"""
|
|
350
|
-
A hook to automatically apply filtering whenever the column makes an appearance in a get/update/list/search handler.
|
|
351
|
-
"""
|
|
352
|
-
return models
|
|
353
|
-
|
|
354
|
-
def validate_models_class(self, models_class, config_name="parent_models_class"):
|
|
355
|
-
if not hasattr(models_class, "model_class"):
|
|
356
|
-
if hasattr(models_class, "columns_configuration"):
|
|
357
|
-
raise ValueError(
|
|
358
|
-
f"'{config_name}' in configuration for column '{self.name}' in model class "
|
|
359
|
-
+ f"'{self.model_class.__name__}' appears to be a Model class, but it should be a Models class"
|
|
360
|
-
)
|
|
361
|
-
else:
|
|
362
|
-
raise ValueError(
|
|
363
|
-
f"'{config_name}' in configuration for column '{self.name}' should be a Models class, "
|
|
364
|
-
+ f"but it appears to be something unknown."
|
|
365
|
-
)
|
|
366
|
-
|
|
367
|
-
def camel_to_nice(self, string):
|
|
368
|
-
string = re.sub("(.)([A-Z][a-z]+)", r"\1 \2", string)
|
|
369
|
-
string = re.sub("([a-z0-9])([A-Z])", r"\1 \2", string).lower()
|
|
370
|
-
return string
|
|
371
|
-
|
|
372
|
-
def documentation(self, name=None, example=None, value=None):
|
|
373
|
-
return self._auto_doc_class(name if name is not None else self.name, example=example, value=value)
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from .datetime import DateTime
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class Created(DateTime):
|
|
5
|
-
my_configs = [
|
|
6
|
-
"date_format",
|
|
7
|
-
"default_date",
|
|
8
|
-
"utc",
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
def __init__(self, di, datetime, timezone):
|
|
12
|
-
super().__init__(di, timezone)
|
|
13
|
-
self.datetime = datetime
|
|
14
|
-
|
|
15
|
-
@property
|
|
16
|
-
def is_writeable(self):
|
|
17
|
-
return False
|
|
18
|
-
|
|
19
|
-
def pre_save(self, data, model):
|
|
20
|
-
if model.exists:
|
|
21
|
-
return data
|
|
22
|
-
if self.config("utc", silent=True):
|
|
23
|
-
now = self.datetime.datetime.now(self.datetime.timezone.utc)
|
|
24
|
-
else:
|
|
25
|
-
now = self.datetime.datetime.now(self._timezone)
|
|
26
|
-
return {**data, self.name: now}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from .string import String
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CreatedByAuthorizationData(String):
|
|
5
|
-
required_configs = [
|
|
6
|
-
"authorization_data_key_name",
|
|
7
|
-
]
|
|
8
|
-
|
|
9
|
-
def __init__(self, di):
|
|
10
|
-
super().__init__(di)
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def is_writeable(self):
|
|
14
|
-
return False
|
|
15
|
-
|
|
16
|
-
def pre_save(self, data, model):
|
|
17
|
-
if model.exists:
|
|
18
|
-
return data
|
|
19
|
-
|
|
20
|
-
authorization_data = self.di.build("input_output", cache=True).get_authorization_data()
|
|
21
|
-
# data comes last so that it can override the info in the authorization data. This seems counter-intuitive,
|
|
22
|
-
# but is important. You would think that you *don't* want the data from the authorization data to be
|
|
23
|
-
# overridden (since this is mainly used for logging), but the trouble is that there are a variety of use-cases
|
|
24
|
-
# where the application must provide the audit data. Examples include registration and login. In these
|
|
25
|
-
# cases, authorization data will be empty, and must be provided by the applicaiton.
|
|
26
|
-
return {self.name: authorization_data.get(self.config("authorization_data_key_name"), "N/A"), **data}
|
|
@@ -1,24 +0,0 @@
|
|
|
1
|
-
from .string import String
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CreatedByHeader(String):
|
|
5
|
-
required_configs = [
|
|
6
|
-
"header_name",
|
|
7
|
-
]
|
|
8
|
-
|
|
9
|
-
def __init__(self, di):
|
|
10
|
-
super().__init__(di)
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def is_writeable(self):
|
|
14
|
-
return False
|
|
15
|
-
|
|
16
|
-
def pre_save(self, data, model):
|
|
17
|
-
if model.exists:
|
|
18
|
-
return data
|
|
19
|
-
|
|
20
|
-
input_output = self.di.build("input_output", cache=True)
|
|
21
|
-
return {
|
|
22
|
-
**data,
|
|
23
|
-
self.name: input_output.get_request_header(self.config("header_name")),
|
|
24
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from .string import String
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CreatedByIp(String):
|
|
5
|
-
def __init__(self, di):
|
|
6
|
-
super().__init__(di)
|
|
7
|
-
|
|
8
|
-
@property
|
|
9
|
-
def is_writeable(self):
|
|
10
|
-
return False
|
|
11
|
-
|
|
12
|
-
def pre_save(self, data, model):
|
|
13
|
-
if model.exists:
|
|
14
|
-
return data
|
|
15
|
-
|
|
16
|
-
input_output = self.di.build("input_output", cache=True)
|
|
17
|
-
return {**data, self.name: input_output.get_client_ip()}
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
from .string import String
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CreatedByRoutingData(String):
|
|
5
|
-
required_configs = [
|
|
6
|
-
"routing_data_name",
|
|
7
|
-
]
|
|
8
|
-
|
|
9
|
-
def __init__(self, di):
|
|
10
|
-
super().__init__(di)
|
|
11
|
-
|
|
12
|
-
@property
|
|
13
|
-
def is_writeable(self):
|
|
14
|
-
return False
|
|
15
|
-
|
|
16
|
-
def pre_save(self, data, model):
|
|
17
|
-
if model.exists:
|
|
18
|
-
return data
|
|
19
|
-
|
|
20
|
-
input_output = self.di.build("input_output", cache=True)
|
|
21
|
-
routing_data = input_output.routing_data()
|
|
22
|
-
return {
|
|
23
|
-
**data,
|
|
24
|
-
self.name: routing_data[self.config("routing_data_name")],
|
|
25
|
-
}
|
|
@@ -1,17 +0,0 @@
|
|
|
1
|
-
from .string import String
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CreatedByUserAgent(String):
|
|
5
|
-
def __init__(self, di):
|
|
6
|
-
super().__init__(di)
|
|
7
|
-
|
|
8
|
-
@property
|
|
9
|
-
def is_writeable(self):
|
|
10
|
-
return False
|
|
11
|
-
|
|
12
|
-
def pre_save(self, data, model):
|
|
13
|
-
if model.exists:
|
|
14
|
-
return data
|
|
15
|
-
|
|
16
|
-
input_output = self.di.build("input_output", cache=True)
|
|
17
|
-
return {**data, self.name: input_output.get_request_header("user-agent")}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
from .datetime_micro import DateTimeMicro
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
class CreatedMicro(DateTimeMicro):
|
|
5
|
-
my_configs = [
|
|
6
|
-
"date_format",
|
|
7
|
-
"default_date",
|
|
8
|
-
"utc",
|
|
9
|
-
]
|
|
10
|
-
|
|
11
|
-
def __init__(self, di, datetime, timezone):
|
|
12
|
-
super().__init__(di, timezone)
|
|
13
|
-
self.datetime = datetime
|
|
14
|
-
|
|
15
|
-
@property
|
|
16
|
-
def is_writeable(self):
|
|
17
|
-
return False
|
|
18
|
-
|
|
19
|
-
def pre_save(self, data, model):
|
|
20
|
-
if model.exists:
|
|
21
|
-
return data
|
|
22
|
-
if self.config("utc", silent=True):
|
|
23
|
-
now = self.datetime.datetime.now(self.datetime.timezone.utc)
|
|
24
|
-
else:
|
|
25
|
-
now = self.datetime.datetime.now(self._timezone)
|
|
26
|
-
return {**data, self.name: now}
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
from .column import Column
|
|
2
|
-
from datetime import datetime, timezone
|
|
3
|
-
import dateparser
|
|
4
|
-
from ..autodoc.schema import DateTime as AutoDocDateTime
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
class DateTime(Column):
|
|
8
|
-
_auto_doc_class = AutoDocDateTime
|
|
9
|
-
_date_format = "%Y-%m-%d %H:%M:%S"
|
|
10
|
-
_default_date = "0000-00-00 00:00:00"
|
|
11
|
-
|
|
12
|
-
my_configs = [
|
|
13
|
-
"date_format",
|
|
14
|
-
"default_date",
|
|
15
|
-
]
|
|
16
|
-
|
|
17
|
-
def __init__(self, di, timezone: datetime.tzinfo):
|
|
18
|
-
super().__init__(di)
|
|
19
|
-
self._timezone = timezone
|
|
20
|
-
|
|
21
|
-
def _finalize_configuration(self, configuration):
|
|
22
|
-
return {
|
|
23
|
-
**{
|
|
24
|
-
"date_format": self._date_format,
|
|
25
|
-
"default_date": self._default_date,
|
|
26
|
-
},
|
|
27
|
-
**super()._finalize_configuration(configuration),
|
|
28
|
-
}
|
|
29
|
-
|
|
30
|
-
def from_backend(self, value):
|
|
31
|
-
if not value or value == self.config("default_date"):
|
|
32
|
-
date = None
|
|
33
|
-
elif type(value) == str:
|
|
34
|
-
date = dateparser.parse(value)
|
|
35
|
-
else:
|
|
36
|
-
date = value
|
|
37
|
-
return date.replace(tzinfo=self._timezone) if date else None
|
|
38
|
-
|
|
39
|
-
def to_backend(self, data):
|
|
40
|
-
if not self.name in data or type(data[self.name]) == str or data[self.name] == None:
|
|
41
|
-
return data
|
|
42
|
-
|
|
43
|
-
# hopefully this is a Python datetime object in UTC timezone...
|
|
44
|
-
return {**data, **{self.name: data[self.name].strftime(self.config("date_format"))}}
|
|
45
|
-
|
|
46
|
-
def to_json(self, model):
|
|
47
|
-
datetime = model.get(self.name, silent=True)
|
|
48
|
-
return {self.name: datetime.isoformat() if datetime else None}
|
|
49
|
-
|
|
50
|
-
def build_condition(self, value, operator=None, column_prefix=""):
|
|
51
|
-
date = dateparser.parse(value).astimezone(self._timezone).strftime(self.config("date_format"))
|
|
52
|
-
if not operator:
|
|
53
|
-
operator = "="
|
|
54
|
-
return f"{column_prefix}{self.name}{operator}{date}"
|
|
55
|
-
|
|
56
|
-
def is_allowed_operator(self, operator, relationship_reference=None):
|
|
57
|
-
"""
|
|
58
|
-
This is called when processing user data to decide if the end-user is specifying an allowed operator
|
|
59
|
-
"""
|
|
60
|
-
return operator in ["=", "<", ">", "<=", ">="]
|
|
61
|
-
|
|
62
|
-
def input_error_for_value(self, value, operator=None):
|
|
63
|
-
value = dateparser.parse(value)
|
|
64
|
-
if not value:
|
|
65
|
-
return "given value did not appear to be a valid date"
|
|
66
|
-
if not value.tzinfo:
|
|
67
|
-
return "date is missing timezone information"
|
|
68
|
-
return ""
|
|
69
|
-
|
|
70
|
-
def values_match(self, value_1, value_2):
|
|
71
|
-
"""
|
|
72
|
-
Compares two values to see if they are the same
|
|
73
|
-
"""
|
|
74
|
-
# in this function we deal with data directly out of the backend, so our date is likely
|
|
75
|
-
# to be string-ified and we want to look for default (e.g. null) values in string form.
|
|
76
|
-
if type(value_1) == str and "0000-00-00" in value_1:
|
|
77
|
-
value_1 = None
|
|
78
|
-
if type(value_2) == str and "0000-00-00" in value_2:
|
|
79
|
-
value_2 = None
|
|
80
|
-
number_values = 0
|
|
81
|
-
if value_1:
|
|
82
|
-
number_values += 1
|
|
83
|
-
if value_2:
|
|
84
|
-
number_values += 1
|
|
85
|
-
if number_values == 0:
|
|
86
|
-
return True
|
|
87
|
-
if number_values == 1:
|
|
88
|
-
return False
|
|
89
|
-
|
|
90
|
-
if type(value_1) == str:
|
|
91
|
-
value_1 = dateparser.parse(value_1)
|
|
92
|
-
if type(value_2) == str:
|
|
93
|
-
value_2 = dateparser.parse(value_2)
|
|
94
|
-
|
|
95
|
-
# we need to make sure we're comparing in the same timezones. For our purposes, a difference in timezone
|
|
96
|
-
# is fine as long as they represent the same time (e.g. 16:00EST == 20:00UTC). For python, same time in different
|
|
97
|
-
# timezones is treated as different datetime objects.
|
|
98
|
-
if value_1.tzinfo is not None and value_2.tzinfo is not None:
|
|
99
|
-
value_1 = value_1.astimezone(value_2.tzinfo)
|
|
100
|
-
|
|
101
|
-
# two times can be the same but if one is datetime-aware and one is not, python will treat them as not equal.
|
|
102
|
-
# we want to treat such times as being the same. Therefore, check for equality but ignore the timezone.
|
|
103
|
-
for to_check in ["year", "month", "day", "hour", "minute", "second", "microsecond"]:
|
|
104
|
-
if getattr(value_1, to_check) != getattr(value_2, to_check):
|
|
105
|
-
return False
|
|
106
|
-
|
|
107
|
-
# and since we already converted the timezones to match (or one has a timezone and one doesn't), we're good to go.
|
|
108
|
-
# if we passed the above loop then the times are the same.
|
|
109
|
-
return True
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
from re import T
|
|
2
|
-
from .datetime import DateTime
|
|
3
|
-
from datetime import datetime, timezone
|
|
4
|
-
import dateparser
|
|
5
|
-
from ..autodoc.schema import DateTime as AutoDocDateTime
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
class DateTimeMicro(DateTime):
|
|
9
|
-
_date_format = "%Y-%m-%d %H:%M:%S.%f"
|
|
10
|
-
_default_date = "0000-00-00 00:00:00.000000"
|
|
11
|
-
|
|
12
|
-
def __init__(self, di, timezone: datetime.tzinfo):
|
|
13
|
-
super().__init__(di, timezone)
|
clearskies/column_types/email.py
DELETED
|
@@ -1,18 +0,0 @@
|
|
|
1
|
-
from .string import String
|
|
2
|
-
import re
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
class Email(String):
|
|
6
|
-
def __init__(self, di):
|
|
7
|
-
super().__init__(di)
|
|
8
|
-
|
|
9
|
-
def input_error_for_value(self, value, operator=None):
|
|
10
|
-
if type(value) != str:
|
|
11
|
-
return f"Value must be a string for {self.name}"
|
|
12
|
-
if operator and operator.lower() == "like":
|
|
13
|
-
# don't check for an email if doing a fuzzy search, since we may be searching
|
|
14
|
-
# for a partial email
|
|
15
|
-
return ""
|
|
16
|
-
if re.search(r"^[^@\s]+@[^@]+\.[^@]+$", value):
|
|
17
|
-
return ""
|
|
18
|
-
return "Invalid email address"
|