clear-skies 2.0.27__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.
Potentially problematic release.
This version of clear-skies might be problematic. Click here for more details.
- clear_skies-2.0.27.dist-info/METADATA +78 -0
- clear_skies-2.0.27.dist-info/RECORD +270 -0
- clear_skies-2.0.27.dist-info/WHEEL +4 -0
- clear_skies-2.0.27.dist-info/licenses/LICENSE +7 -0
- clearskies/__init__.py +69 -0
- clearskies/action.py +7 -0
- clearskies/authentication/__init__.py +15 -0
- clearskies/authentication/authentication.py +44 -0
- clearskies/authentication/authorization.py +23 -0
- clearskies/authentication/authorization_pass_through.py +22 -0
- clearskies/authentication/jwks.py +165 -0
- clearskies/authentication/public.py +5 -0
- clearskies/authentication/secret_bearer.py +551 -0
- clearskies/autodoc/__init__.py +8 -0
- clearskies/autodoc/formats/__init__.py +5 -0
- clearskies/autodoc/formats/oai3_json/__init__.py +7 -0
- clearskies/autodoc/formats/oai3_json/oai3_json.py +87 -0
- clearskies/autodoc/formats/oai3_json/oai3_schema_resolver.py +15 -0
- clearskies/autodoc/formats/oai3_json/parameter.py +35 -0
- clearskies/autodoc/formats/oai3_json/request.py +68 -0
- clearskies/autodoc/formats/oai3_json/response.py +28 -0
- clearskies/autodoc/formats/oai3_json/schema/__init__.py +11 -0
- clearskies/autodoc/formats/oai3_json/schema/array.py +9 -0
- clearskies/autodoc/formats/oai3_json/schema/default.py +13 -0
- clearskies/autodoc/formats/oai3_json/schema/enum.py +7 -0
- clearskies/autodoc/formats/oai3_json/schema/object.py +35 -0
- clearskies/autodoc/formats/oai3_json/test.json +1985 -0
- clearskies/autodoc/py.typed +0 -0
- clearskies/autodoc/request/__init__.py +15 -0
- clearskies/autodoc/request/header.py +6 -0
- clearskies/autodoc/request/json_body.py +6 -0
- clearskies/autodoc/request/parameter.py +8 -0
- clearskies/autodoc/request/request.py +47 -0
- clearskies/autodoc/request/url_parameter.py +6 -0
- clearskies/autodoc/request/url_path.py +6 -0
- clearskies/autodoc/response/__init__.py +5 -0
- clearskies/autodoc/response/response.py +9 -0
- clearskies/autodoc/schema/__init__.py +31 -0
- clearskies/autodoc/schema/array.py +10 -0
- clearskies/autodoc/schema/base64.py +8 -0
- clearskies/autodoc/schema/boolean.py +5 -0
- clearskies/autodoc/schema/date.py +5 -0
- clearskies/autodoc/schema/datetime.py +5 -0
- clearskies/autodoc/schema/double.py +5 -0
- clearskies/autodoc/schema/enum.py +17 -0
- clearskies/autodoc/schema/integer.py +6 -0
- clearskies/autodoc/schema/long.py +5 -0
- clearskies/autodoc/schema/number.py +6 -0
- clearskies/autodoc/schema/object.py +13 -0
- clearskies/autodoc/schema/password.py +5 -0
- clearskies/autodoc/schema/schema.py +11 -0
- clearskies/autodoc/schema/string.py +5 -0
- clearskies/backends/__init__.py +67 -0
- clearskies/backends/api_backend.py +1194 -0
- clearskies/backends/backend.py +137 -0
- clearskies/backends/cursor_backend.py +339 -0
- clearskies/backends/graphql_backend.py +977 -0
- clearskies/backends/memory_backend.py +794 -0
- clearskies/backends/secrets_backend.py +100 -0
- clearskies/clients/__init__.py +5 -0
- clearskies/clients/graphql_client.py +182 -0
- 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 +145 -0
- clearskies/columns/belongs_to_self.py +109 -0
- clearskies/columns/boolean.py +110 -0
- clearskies/columns/category_tree.py +274 -0
- clearskies/columns/category_tree_ancestors.py +51 -0
- clearskies/columns/category_tree_children.py +125 -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 +552 -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 +281 -0
- clearskies/columns/many_to_many_models.py +163 -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 +11 -0
- clearskies/contexts/cli.py +130 -0
- clearskies/contexts/context.py +99 -0
- clearskies/contexts/wsgi.py +79 -0
- clearskies/contexts/wsgi_ref.py +87 -0
- clearskies/cursors/__init__.py +10 -0
- clearskies/cursors/cursor.py +161 -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 +15 -0
- clearskies/di/additional_config.py +130 -0
- clearskies/di/additional_config_auto_import.py +17 -0
- clearskies/di/di.py +948 -0
- clearskies/di/inject/__init__.py +25 -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/logger.py +16 -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 +106 -0
- clearskies/exceptions/__init__.py +19 -0
- clearskies/exceptions/authentication.py +2 -0
- clearskies/exceptions/authorization.py +2 -0
- clearskies/exceptions/client_error.py +2 -0
- clearskies/exceptions/input_errors.py +4 -0
- clearskies/exceptions/missing_dependency.py +2 -0
- clearskies/exceptions/moved_permanently.py +3 -0
- clearskies/exceptions/moved_temporarily.py +3 -0
- clearskies/exceptions/not_found.py +2 -0
- clearskies/functional/__init__.py +7 -0
- clearskies/functional/json.py +47 -0
- clearskies/functional/routing.py +92 -0
- clearskies/functional/string.py +112 -0
- clearskies/functional/validations.py +76 -0
- clearskies/input_outputs/__init__.py +13 -0
- clearskies/input_outputs/cli.py +157 -0
- clearskies/input_outputs/exceptions/__init__.py +7 -0
- clearskies/input_outputs/exceptions/cli_input_error.py +2 -0
- clearskies/input_outputs/exceptions/cli_not_found.py +2 -0
- clearskies/input_outputs/headers.py +54 -0
- clearskies/input_outputs/input_output.py +116 -0
- clearskies/input_outputs/programmatic.py +62 -0
- clearskies/input_outputs/py.typed +0 -0
- clearskies/input_outputs/wsgi.py +80 -0
- clearskies/loggable.py +19 -0
- clearskies/model.py +2039 -0
- clearskies/py.typed +0 -0
- clearskies/query/__init__.py +12 -0
- clearskies/query/condition.py +228 -0
- clearskies/query/join.py +136 -0
- clearskies/query/query.py +195 -0
- clearskies/query/sort.py +27 -0
- clearskies/schema.py +82 -0
- clearskies/secrets/__init__.py +7 -0
- clearskies/secrets/additional_configs/__init__.py +32 -0
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer.py +61 -0
- clearskies/secrets/additional_configs/mysql_connection_dynamic_producer_via_ssh_cert_bastion.py +160 -0
- clearskies/secrets/akeyless.py +507 -0
- clearskies/secrets/exceptions/__init__.py +7 -0
- clearskies/secrets/exceptions/not_found_error.py +2 -0
- clearskies/secrets/exceptions/permissions_error.py +2 -0
- clearskies/secrets/secrets.py +39 -0
- clearskies/security_header.py +17 -0
- clearskies/security_headers/__init__.py +11 -0
- clearskies/security_headers/cache_control.py +68 -0
- clearskies/security_headers/cors.py +51 -0
- clearskies/security_headers/csp.py +95 -0
- clearskies/security_headers/hsts.py +23 -0
- clearskies/security_headers/x_content_type_options.py +0 -0
- clearskies/security_headers/x_frame_options.py +0 -0
- 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/validators/required.py +32 -0
- clearskies/validators/timedelta.py +58 -0
- clearskies/validators/unique.py +28 -0
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
from .audit import Audit
|
|
2
|
+
from .belongs_to_id import BelongsToId
|
|
3
|
+
from .belongs_to_model import BelongsToModel
|
|
4
|
+
from .belongs_to_self import BelongsToSelf
|
|
5
|
+
from .boolean import Boolean
|
|
6
|
+
from .category_tree import CategoryTree
|
|
7
|
+
from .category_tree_ancestors import CategoryTreeAncestors
|
|
8
|
+
from .category_tree_children import CategoryTreeChildren
|
|
9
|
+
from .category_tree_descendants import CategoryTreeDescendants
|
|
10
|
+
from .created import Created
|
|
11
|
+
from .created_by_authorization_data import CreatedByAuthorizationData
|
|
12
|
+
from .created_by_header import CreatedByHeader
|
|
13
|
+
from .created_by_ip import CreatedByIp
|
|
14
|
+
from .created_by_routing_data import CreatedByRoutingData
|
|
15
|
+
from .created_by_user_agent import CreatedByUserAgent
|
|
16
|
+
from .date import Date
|
|
17
|
+
from .datetime import Datetime
|
|
18
|
+
from .email import Email
|
|
19
|
+
from .float import Float
|
|
20
|
+
from .has_many import HasMany
|
|
21
|
+
from .has_many_self import HasManySelf
|
|
22
|
+
from .has_one import HasOne
|
|
23
|
+
from .integer import Integer
|
|
24
|
+
from .json import Json
|
|
25
|
+
from .many_to_many_ids import ManyToManyIds
|
|
26
|
+
from .many_to_many_ids_with_data import ManyToManyIdsWithData
|
|
27
|
+
from .many_to_many_models import ManyToManyModels
|
|
28
|
+
from .many_to_many_pivots import ManyToManyPivots
|
|
29
|
+
from .phone import Phone
|
|
30
|
+
from .select import Select
|
|
31
|
+
from .string import String
|
|
32
|
+
from .timestamp import Timestamp
|
|
33
|
+
from .updated import Updated
|
|
34
|
+
from .uuid import Uuid
|
|
35
|
+
|
|
36
|
+
__all__ = [
|
|
37
|
+
"Audit",
|
|
38
|
+
"BelongsToId",
|
|
39
|
+
"BelongsToModel",
|
|
40
|
+
"BelongsToSelf",
|
|
41
|
+
"Boolean",
|
|
42
|
+
"CategoryTree",
|
|
43
|
+
"CategoryTreeAncestors",
|
|
44
|
+
"CategoryTreeChildren",
|
|
45
|
+
"CategoryTreeDescendants",
|
|
46
|
+
"Created",
|
|
47
|
+
"CreatedByAuthorizationData",
|
|
48
|
+
"CreatedByHeader",
|
|
49
|
+
"CreatedByIp",
|
|
50
|
+
"CreatedByRoutingData",
|
|
51
|
+
"CreatedByUserAgent",
|
|
52
|
+
"Date",
|
|
53
|
+
"Datetime",
|
|
54
|
+
"Email",
|
|
55
|
+
"Float",
|
|
56
|
+
"HasMany",
|
|
57
|
+
"HasManySelf",
|
|
58
|
+
"HasOne",
|
|
59
|
+
"Integer",
|
|
60
|
+
"Json",
|
|
61
|
+
"ManyToManyIds",
|
|
62
|
+
"ManyToManyIdsWithData",
|
|
63
|
+
"ManyToManyModels",
|
|
64
|
+
"ManyToManyPivots",
|
|
65
|
+
"Phone",
|
|
66
|
+
"Select",
|
|
67
|
+
"String",
|
|
68
|
+
"Timestamp",
|
|
69
|
+
"Updated",
|
|
70
|
+
"Uuid",
|
|
71
|
+
]
|
|
@@ -0,0 +1,306 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
|
+
|
|
5
|
+
from clearskies import configs, decorators
|
|
6
|
+
from clearskies.column import Column
|
|
7
|
+
from clearskies.columns.has_many import HasMany
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from clearskies import Model, typing
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
class Audit(HasMany):
|
|
14
|
+
"""
|
|
15
|
+
Enables auditing for a model.
|
|
16
|
+
|
|
17
|
+
Specify the audit class to use and attach this column to your model. Everytime the model is created/updated/deleted,
|
|
18
|
+
the audit class will record the action and the changes. Your audit model must have the following columns:
|
|
19
|
+
|
|
20
|
+
| Name | type |
|
|
21
|
+
|-------------|----------|
|
|
22
|
+
| class_name | str |
|
|
23
|
+
| resource_id | str |
|
|
24
|
+
| action | str |
|
|
25
|
+
| data | json |
|
|
26
|
+
| created_at | created |
|
|
27
|
+
|
|
28
|
+
The names are not currently adjustable.
|
|
29
|
+
|
|
30
|
+
1. Class is a string that records the name of the class that the action happened for. This allows you to use
|
|
31
|
+
the same audit class for multiple, different, resources.
|
|
32
|
+
2. resource_id is the id of the record which the audit entry is for.
|
|
33
|
+
3. Action is the actual action taken (create/update/delete)
|
|
34
|
+
4. Data is a serialized record of what columns in the record were changed (both their previous and new values)
|
|
35
|
+
5. The time the audit record was created
|
|
36
|
+
|
|
37
|
+
Here's an example:
|
|
38
|
+
|
|
39
|
+
```
|
|
40
|
+
#!/usr/bin/env python
|
|
41
|
+
|
|
42
|
+
import clearskies
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
class PersonHistory(clearskies.Model):
|
|
46
|
+
id_column_name = "id"
|
|
47
|
+
backend = clearskies.backends.MemoryBackend()
|
|
48
|
+
|
|
49
|
+
id = clearskies.columns.Uuid()
|
|
50
|
+
class_name = clearskies.columns.String()
|
|
51
|
+
resource_id = clearskies.columns.String()
|
|
52
|
+
action = clearskies.columns.String()
|
|
53
|
+
data = clearskies.columns.Json()
|
|
54
|
+
created_at = clearskies.columns.Created(date_format="%Y-%m-%d %H:%M:%S.%f")
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class Person(clearskies.Model):
|
|
58
|
+
id_column_name = "id"
|
|
59
|
+
backend = clearskies.backends.MemoryBackend()
|
|
60
|
+
|
|
61
|
+
id = clearskies.columns.Uuid()
|
|
62
|
+
name = clearskies.columns.String()
|
|
63
|
+
age = clearskies.columns.Integer()
|
|
64
|
+
history = clearskies.columns.Audit(audit_model_class=PersonHistory)
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
def test_audit(persons: Person):
|
|
68
|
+
bob = persons.create({"name": "Bob", "age": 30})
|
|
69
|
+
bob.save({"age": 31})
|
|
70
|
+
bob.save({"age": 32})
|
|
71
|
+
bob.delete()
|
|
72
|
+
|
|
73
|
+
return bob.history.sort_by("created_at", "asc")
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
cli = clearskies.contexts.Cli(
|
|
77
|
+
clearskies.endpoints.Callable(
|
|
78
|
+
test_audit,
|
|
79
|
+
model_class=PersonHistory,
|
|
80
|
+
return_records=True,
|
|
81
|
+
readable_column_names=["id", "action", "data", "created_at"],
|
|
82
|
+
),
|
|
83
|
+
classes=[Person, PersonHistory],
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
if __name__ == "__main__":
|
|
87
|
+
cli()
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
And if you invoke this you will get back:
|
|
91
|
+
|
|
92
|
+
```
|
|
93
|
+
{
|
|
94
|
+
"status": "success",
|
|
95
|
+
"error": "",
|
|
96
|
+
"data": [
|
|
97
|
+
{
|
|
98
|
+
"id": "25eae3d9-d64b-4819-9e31-70e1d4d34945",
|
|
99
|
+
"action": "create",
|
|
100
|
+
"data": {"name": "Bob", "age": 30, "id": "145c7cf2-3fc6-41c8-b3b2-f68eb2b08b03"},
|
|
101
|
+
"created_at": "2025-12-04T12:14:42.540108+00:00",
|
|
102
|
+
},
|
|
103
|
+
{
|
|
104
|
+
"id": "c8dea383-ae1d-4e25-a58e-d4ebdf2fb4f9",
|
|
105
|
+
"action": "update",
|
|
106
|
+
"data": {"from": {"age": 30}, "to": {"age": 31}},
|
|
107
|
+
"created_at": "2025-12-04T12:14:42.540384+00:00",
|
|
108
|
+
},
|
|
109
|
+
{
|
|
110
|
+
"id": "5e1f3067-a45a-4463-8a66-3b92d47a8863",
|
|
111
|
+
"action": "update",
|
|
112
|
+
"data": {"from": {"age": 31}, "to": {"age": 32}},
|
|
113
|
+
"created_at": "2025-12-04T12:14:42.540595+00:00",
|
|
114
|
+
},
|
|
115
|
+
{
|
|
116
|
+
"id": "44179d35-9abb-4117-803c-ec87bb58adb5",
|
|
117
|
+
"action": "delete",
|
|
118
|
+
"data": {"name": "Bob", "age": 32, "id": "145c7cf2-3fc6-41c8-b3b2-f68eb2b08b03"},
|
|
119
|
+
"created_at": "2025-12-04T12:14:42.540747+00:00",
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
"pagination": {"number_results": 4, "limit": 0, "next_page": {}},
|
|
123
|
+
"input_errors": {},
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
"""
|
|
128
|
+
|
|
129
|
+
""" The model class for the destination that will store the audit data. """
|
|
130
|
+
audit_model_class = configs.ModelClass(required=True)
|
|
131
|
+
|
|
132
|
+
"""
|
|
133
|
+
A list of columns that shouldn't be copied into the audit record.
|
|
134
|
+
|
|
135
|
+
To be clear, these are columns from the model class that the audit column is attached to.
|
|
136
|
+
If only excluded columns are updated then no audit record will be created.
|
|
137
|
+
"""
|
|
138
|
+
exclude_columns = configs.ModelColumns(default=[])
|
|
139
|
+
|
|
140
|
+
"""
|
|
141
|
+
A list of columns that should be masked when copied into the audit record.
|
|
142
|
+
|
|
143
|
+
With masked columns a generic value is placed in the audit record (e.g. XXXXX) which denotes that
|
|
144
|
+
the column was changed, but it does not record either old or new values.
|
|
145
|
+
"""
|
|
146
|
+
mask_columns = configs.ModelColumns(default=[])
|
|
147
|
+
|
|
148
|
+
""" Columns from the child table that should be included when converting this column to JSON. """
|
|
149
|
+
readable_child_column_names = configs.ReadableModelColumns(
|
|
150
|
+
"audit_model_class", default=["resource_id", "action", "data", "created_at"]
|
|
151
|
+
)
|
|
152
|
+
|
|
153
|
+
"""
|
|
154
|
+
Since this column is always populated automatically, it is never directly writeable.
|
|
155
|
+
"""
|
|
156
|
+
is_writeable = configs.Boolean(default=False)
|
|
157
|
+
is_searchable = configs.Boolean(default=False)
|
|
158
|
+
_descriptor_config_map = None
|
|
159
|
+
_parent_columns: dict[str, Column] | None
|
|
160
|
+
|
|
161
|
+
@decorators.parameters_to_properties
|
|
162
|
+
def __init__(
|
|
163
|
+
self,
|
|
164
|
+
audit_model_class,
|
|
165
|
+
exclude_columns: list[str] = [],
|
|
166
|
+
mask_columns: list[str] = [],
|
|
167
|
+
foreign_column_name: str | None = None,
|
|
168
|
+
readable_child_column_names: list[str] = [],
|
|
169
|
+
where: typing.condition | list[typing.condition] = [],
|
|
170
|
+
default: str | None = None,
|
|
171
|
+
is_readable: bool = True,
|
|
172
|
+
is_temporary: bool = False,
|
|
173
|
+
on_change_pre_save: typing.action | list[typing.action] = [],
|
|
174
|
+
on_change_post_save: typing.action | list[typing.action] = [],
|
|
175
|
+
on_change_save_finished: typing.action | list[typing.action] = [],
|
|
176
|
+
):
|
|
177
|
+
self.child_model_class = self.audit_model_class
|
|
178
|
+
self.foreign_column_name = "resource_id"
|
|
179
|
+
|
|
180
|
+
def save_finished(self, model: Model):
|
|
181
|
+
super().save_finished(model)
|
|
182
|
+
old_data: dict[str, Any] = model._previous_data
|
|
183
|
+
new_data: dict[str, Any] = model.get_raw_data()
|
|
184
|
+
exclude_columns = self.exclude_columns
|
|
185
|
+
mask_columns = self.mask_columns
|
|
186
|
+
model_columns = self.get_model_columns()
|
|
187
|
+
|
|
188
|
+
if not old_data:
|
|
189
|
+
create_data: dict[str, Any] = {}
|
|
190
|
+
for key in new_data.keys():
|
|
191
|
+
if key in self.exclude_columns or key == self.name:
|
|
192
|
+
continue
|
|
193
|
+
if key in model_columns:
|
|
194
|
+
column_data = model_columns[key].to_json(model)
|
|
195
|
+
else:
|
|
196
|
+
column_data = {key: new_data[key]}
|
|
197
|
+
|
|
198
|
+
create_data = {
|
|
199
|
+
**create_data,
|
|
200
|
+
**column_data,
|
|
201
|
+
}
|
|
202
|
+
if key in mask_columns and key in create_data:
|
|
203
|
+
create_data[key] = "****"
|
|
204
|
+
self.record(model, "create", data=create_data)
|
|
205
|
+
return
|
|
206
|
+
|
|
207
|
+
# note that this is fairly simple logic to get started. It's not going to detect changes that happen
|
|
208
|
+
# in other "tables". For instance, disconnecting a record by deleting an entry in a many-to-many relationship
|
|
209
|
+
# won't be picked up by this.
|
|
210
|
+
old_model = model.empty()
|
|
211
|
+
old_model._data = old_data
|
|
212
|
+
from_data: dict[str, Any] = {}
|
|
213
|
+
to_data: dict[str, Any] = {}
|
|
214
|
+
for column, new_value in new_data.items():
|
|
215
|
+
if column in exclude_columns or column not in old_data or column == self.name:
|
|
216
|
+
continue
|
|
217
|
+
if old_data[column] == new_value:
|
|
218
|
+
continue
|
|
219
|
+
from_data = {
|
|
220
|
+
**from_data,
|
|
221
|
+
**(
|
|
222
|
+
model_columns[column].to_json(old_model)
|
|
223
|
+
if column in model_columns
|
|
224
|
+
else {column: old_data.get(column)}
|
|
225
|
+
),
|
|
226
|
+
}
|
|
227
|
+
to_data = {
|
|
228
|
+
**to_data,
|
|
229
|
+
**(
|
|
230
|
+
model_columns[column].to_json(model)
|
|
231
|
+
if column in model_columns
|
|
232
|
+
else {column: model._data.get(column)}
|
|
233
|
+
),
|
|
234
|
+
}
|
|
235
|
+
if column in mask_columns and column in to_data:
|
|
236
|
+
to_data[column] = "****"
|
|
237
|
+
from_data[column] = "****"
|
|
238
|
+
if not from_data and not to_data:
|
|
239
|
+
return
|
|
240
|
+
|
|
241
|
+
self.record(
|
|
242
|
+
model,
|
|
243
|
+
"update",
|
|
244
|
+
data={
|
|
245
|
+
"from": from_data,
|
|
246
|
+
"to": to_data,
|
|
247
|
+
},
|
|
248
|
+
)
|
|
249
|
+
|
|
250
|
+
def post_delete(self, model: Model) -> None:
|
|
251
|
+
super().post_delete(model)
|
|
252
|
+
exclude_columns = self.exclude_columns
|
|
253
|
+
model_columns = self.get_model_columns()
|
|
254
|
+
mask_columns = self.mask_columns
|
|
255
|
+
|
|
256
|
+
final_data: dict[str, Any] = {}
|
|
257
|
+
for key in model._data.keys():
|
|
258
|
+
if key in exclude_columns or key == self.name:
|
|
259
|
+
continue
|
|
260
|
+
final_data = {
|
|
261
|
+
**final_data,
|
|
262
|
+
**(model_columns[key].to_json(model) if key in model_columns else {key: model.get_raw_data().get(key)}),
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
for key in mask_columns:
|
|
266
|
+
if key not in final_data:
|
|
267
|
+
continue
|
|
268
|
+
final_data[key] = "****"
|
|
269
|
+
|
|
270
|
+
self.child_model.create(
|
|
271
|
+
{
|
|
272
|
+
"class_name": self.model_class.__name__,
|
|
273
|
+
"resource_id": getattr(model, self.model_class.id_column_name),
|
|
274
|
+
"action": "delete",
|
|
275
|
+
"data": final_data,
|
|
276
|
+
}
|
|
277
|
+
)
|
|
278
|
+
|
|
279
|
+
@property
|
|
280
|
+
def parent_columns(self) -> dict[str, Column]:
|
|
281
|
+
if self._parent_columns == None:
|
|
282
|
+
self._parent_columns = self.di.build(self.model_class, cache=True).columns()
|
|
283
|
+
return self._parent_columns # type: ignore[return-value]
|
|
284
|
+
|
|
285
|
+
def record(self, model, action, data=None, record_data=None):
|
|
286
|
+
audit_data = {
|
|
287
|
+
"class_name": self.model_class.__name__,
|
|
288
|
+
"resource_id": getattr(model, self.model_class.id_column_name),
|
|
289
|
+
"action": action,
|
|
290
|
+
}
|
|
291
|
+
if data is not None:
|
|
292
|
+
audit_data["data"] = data
|
|
293
|
+
if record_data is not None:
|
|
294
|
+
audit_data = {
|
|
295
|
+
**audit_data,
|
|
296
|
+
**record_data,
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
self.child_model.create(audit_data)
|
|
300
|
+
|
|
301
|
+
def __get__(self, model, cls):
|
|
302
|
+
if model is None:
|
|
303
|
+
self.model_class = cls
|
|
304
|
+
return self # type: ignore
|
|
305
|
+
|
|
306
|
+
return super().__get__(model, cls).where(f"class_name={self.model_class.__name__}")
|