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
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import datetime
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable, Self, overload
|
|
5
|
+
|
|
6
|
+
from clearskies import configs, decorators
|
|
7
|
+
from clearskies.columns.datetime import Datetime
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from clearskies import Model, typing
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Timestamp(Datetime):
|
|
14
|
+
"""
|
|
15
|
+
A timestamp column.
|
|
16
|
+
|
|
17
|
+
The difference between this and the datetime column is that this stores the datetime
|
|
18
|
+
as a standard unix timestamp - the number of seconds since the unix epoch.
|
|
19
|
+
|
|
20
|
+
Also, this **always** assumes the timezone for the timestamp is UTC
|
|
21
|
+
|
|
22
|
+
```python
|
|
23
|
+
import datetime
|
|
24
|
+
import clearskies
|
|
25
|
+
|
|
26
|
+
|
|
27
|
+
class Pet(clearskies.Model):
|
|
28
|
+
id_column_name = "id"
|
|
29
|
+
backend = clearskies.backends.MemoryBackend()
|
|
30
|
+
|
|
31
|
+
id = clearskies.columns.Uuid()
|
|
32
|
+
name = clearskies.columns.String()
|
|
33
|
+
last_fed = clearskies.columns.Timestamp()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def demo_timestamp(utcnow: datetime.datetime, pets: Pet) -> dict[str, str | int]:
|
|
37
|
+
pet = pets.create(
|
|
38
|
+
{
|
|
39
|
+
"name": "Spot",
|
|
40
|
+
"last_fed": utcnow,
|
|
41
|
+
}
|
|
42
|
+
)
|
|
43
|
+
return {
|
|
44
|
+
"last_fed": pet.last_fed.isoformat(),
|
|
45
|
+
"raw_data": pet.get_raw_data()["last_fed"],
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
cli = clearskies.contexts.Cli(
|
|
50
|
+
clearskies.endpoints.Callable(
|
|
51
|
+
demo_timestamp,
|
|
52
|
+
),
|
|
53
|
+
classes=[Pet],
|
|
54
|
+
)
|
|
55
|
+
cli()
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
And when invoked it returns:
|
|
59
|
+
|
|
60
|
+
```json
|
|
61
|
+
{
|
|
62
|
+
"status": "success",
|
|
63
|
+
"error": "",
|
|
64
|
+
"data": {"last_fed": "2025-05-18T19:14:56+00:00", "raw_data": 1747595696},
|
|
65
|
+
"pagination": {},
|
|
66
|
+
"input_errors": {},
|
|
67
|
+
}
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
Note that if you pull the column from the model in the usual way (e.g. `pet.last_fed` you get a timestamp,
|
|
71
|
+
but if you check the raw data straight out of the backend (e.g. `pet.get_raw_data()["last_fed"]`) it's an
|
|
72
|
+
integer.
|
|
73
|
+
"""
|
|
74
|
+
|
|
75
|
+
# whether or not to include the microseconds in the timestamp
|
|
76
|
+
include_microseconds = configs.Boolean(default=False)
|
|
77
|
+
_descriptor_config_map = None
|
|
78
|
+
|
|
79
|
+
@decorators.parameters_to_properties
|
|
80
|
+
def __init__(
|
|
81
|
+
self,
|
|
82
|
+
include_microseconds: bool = False,
|
|
83
|
+
default: datetime.datetime | None = None,
|
|
84
|
+
setable: datetime.datetime | Callable[..., datetime.datetime] | None = None,
|
|
85
|
+
is_readable: bool = True,
|
|
86
|
+
is_writeable: bool = True,
|
|
87
|
+
is_searchable: bool = True,
|
|
88
|
+
is_temporary: bool = False,
|
|
89
|
+
validators: typing.validator | list[typing.validator] = [],
|
|
90
|
+
on_change_pre_save: typing.action | list[typing.action] = [],
|
|
91
|
+
on_change_post_save: typing.action | list[typing.action] = [],
|
|
92
|
+
on_change_save_finished: typing.action | list[typing.action] = [],
|
|
93
|
+
created_by_source_type: str = "",
|
|
94
|
+
created_by_source_key: str = "",
|
|
95
|
+
created_by_source_strict: bool = True,
|
|
96
|
+
):
|
|
97
|
+
pass
|
|
98
|
+
|
|
99
|
+
def from_backend(self, value) -> datetime.datetime | None:
|
|
100
|
+
mult = 1000 if self.include_microseconds else 1
|
|
101
|
+
if not value:
|
|
102
|
+
date = None
|
|
103
|
+
elif isinstance(value, str):
|
|
104
|
+
if not value.isdigit():
|
|
105
|
+
raise ValueError(
|
|
106
|
+
f"Invalid data was found in the backend for model {self.model_class.__name__} and column {self.name}: a string value was found that is not a timestamp. It was '{value}'"
|
|
107
|
+
)
|
|
108
|
+
date = datetime.datetime.fromtimestamp(int(value) / mult, datetime.timezone.utc)
|
|
109
|
+
elif isinstance(value, (int, float)):
|
|
110
|
+
date = datetime.datetime.fromtimestamp(value / mult, datetime.timezone.utc)
|
|
111
|
+
else:
|
|
112
|
+
if not isinstance(value, datetime.datetime):
|
|
113
|
+
raise ValueError(
|
|
114
|
+
f"Invalid data was found in the backend for model {self.model_class.__name__} and column {self.name}: the value was neither an integer, float, string, or datetime object"
|
|
115
|
+
)
|
|
116
|
+
date = value
|
|
117
|
+
return date.replace(tzinfo=datetime.timezone.utc) if date else None
|
|
118
|
+
|
|
119
|
+
def to_backend(self, data: dict[str, Any]) -> dict[str, Any]:
|
|
120
|
+
if not self.name in data or isinstance(data[self.name], int) or data[self.name] == None:
|
|
121
|
+
return data
|
|
122
|
+
|
|
123
|
+
value = data[self.name]
|
|
124
|
+
if isinstance(value, str):
|
|
125
|
+
if not value.isdigit():
|
|
126
|
+
raise ValueError(
|
|
127
|
+
f"Invalid data was sent to the backend for model {self.model_class.__name__} and column {self.name}: a string value was found that is not a timestamp. It was '{value}'"
|
|
128
|
+
)
|
|
129
|
+
value = int(value)
|
|
130
|
+
elif isinstance(value, datetime.datetime):
|
|
131
|
+
value = value.timestamp()
|
|
132
|
+
else:
|
|
133
|
+
raise ValueError(
|
|
134
|
+
f"Invalid data was sent to the backend for model {self.model_class.__name__} and column {self.name}: the value was neither an integer, a string, nor a datetime object"
|
|
135
|
+
)
|
|
136
|
+
|
|
137
|
+
return {**data, self.name: int(value)}
|
|
138
|
+
|
|
139
|
+
@overload
|
|
140
|
+
def __get__(self, instance: None, cls: type) -> Self:
|
|
141
|
+
pass
|
|
142
|
+
|
|
143
|
+
@overload
|
|
144
|
+
def __get__(self, instance: Model, cls: type) -> datetime.datetime:
|
|
145
|
+
pass
|
|
146
|
+
|
|
147
|
+
def __get__(self, instance, cls):
|
|
148
|
+
return super().__get__(instance, cls)
|
|
149
|
+
|
|
150
|
+
def __set__(self, instance, value: datetime.datetime) -> None:
|
|
151
|
+
# this makes sure we're initialized
|
|
152
|
+
if "name" not in self._config: # type: ignore
|
|
153
|
+
instance.get_columns()
|
|
154
|
+
|
|
155
|
+
instance._next_data[self.name] = value
|
|
156
|
+
|
|
157
|
+
def input_error_for_value(self, value: str, operator: str | None = None) -> str:
|
|
158
|
+
if not isinstance(value, int):
|
|
159
|
+
return f"'{self.name}' must be an integer"
|
|
160
|
+
return ""
|
|
161
|
+
|
|
162
|
+
def values_match(self, value_1, value_2):
|
|
163
|
+
"""Compare two values to see if they are the same."""
|
|
164
|
+
return value_1 == value_2
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from clearskies import configs, decorators
|
|
6
|
+
from clearskies.columns.datetime import Datetime
|
|
7
|
+
from clearskies.di import inject
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from clearskies import Model, typing
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Updated(Datetime):
|
|
14
|
+
"""
|
|
15
|
+
The updated column records the time that a record is created or updated.
|
|
16
|
+
|
|
17
|
+
Note that this will always populate the column anytime the model is created or updated.
|
|
18
|
+
You don't have to provide the timestamp yourself and you should never expose it as
|
|
19
|
+
a writeable column through an endpoint (in fact, you can't).
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import clearskies
|
|
23
|
+
import time
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
class MyModel(clearskies.Model):
|
|
27
|
+
backend = clearskies.backends.MemoryBackend()
|
|
28
|
+
id_column_name = "id"
|
|
29
|
+
|
|
30
|
+
id = clearskies.columns.Uuid()
|
|
31
|
+
name = clearskies.columns.String()
|
|
32
|
+
created = clearskies.columns.Created()
|
|
33
|
+
updated = clearskies.columns.Updated()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def test_updated(my_models: MyModel) -> MyModel:
|
|
37
|
+
my_model = my_models.create({"name": "Jane"})
|
|
38
|
+
updated_column_after_create = my_model.updated
|
|
39
|
+
|
|
40
|
+
time.sleep(2)
|
|
41
|
+
|
|
42
|
+
my_model.save({"name": "Susan"})
|
|
43
|
+
|
|
44
|
+
return {
|
|
45
|
+
"updated_column_after_create": updated_column_after_create.isoformat(),
|
|
46
|
+
"updated_column_at_end": my_model.updated.isoformat(),
|
|
47
|
+
"difference_in_seconds": (my_model.updated - updated_column_after_create).total_seconds(),
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
cli = clearskies.contexts.Cli(clearskies.endpoints.Callable(test_updated), classes=[MyModel])
|
|
52
|
+
cli()
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
And when invoked:
|
|
56
|
+
|
|
57
|
+
```bash
|
|
58
|
+
$ ./test.py | jq
|
|
59
|
+
{
|
|
60
|
+
"status": "success",
|
|
61
|
+
"error": "",
|
|
62
|
+
"data": {
|
|
63
|
+
"updated_column_after_create": "2025-05-18T19:28:46+00:00",
|
|
64
|
+
"updated_column_at_end": "2025-05-18T19:28:48+00:00",
|
|
65
|
+
"difference_in_seconds": 2.0
|
|
66
|
+
},
|
|
67
|
+
"pagination": {},
|
|
68
|
+
"input_errors": {}
|
|
69
|
+
}
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
Note that the `updated` column was set both when the record was first created and when it was updated,
|
|
73
|
+
so there is a two second difference between them (since we slept for two seconds).
|
|
74
|
+
|
|
75
|
+
"""
|
|
76
|
+
|
|
77
|
+
"""
|
|
78
|
+
Created fields are never writeable because they always set the created time automatically.
|
|
79
|
+
"""
|
|
80
|
+
is_writeable = configs.Boolean(default=False)
|
|
81
|
+
_descriptor_config_map = None
|
|
82
|
+
|
|
83
|
+
now = inject.Now()
|
|
84
|
+
|
|
85
|
+
@decorators.parameters_to_properties
|
|
86
|
+
def __init__(
|
|
87
|
+
self,
|
|
88
|
+
in_utc: bool = True,
|
|
89
|
+
date_format: str = "%Y-%m-%d %H:%M:%S",
|
|
90
|
+
backend_default: str = "0000-00-00 00:00:00",
|
|
91
|
+
is_readable: bool = True,
|
|
92
|
+
is_searchable: bool = True,
|
|
93
|
+
is_temporary: bool = False,
|
|
94
|
+
on_change_pre_save: typing.action | list[typing.action] = [],
|
|
95
|
+
on_change_post_save: typing.action | list[typing.action] = [],
|
|
96
|
+
on_change_save_finished: typing.action | list[typing.action] = [],
|
|
97
|
+
):
|
|
98
|
+
pass
|
|
99
|
+
|
|
100
|
+
def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
101
|
+
now = self.now
|
|
102
|
+
if self.timezone_aware:
|
|
103
|
+
now = now.astimezone(self.timezone)
|
|
104
|
+
data = {**data, self.name: now}
|
|
105
|
+
if self.on_change_pre_save:
|
|
106
|
+
data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
|
|
107
|
+
return data
|
|
@@ -0,0 +1,83 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from clearskies import configs, decorators, di
|
|
6
|
+
from clearskies.columns.string import String
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from clearskies import Model, typing
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Uuid(String):
|
|
13
|
+
"""
|
|
14
|
+
Populates the column with a UUID upon record creation.
|
|
15
|
+
|
|
16
|
+
This column really just has a very specific purpose: ids!
|
|
17
|
+
|
|
18
|
+
When used, it will automatically populate the column with a random UUID upon record creation.
|
|
19
|
+
It is not a writeable column, which means that you cannot expose it for write operations via an endpoint.
|
|
20
|
+
|
|
21
|
+
```python
|
|
22
|
+
import clearskies
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class MyModel(clearskies.Model):
|
|
26
|
+
backend = clearskies.backends.MemoryBackend()
|
|
27
|
+
id_column_name = "id"
|
|
28
|
+
|
|
29
|
+
id = clearskies.columns.Uuid()
|
|
30
|
+
name = clearskies.columns.String()
|
|
31
|
+
|
|
32
|
+
|
|
33
|
+
wsgi = clearskies.contexts.WsgiRef(
|
|
34
|
+
clearskies.endpoints.Create(
|
|
35
|
+
MyModel,
|
|
36
|
+
writeable_column_names=["name"],
|
|
37
|
+
readable_column_names=["id", "name"],
|
|
38
|
+
),
|
|
39
|
+
)
|
|
40
|
+
wsgi()
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
and when invoked:
|
|
44
|
+
|
|
45
|
+
```bash
|
|
46
|
+
$ curl http://localhost:8080 -d '{"name": "John Doe"}' | jq
|
|
47
|
+
{
|
|
48
|
+
"status": "success",
|
|
49
|
+
"error": "",
|
|
50
|
+
"data": {
|
|
51
|
+
"id": "d4f23106-b48a-4dc5-9bf6-df61f6ca54f7",
|
|
52
|
+
"name": "John Doe"
|
|
53
|
+
},
|
|
54
|
+
"pagination": {},
|
|
55
|
+
"input_errors": {}
|
|
56
|
+
}
|
|
57
|
+
```
|
|
58
|
+
"""
|
|
59
|
+
|
|
60
|
+
is_writeable = configs.Boolean(default=False)
|
|
61
|
+
_descriptor_config_map = None
|
|
62
|
+
|
|
63
|
+
uuid = di.inject.Uuid()
|
|
64
|
+
|
|
65
|
+
@decorators.parameters_to_properties
|
|
66
|
+
def __init__(
|
|
67
|
+
self,
|
|
68
|
+
is_readable: bool = True,
|
|
69
|
+
is_searchable: bool = True,
|
|
70
|
+
is_temporary: bool = False,
|
|
71
|
+
on_change_pre_save: typing.action | list[typing.action] = [],
|
|
72
|
+
on_change_post_save: typing.action | list[typing.action] = [],
|
|
73
|
+
on_change_save_finished: typing.action | list[typing.action] = [],
|
|
74
|
+
):
|
|
75
|
+
pass
|
|
76
|
+
|
|
77
|
+
def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
78
|
+
if model:
|
|
79
|
+
return data
|
|
80
|
+
data = {**data, self.name: str(self.uuid.uuid4())}
|
|
81
|
+
if self.on_change_pre_save:
|
|
82
|
+
data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
|
|
83
|
+
return data
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# About
|
|
2
|
+
|
|
3
|
+
There are all sorts of things in clearskies that need to be configured - handlers, columns, models, etc... `configurable.Configurable` works together with the config classes to make this happen. The idea is that something that needs to be configured extends `Configurable` and then declares configs as properties. A simple example:
|
|
4
|
+
|
|
5
|
+
```python
|
|
6
|
+
class ConfigurableThing(configurable.Configurable):
|
|
7
|
+
my_name = config.String(required=True)
|
|
8
|
+
is_required = config.Boolean(default=False)
|
|
9
|
+
some_option = config.Select(allowed_values=["option 1", "option 2", "option 3"])
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
We've declared three configuration options for our `ConfigurableThing` class:
|
|
13
|
+
|
|
14
|
+
1. `my_name` which is a string and must be set
|
|
15
|
+
2. `is_required` which is a boolean and defaults to `False`
|
|
16
|
+
3. `some_option` which is a string and must be one of `[None, "option 1", "option 2", "option 3"]`
|
|
17
|
+
|
|
18
|
+
However, our example above is missing one important thing: actually setting these values. They act like standard descriptors, so with just the above code you could:
|
|
19
|
+
|
|
20
|
+
```python
|
|
21
|
+
configruable_thing = ConfigurableThing()
|
|
22
|
+
configruable_thing.my_name = "Jane Doe"
|
|
23
|
+
configruable_thing.is_required = True
|
|
24
|
+
configruable_thing.some_option = "option 2"
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
Typically though you need a well defined way to set these values **AND** the class must call `super().finalize_and_validate_configuration()` once the configuration is set. This is because many of the validations are only possible after all the configs are set, so the configurable class treats the process of setting the configuration as a one-time, monolithic process: you set the configs, validate everything, and then use the config. It's *NOT* the goal to continually change the configuration for an object after creation. The simplest way to do this would be in the constructor:
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
class ConfigurableThing(configurable.Configurable):
|
|
31
|
+
my_name = config.String(required=True)
|
|
32
|
+
is_required = config.Boolean(default=False)
|
|
33
|
+
some_option = config.Select(allowed_values=["option 1", "option 2", "option 3"])
|
|
34
|
+
|
|
35
|
+
def __init__(self,
|
|
36
|
+
my_name: str,
|
|
37
|
+
is_required: bool=False,
|
|
38
|
+
some_option: str=None,
|
|
39
|
+
):
|
|
40
|
+
self.my_name = my_name
|
|
41
|
+
self.is_required = is_required
|
|
42
|
+
self.some_option = some_option
|
|
43
|
+
|
|
44
|
+
super().finalize_and_validate_configuration()
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
However, this doesn't always work because your class may be constructed via the dependency injection system. In this case, the constructor must be reserved for injecting the necessary dependencies. In addition, the object won't be constructed directly via code, so it's not possible to specify the configuration options there. In this case, config values can be shifted to the `configure` method and you can use a binding config:
|
|
48
|
+
|
|
49
|
+
```python
|
|
50
|
+
class ConfigurableThing(configurable.Configurable):
|
|
51
|
+
my_name = config.String(required=True)
|
|
52
|
+
is_required = config.Boolean(default=False)
|
|
53
|
+
some_option = config.Select(allowed_values=["option 1", "option 2", "option 3"])
|
|
54
|
+
|
|
55
|
+
def __init__(self, some_dependency, other_dependency):
|
|
56
|
+
self.some_dependency = some_dependency
|
|
57
|
+
self.other_dependency = other_dependency
|
|
58
|
+
|
|
59
|
+
def configure(self,
|
|
60
|
+
my_name: str,
|
|
61
|
+
is_required: bool=False,
|
|
62
|
+
some_option: str=None,
|
|
63
|
+
):
|
|
64
|
+
self.my_name = my_name
|
|
65
|
+
self.is_required = is_required
|
|
66
|
+
self.some_option = some_option
|
|
67
|
+
|
|
68
|
+
super().finalize_and_validate_configuration()
|
|
69
|
+
|
|
70
|
+
context = clearskies.contexts.cli(
|
|
71
|
+
SomeApplication,
|
|
72
|
+
bindings={
|
|
73
|
+
"configurable_thing": clearskies.bindings.Binding(ConfigurableThing, my_name="hey", is_required=Flase, some_option="option 2"),
|
|
74
|
+
}
|
|
75
|
+
)
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
Note that we've lost our strong typing when creating the binding, but that can be fixed by extending the binding config:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
class ConfigurableThing:
|
|
82
|
+
""" See above """
|
|
83
|
+
|
|
84
|
+
class ConfigurableThingBinding(clearskies.bindings.Binding):
|
|
85
|
+
def __init__(
|
|
86
|
+
self,
|
|
87
|
+
my_name: str,
|
|
88
|
+
is_required: bool=False,
|
|
89
|
+
some_option: str=None,
|
|
90
|
+
):
|
|
91
|
+
self.object_class = ConfigurableThing
|
|
92
|
+
self.args = [my_name]
|
|
93
|
+
self.kwargs = {"is_required": is_required, "some_option": some_option}
|
|
94
|
+
|
|
95
|
+
context = clearskies.contexts.cli(
|
|
96
|
+
SomeApplication,
|
|
97
|
+
bindings={
|
|
98
|
+
"configurable_thing": ConfigurableThingBinding("hey", some_option="option 3")
|
|
99
|
+
}
|
|
100
|
+
)
|
|
101
|
+
```
|
|
102
|
+
|
|
103
|
+
The primary example of classes that implement this pattern are the column config classes (`clearskies.columns.*`, but excluding `clearskies.columns.implementors`). In this case the config and implementation are completely separated, so the configuration is set in the constructor instead of a separate `configure` method.
|
|
104
|
+
|
|
105
|
+
Validators, actions, and handlers also use the above config pattern, but those use the `Binding` pattern and so make use of a `configure` method.
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""
|
|
2
|
+
This module helps classes declare their configuration parameters via properties.
|
|
3
|
+
|
|
4
|
+
To use it, the class needs to include the clearskies.configs.Confirgurable in its parent chain and
|
|
5
|
+
then create properties as needed using the various classes in the clearskies.configs module. Each
|
|
6
|
+
class represents a specific "kind" of configuration, has typing declared to help while writing code,
|
|
7
|
+
and runtime checks to verify configs while the application is (preferably) booting.
|
|
8
|
+
|
|
9
|
+
Each config accepts a `required` and `default` kwarg to assist with validation/construction of
|
|
10
|
+
the configuration
|
|
11
|
+
|
|
12
|
+
Data is stored in the `_config` property on the instance.
|
|
13
|
+
|
|
14
|
+
Usage:
|
|
15
|
+
|
|
16
|
+
```python
|
|
17
|
+
from clearskies import configs
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
class MyConfigurableClass(configs.Configurable):
|
|
21
|
+
name = configs.String()
|
|
22
|
+
age = configs.Integer(required=True)
|
|
23
|
+
property_with_default = configs.String(default="some value")
|
|
24
|
+
|
|
25
|
+
def __init__(self, name, age, optional=None):
|
|
26
|
+
self.name = name
|
|
27
|
+
self.age = age
|
|
28
|
+
|
|
29
|
+
# always call this after saving the confiuration values to the properties.
|
|
30
|
+
# It will fill in default values for any properties that have a default and
|
|
31
|
+
# are none, and it will raise a ValueError if there is a required property
|
|
32
|
+
# that does not have a value.
|
|
33
|
+
self.finalize_and_validate_configuration()
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
configured_thingie = MyConfigurableClass("Bob", 18)
|
|
37
|
+
print(configured_thingie.age) # prints: 18
|
|
38
|
+
print(configured_thingie.property_with_default) # prints: some value
|
|
39
|
+
|
|
40
|
+
invalid_thingie = MyConfigurableClass(18, 20) # raises a TypeError
|
|
41
|
+
|
|
42
|
+
also_invalid = MyConfigurableClass("", 18) # raises a ValueError
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
Finally, parameters_to_properties is a decorator that will take any parameters passed into the
|
|
46
|
+
decorated function and assign them as instance properties. You can use this to skip some code,
|
|
47
|
+
especially if you have a lot of configuration parameters. In the above example, you could simplify
|
|
48
|
+
it as:
|
|
49
|
+
|
|
50
|
+
```python
|
|
51
|
+
from clearskies import configs
|
|
52
|
+
|
|
53
|
+
|
|
54
|
+
class MyConfigurableClass(configs.Configurable):
|
|
55
|
+
name = configs.String()
|
|
56
|
+
age = configs.Integer(required=True)
|
|
57
|
+
property_with_default = configs.String(default="some value")
|
|
58
|
+
|
|
59
|
+
@clearskies.decorators()
|
|
60
|
+
def __init__(self, name: str, age: int, optional: string = None):
|
|
61
|
+
self.finalize_and_validate_configuration()
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
"""
|
|
65
|
+
|
|
66
|
+
from .actions import Actions
|
|
67
|
+
from .any import Any
|
|
68
|
+
from .any_dict import AnyDict
|
|
69
|
+
from .any_dict_or_callable import AnyDictOrCallable
|
|
70
|
+
from .authentication import Authentication
|
|
71
|
+
from .authorization import Authorization
|
|
72
|
+
from .boolean import Boolean
|
|
73
|
+
from .boolean_or_callable import BooleanOrCallable
|
|
74
|
+
from .callable_config import Callable
|
|
75
|
+
from .columns import Columns
|
|
76
|
+
from .conditions import Conditions
|
|
77
|
+
from .config import Config
|
|
78
|
+
from .datetime import Datetime
|
|
79
|
+
from .datetime_or_callable import DatetimeOrCallable
|
|
80
|
+
from .email import Email
|
|
81
|
+
from .email_list import EmailList
|
|
82
|
+
from .email_list_or_callable import EmailListOrCallable
|
|
83
|
+
from .email_or_email_list_or_callable import EmailOrEmailListOrCallable
|
|
84
|
+
from .endpoint import Endpoint
|
|
85
|
+
from .endpoint_list import EndpointList
|
|
86
|
+
from .float import Float
|
|
87
|
+
from .float_or_callable import FloatOrCallable
|
|
88
|
+
from .headers import Headers
|
|
89
|
+
from .integer import Integer
|
|
90
|
+
from .integer_or_callable import IntegerOrCallable
|
|
91
|
+
from .joins import Joins
|
|
92
|
+
from .list_any_dict import ListAnyDict
|
|
93
|
+
from .list_any_dict_or_callable import ListAnyDictOrCallable
|
|
94
|
+
from .model_class import ModelClass
|
|
95
|
+
from .model_column import ModelColumn
|
|
96
|
+
from .model_columns import ModelColumns
|
|
97
|
+
from .model_destination_name import ModelDestinationName
|
|
98
|
+
from .model_to_id_column import ModelToIdColumn
|
|
99
|
+
from .readable_model_column import ReadableModelColumn
|
|
100
|
+
from .readable_model_columns import ReadableModelColumns
|
|
101
|
+
from .schema import Schema
|
|
102
|
+
from .searchable_model_columns import SearchableModelColumns
|
|
103
|
+
from .security_headers import SecurityHeaders
|
|
104
|
+
from .select import Select
|
|
105
|
+
from .select_list import SelectList
|
|
106
|
+
from .string import String
|
|
107
|
+
from .string_dict import StringDict
|
|
108
|
+
from .string_list import StringList
|
|
109
|
+
from .string_list_or_callable import StringListOrCallable
|
|
110
|
+
from .string_or_callable import StringOrCallable
|
|
111
|
+
from .timedelta import Timedelta
|
|
112
|
+
from .timezone import Timezone
|
|
113
|
+
from .url import Url
|
|
114
|
+
from .validators import Validators
|
|
115
|
+
from .writeable_model_column import WriteableModelColumn
|
|
116
|
+
from .writeable_model_columns import WriteableModelColumns
|
|
117
|
+
|
|
118
|
+
__all__ = [
|
|
119
|
+
"Actions",
|
|
120
|
+
"Any",
|
|
121
|
+
"AnyDict",
|
|
122
|
+
"AnyDictOrCallable",
|
|
123
|
+
"Authentication",
|
|
124
|
+
"Authorization",
|
|
125
|
+
"Boolean",
|
|
126
|
+
"BooleanOrCallable",
|
|
127
|
+
"Callable",
|
|
128
|
+
"Columns",
|
|
129
|
+
"Conditions",
|
|
130
|
+
"Config",
|
|
131
|
+
"Datetime",
|
|
132
|
+
"DatetimeOrCallable",
|
|
133
|
+
"Email",
|
|
134
|
+
"EmailList",
|
|
135
|
+
"EmailListOrCallable",
|
|
136
|
+
"EmailOrEmailListOrCallable",
|
|
137
|
+
"Endpoint",
|
|
138
|
+
"EndpointList",
|
|
139
|
+
"Float",
|
|
140
|
+
"FloatOrCallable",
|
|
141
|
+
"Headers",
|
|
142
|
+
"Joins",
|
|
143
|
+
"Integer",
|
|
144
|
+
"IntegerOrCallable",
|
|
145
|
+
"ListAnyDict",
|
|
146
|
+
"ListAnyDictOrCallable",
|
|
147
|
+
"ModelClass",
|
|
148
|
+
"ModelColumn",
|
|
149
|
+
"ModelColumns",
|
|
150
|
+
"ModelToIdColumn",
|
|
151
|
+
"ModelDestinationName",
|
|
152
|
+
"ReadableModelColumn",
|
|
153
|
+
"ReadableModelColumns",
|
|
154
|
+
"Schema",
|
|
155
|
+
"SearchableModelColumns",
|
|
156
|
+
"SecurityHeaders",
|
|
157
|
+
"Select",
|
|
158
|
+
"SelectList",
|
|
159
|
+
"String",
|
|
160
|
+
"StringDict",
|
|
161
|
+
"StringList",
|
|
162
|
+
"StringListOrCallable",
|
|
163
|
+
"StringOrCallable",
|
|
164
|
+
"Timedelta",
|
|
165
|
+
"Timezone",
|
|
166
|
+
"Url",
|
|
167
|
+
"Validators",
|
|
168
|
+
"WriteableModelColumn",
|
|
169
|
+
"WriteableModelColumns",
|
|
170
|
+
]
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from clearskies import action
|
|
6
|
+
from clearskies.configs import config
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from clearskies import typing
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Actions(config.Config):
|
|
13
|
+
"""
|
|
14
|
+
Action config.
|
|
15
|
+
|
|
16
|
+
A config that accepts various things that are accepted as actions in model lifecycle hooks:
|
|
17
|
+
|
|
18
|
+
1. A callable (which should accept `model` as a parameter)
|
|
19
|
+
2. An instance of clearskies.actions.Action
|
|
20
|
+
3. A list containing any combination of the above
|
|
21
|
+
|
|
22
|
+
Incoming values are normalized to a list so that a list always comes out even if a non-list is provided.
|
|
23
|
+
"""
|
|
24
|
+
|
|
25
|
+
def __set__(self, instance, value: typing.action | list[typing.action]):
|
|
26
|
+
if not isinstance(value, list):
|
|
27
|
+
value = [value]
|
|
28
|
+
|
|
29
|
+
for index, item in enumerate(value):
|
|
30
|
+
if callable(item) or isinstance(item, action.Action):
|
|
31
|
+
continue
|
|
32
|
+
|
|
33
|
+
error_prefix = self._error_prefix(instance)
|
|
34
|
+
raise TypeError(
|
|
35
|
+
f"{error_prefix} attempt to set a value of type '{item.__class__.__name__}' for item #{index + 1} when a callable or Action is required"
|
|
36
|
+
)
|
|
37
|
+
|
|
38
|
+
instance._set_config(self, [*value])
|
|
39
|
+
|
|
40
|
+
def __get__(self, instance, parent) -> list[typing.action]:
|
|
41
|
+
if not instance:
|
|
42
|
+
return self # type: ignore
|
|
43
|
+
return instance._get_config(self)
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Any as AnyType
|
|
4
|
+
|
|
5
|
+
from clearskies.configs import config
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
class Any(config.Config):
|
|
9
|
+
def __set__(self, instance, value: AnyType):
|
|
10
|
+
instance._set_config(self, value)
|
|
11
|
+
|
|
12
|
+
def __get__(self, instance, parent) -> AnyType:
|
|
13
|
+
if not instance:
|
|
14
|
+
return self # type: ignore
|
|
15
|
+
return instance._get_config(self)
|