clear-skies 2.0.6__py3-none-any.whl → 2.0.8__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.6.dist-info → clear_skies-2.0.8.dist-info}/METADATA +1 -1
- clear_skies-2.0.8.dist-info/RECORD +252 -0
- clearskies/__init__.py +2 -2
- clearskies/authentication/authentication.py +1 -3
- clearskies/authentication/authorization.py +12 -5
- clearskies/authentication/authorization_pass_through.py +5 -3
- clearskies/authentication/jwks.py +25 -23
- clearskies/authentication/secret_bearer.py +15 -17
- clearskies/autodoc/schema/schema.py +1 -1
- clearskies/backends/api_backend.py +50 -56
- clearskies/backends/backend.py +14 -14
- clearskies/backends/cursor_backend.py +17 -23
- clearskies/backends/memory_backend.py +27 -30
- clearskies/backends/secrets_backend.py +13 -18
- clearskies/column.py +44 -56
- clearskies/columns/audit.py +14 -13
- clearskies/columns/belongs_to_id.py +10 -15
- clearskies/columns/belongs_to_model.py +6 -9
- clearskies/columns/belongs_to_self.py +13 -9
- clearskies/columns/boolean.py +13 -16
- clearskies/columns/category_tree.py +9 -11
- clearskies/columns/category_tree_children.py +2 -3
- clearskies/columns/category_tree_descendants.py +1 -1
- clearskies/columns/created.py +8 -11
- clearskies/columns/created_by_authorization_data.py +7 -9
- clearskies/columns/created_by_header.py +12 -8
- clearskies/columns/created_by_ip.py +6 -8
- clearskies/columns/created_by_routing_data.py +12 -7
- clearskies/columns/created_by_user_agent.py +6 -9
- clearskies/columns/date.py +12 -14
- clearskies/columns/datetime.py +19 -17
- clearskies/columns/email.py +3 -1
- clearskies/columns/float.py +10 -14
- clearskies/columns/has_many.py +8 -10
- clearskies/columns/has_many_self.py +13 -7
- clearskies/columns/has_one.py +2 -0
- clearskies/columns/integer.py +9 -11
- clearskies/columns/json.py +10 -12
- clearskies/columns/many_to_many_ids.py +14 -16
- clearskies/columns/many_to_many_ids_with_data.py +16 -16
- clearskies/columns/many_to_many_models.py +5 -7
- clearskies/columns/many_to_many_pivots.py +3 -5
- clearskies/columns/phone.py +12 -9
- clearskies/columns/select.py +12 -9
- clearskies/columns/string.py +1 -1
- clearskies/columns/timestamp.py +15 -15
- clearskies/columns/updated.py +8 -10
- clearskies/columns/uuid.py +7 -10
- clearskies/configs/any.py +2 -0
- clearskies/configs/any_dict.py +2 -0
- clearskies/configs/any_dict_or_callable.py +2 -0
- clearskies/configs/boolean.py +2 -0
- clearskies/configs/boolean_or_callable.py +2 -0
- clearskies/configs/callable_config.py +2 -0
- clearskies/configs/config.py +2 -0
- clearskies/configs/datetime.py +2 -0
- clearskies/configs/datetime_or_callable.py +2 -0
- clearskies/configs/float.py +2 -0
- clearskies/configs/float_or_callable.py +2 -0
- clearskies/configs/integer.py +2 -0
- clearskies/configs/integer_or_callable.py +2 -0
- clearskies/configs/list_any_dict.py +2 -0
- clearskies/configs/list_any_dict_or_callable.py +2 -0
- clearskies/configs/model_column.py +2 -0
- clearskies/configs/model_columns.py +2 -0
- clearskies/configs/model_destination_name.py +2 -1
- clearskies/configs/model_to_id_column.py +2 -0
- clearskies/configs/readable_model_column.py +2 -0
- clearskies/configs/readable_model_columns.py +2 -0
- clearskies/configs/searchable_model_columns.py +2 -0
- clearskies/configs/select.py +2 -0
- clearskies/configs/select_list.py +2 -0
- clearskies/configs/string.py +2 -0
- clearskies/configs/string_dict.py +2 -0
- clearskies/configs/string_list.py +2 -0
- clearskies/configs/string_list_or_callable.py +2 -0
- clearskies/configs/timedelta.py +2 -0
- clearskies/configs/timezone.py +2 -0
- clearskies/configs/url.py +2 -0
- clearskies/configs/writeable_model_column.py +2 -0
- clearskies/configs/writeable_model_columns.py +2 -0
- clearskies/configurable.py +2 -0
- clearskies/contexts/cli.py +9 -1
- clearskies/contexts/context.py +13 -14
- clearskies/contexts/wsgi.py +12 -10
- clearskies/contexts/wsgi_ref.py +12 -6
- clearskies/decorators.py +1 -1
- clearskies/decorators.pyi +10 -0
- clearskies/di/__init__.py +2 -1
- clearskies/di/di.py +7 -6
- clearskies/di/inject/by_class.py +2 -0
- clearskies/di/inject/by_name.py +2 -0
- clearskies/di/inject/di.py +2 -0
- clearskies/di/inject/environment.py +1 -1
- clearskies/di/inject/now.py +2 -0
- clearskies/di/inject/requests.py +2 -0
- clearskies/di/inject/secrets.py +2 -2
- clearskies/di/inject/utcnow.py +2 -0
- clearskies/di/inject/uuid.py +2 -2
- clearskies/end.py +45 -7
- clearskies/endpoint.py +43 -59
- clearskies/endpoint_group.py +15 -18
- clearskies/endpoints/advanced_search.py +19 -26
- clearskies/endpoints/callable.py +10 -16
- clearskies/endpoints/create.py +6 -10
- clearskies/endpoints/delete.py +5 -11
- clearskies/endpoints/get.py +11 -15
- clearskies/endpoints/health_check.py +9 -11
- clearskies/endpoints/list.py +29 -36
- clearskies/endpoints/restful_api.py +43 -53
- clearskies/endpoints/schema.py +14 -18
- clearskies/endpoints/simple_search.py +5 -12
- clearskies/endpoints/update.py +6 -11
- clearskies/environment.py +2 -0
- clearskies/input_outputs/cli.py +2 -0
- clearskies/input_outputs/headers.py +2 -0
- clearskies/input_outputs/input_output.py +15 -15
- clearskies/input_outputs/programmatic.py +2 -2
- clearskies/input_outputs/wsgi.py +2 -2
- clearskies/model.py +120 -25
- clearskies/query/query.py +1 -4
- clearskies/secrets/__init__.py +2 -1
- clearskies/secrets/akeyless.py +12 -10
- clearskies/secrets/secrets.py +7 -2
- clearskies/security_header.py +4 -2
- clearskies/security_headers/cache_control.py +15 -14
- clearskies/security_headers/cors.py +10 -9
- clearskies/security_headers/csp.py +25 -24
- clearskies/security_headers/hsts.py +6 -5
- clearskies/typing.py +1 -1
- clearskies/validator.py +5 -6
- clearskies/validators/after_column.py +6 -7
- clearskies/validators/before_column.py +2 -0
- clearskies/validators/in_the_future.py +5 -8
- clearskies/validators/in_the_future_at_least.py +2 -0
- clearskies/validators/in_the_future_at_most.py +2 -0
- clearskies/validators/in_the_past.py +5 -8
- clearskies/validators/in_the_past_at_least.py +2 -0
- clearskies/validators/in_the_past_at_most.py +2 -0
- clearskies/validators/maximum_length.py +4 -5
- clearskies/validators/maximum_value.py +4 -4
- clearskies/validators/minimum_length.py +4 -4
- clearskies/validators/minimum_value.py +4 -4
- clearskies/validators/required.py +2 -4
- clearskies/validators/timedelta.py +8 -9
- clearskies/validators/unique.py +2 -3
- clear_skies-2.0.6.dist-info/RECORD +0 -251
- {clear_skies-2.0.6.dist-info → clear_skies-2.0.8.dist-info}/WHEEL +0 -0
- {clear_skies-2.0.6.dist-info → clear_skies-2.0.8.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,15 +1,18 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from functools import cmp_to_key
|
|
3
|
-
from typing import Any, Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
5
|
|
|
5
|
-
import clearskies.model
|
|
6
|
-
import clearskies.query
|
|
7
6
|
from clearskies import functional
|
|
8
7
|
from clearskies.autodoc.schema import Integer as AutoDocInteger
|
|
9
8
|
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
10
9
|
from clearskies.backends.backend import Backend
|
|
11
10
|
from clearskies.di import InjectableProperties, inject
|
|
12
11
|
|
|
12
|
+
if TYPE_CHECKING:
|
|
13
|
+
from clearskies import Model
|
|
14
|
+
from clearskies.query import Condition, Join, Query, Sort
|
|
15
|
+
|
|
13
16
|
|
|
14
17
|
class Null:
|
|
15
18
|
def __lt__(self, other):
|
|
@@ -31,7 +34,7 @@ def gentle_float_conversion(value):
|
|
|
31
34
|
return value
|
|
32
35
|
|
|
33
36
|
|
|
34
|
-
def _sort(row_a: Any, row_b: Any, sorts: list[
|
|
37
|
+
def _sort(row_a: Any, row_b: Any, sorts: list[Sort], default_table_name: str) -> int:
|
|
35
38
|
for sort in sorts:
|
|
36
39
|
# so, if we've done a join then the rows will have data from all joined tables via a dict of dicts.
|
|
37
40
|
# if there wasn't a join then we'll just have the data
|
|
@@ -92,7 +95,7 @@ class MemoryTable:
|
|
|
92
95
|
_id_index: dict[int | str, int] = {}
|
|
93
96
|
id_column_name: str = ""
|
|
94
97
|
_next_id: int = 1
|
|
95
|
-
_model_class: type[
|
|
98
|
+
_model_class: type[Model] = None # type: ignore
|
|
96
99
|
|
|
97
100
|
# here be dragons. This is not a 100% drop-in replacement for the equivalent SQL operators
|
|
98
101
|
# https://codereview.stackexchange.com/questions/259198/in-memory-table-filtering-in-python
|
|
@@ -124,7 +127,7 @@ class MemoryTable:
|
|
|
124
127
|
"in": lambda column, values, null: lambda row: row.get(column, null) in values,
|
|
125
128
|
}
|
|
126
129
|
|
|
127
|
-
def __init__(self, model_class: type[
|
|
130
|
+
def __init__(self, model_class: type[Model]) -> None:
|
|
128
131
|
self._rows = []
|
|
129
132
|
self._id_index = {}
|
|
130
133
|
self.id_column_name = model_class.id_column_name
|
|
@@ -193,13 +196,13 @@ class MemoryTable:
|
|
|
193
196
|
self._rows[index] = None
|
|
194
197
|
return True
|
|
195
198
|
|
|
196
|
-
def count(self, query:
|
|
199
|
+
def count(self, query: Query):
|
|
197
200
|
return len(self.rows(query, query.conditions, filter_only=True))
|
|
198
201
|
|
|
199
202
|
def rows(
|
|
200
203
|
self,
|
|
201
|
-
query:
|
|
202
|
-
conditions: list[
|
|
204
|
+
query: Query,
|
|
205
|
+
conditions: list[Condition],
|
|
203
206
|
filter_only: bool = False,
|
|
204
207
|
next_page_data: dict[str, Any] | None = None,
|
|
205
208
|
):
|
|
@@ -232,7 +235,7 @@ class MemoryTable:
|
|
|
232
235
|
return rows
|
|
233
236
|
|
|
234
237
|
@classmethod
|
|
235
|
-
def _condition_as_filter(cls, condition:
|
|
238
|
+
def _condition_as_filter(cls, condition: Condition) -> Callable:
|
|
236
239
|
column = condition.column_name
|
|
237
240
|
values = condition.values
|
|
238
241
|
return cls._operator_lambda_builders[condition.operator.lower()](column, values, cls.null)
|
|
@@ -469,19 +472,19 @@ class MemoryBackend(Backend, InjectableProperties):
|
|
|
469
472
|
def silent_on_missing_tables(self, silent=True):
|
|
470
473
|
self._silent_on_missing_tables = silent
|
|
471
474
|
|
|
472
|
-
def create_table(self, model_class: type[
|
|
475
|
+
def create_table(self, model_class: type[Model]):
|
|
473
476
|
self.load_default_data()
|
|
474
477
|
table_name = model_class.destination_name()
|
|
475
478
|
if table_name in self.__class__._tables:
|
|
476
479
|
return
|
|
477
480
|
self.__class__._tables[table_name] = MemoryTable(model_class)
|
|
478
481
|
|
|
479
|
-
def has_table(self, model_class: type[
|
|
482
|
+
def has_table(self, model_class: type[Model]) -> bool:
|
|
480
483
|
self.load_default_data()
|
|
481
484
|
table_name = model_class.destination_name()
|
|
482
485
|
return table_name in self.__class__._tables
|
|
483
486
|
|
|
484
|
-
def get_table(self, model_class: type[
|
|
487
|
+
def get_table(self, model_class: type[Model], create_if_missing=False) -> MemoryTable:
|
|
485
488
|
table_name = model_class.destination_name()
|
|
486
489
|
if table_name not in self.__class__._tables:
|
|
487
490
|
if create_if_missing:
|
|
@@ -492,25 +495,23 @@ class MemoryBackend(Backend, InjectableProperties):
|
|
|
492
495
|
)
|
|
493
496
|
return self.__class__._tables[table_name]
|
|
494
497
|
|
|
495
|
-
def create_with_model_class(
|
|
496
|
-
self, data: dict[str, Any], model_class: type[clearskies.model.Model]
|
|
497
|
-
) -> dict[str, Any]:
|
|
498
|
+
def create_with_model_class(self, data: dict[str, Any], model_class: type[Model]) -> dict[str, Any]:
|
|
498
499
|
self.create_table(model_class)
|
|
499
500
|
return self.get_table(model_class).create(data)
|
|
500
501
|
|
|
501
|
-
def update(self, id: int | str, data: dict[str, Any], model:
|
|
502
|
+
def update(self, id: int | str, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
502
503
|
self.create_table(model.__class__)
|
|
503
504
|
return self.get_table(model.__class__).update(id, data)
|
|
504
505
|
|
|
505
|
-
def create(self, data: dict[str, Any], model:
|
|
506
|
+
def create(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
506
507
|
self.create_table(model.__class__)
|
|
507
508
|
return self.get_table(model.__class__).create(data)
|
|
508
509
|
|
|
509
|
-
def delete(self, id: int | str, model:
|
|
510
|
+
def delete(self, id: int | str, model: Model) -> bool:
|
|
510
511
|
self.create_table(model.__class__)
|
|
511
512
|
return self.get_table(model.__class__).delete(id)
|
|
512
513
|
|
|
513
|
-
def count(self, query:
|
|
514
|
+
def count(self, query: Query) -> int:
|
|
514
515
|
self.check_query(query)
|
|
515
516
|
if not self.has_table(query.model_class):
|
|
516
517
|
if self._silent_on_missing_tables:
|
|
@@ -528,9 +529,7 @@ class MemoryBackend(Backend, InjectableProperties):
|
|
|
528
529
|
query.joins = [join for join in query.joins if join.join_type != "LEFT"]
|
|
529
530
|
return len(self.rows_with_joins(query))
|
|
530
531
|
|
|
531
|
-
def records(
|
|
532
|
-
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
533
|
-
) -> list[dict[str, Any]]:
|
|
532
|
+
def records(self, query: Query, next_page_data: dict[str, str | int] | None = None) -> list[dict[str, Any]]:
|
|
534
533
|
self.check_query(query)
|
|
535
534
|
if not self.has_table(query.model_class):
|
|
536
535
|
if self._silent_on_missing_tables:
|
|
@@ -568,7 +567,7 @@ class MemoryBackend(Backend, InjectableProperties):
|
|
|
568
567
|
next_page_data["start"] = start + query.limit
|
|
569
568
|
return rows
|
|
570
569
|
|
|
571
|
-
def rows_with_joins(self, query:
|
|
570
|
+
def rows_with_joins(self, query: Query) -> list[dict[str, Any]]:
|
|
572
571
|
joins = [*query.joins]
|
|
573
572
|
conditions = [*query.conditions]
|
|
574
573
|
# quick sanity check
|
|
@@ -642,15 +641,13 @@ class MemoryBackend(Backend, InjectableProperties):
|
|
|
642
641
|
raise ValueError(f"Cannot return rows for unknown table '{table_name}'")
|
|
643
642
|
return list(filter(None, self.__class__._tables[table_name]._rows))
|
|
644
643
|
|
|
645
|
-
def check_query(self, query:
|
|
644
|
+
def check_query(self, query: Query) -> None:
|
|
646
645
|
if query.group_by:
|
|
647
646
|
raise KeyError(
|
|
648
647
|
f"MemoryBackend does not support group_by clauses in queries. You may be using the wrong backend."
|
|
649
648
|
)
|
|
650
649
|
|
|
651
|
-
def conditions_for_table(
|
|
652
|
-
self, table_name: str, conditions: list[clearskies.query.Condition], is_left=False
|
|
653
|
-
) -> list[clearskies.query.Condition]:
|
|
650
|
+
def conditions_for_table(self, table_name: str, conditions: list[Condition], is_left=False) -> list[Condition]:
|
|
654
651
|
"""
|
|
655
652
|
Return only the conditions for the given table.
|
|
656
653
|
|
|
@@ -667,7 +664,7 @@ class MemoryBackend(Backend, InjectableProperties):
|
|
|
667
664
|
self,
|
|
668
665
|
rows: list[dict[str, Any]],
|
|
669
666
|
join_rows: list[dict[str, Any]],
|
|
670
|
-
join:
|
|
667
|
+
join: Join,
|
|
671
668
|
joined_tables: list[str],
|
|
672
669
|
) -> list[dict[str, Any]]:
|
|
673
670
|
"""
|
|
@@ -1,12 +1,13 @@
|
|
|
1
|
-
from typing import Any, Callable
|
|
1
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
2
2
|
|
|
3
|
-
import clearskies
|
|
4
|
-
from clearskies.autodoc.schema import Integer as AutoDocInteger
|
|
5
3
|
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
6
|
-
from clearskies.autodoc.schema import String as AutoDocString
|
|
7
4
|
from clearskies.backends.backend import Backend
|
|
8
|
-
from clearskies.di import
|
|
9
|
-
from clearskies.
|
|
5
|
+
from clearskies.di import inject
|
|
6
|
+
from clearskies.query import Condition, Query
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from clearskies import Model
|
|
10
|
+
from clearskies.authentication import Authentication
|
|
10
11
|
|
|
11
12
|
|
|
12
13
|
class SecretsBackend(Backend):
|
|
@@ -31,11 +32,11 @@ class SecretsBackend(Backend):
|
|
|
31
32
|
def __init__(self):
|
|
32
33
|
pass
|
|
33
34
|
|
|
34
|
-
def check_query(self, query:
|
|
35
|
+
def check_query(self, query: Query) -> None:
|
|
35
36
|
if not query.conditions:
|
|
36
37
|
raise KeyError(f"You must search by an id when using the secrets backend.")
|
|
37
38
|
|
|
38
|
-
def update(self, id: str, data: dict[str, Any], model:
|
|
39
|
+
def update(self, id: str, data: dict[str, Any], model: Model) -> dict[str, Any]: # type: ignore[override]
|
|
39
40
|
"""Update the record with the given id with the information from the data dictionary."""
|
|
40
41
|
folder_path = self._make_folder_path(model, id)
|
|
41
42
|
for key, value in data.items():
|
|
@@ -44,20 +45,16 @@ class SecretsBackend(Backend):
|
|
|
44
45
|
self.secrets.update(f"{folder_path}{key}", value)
|
|
45
46
|
|
|
46
47
|
# and now query again to fetch the updated record.
|
|
47
|
-
return self.records(
|
|
48
|
-
clearskies.query.Query(
|
|
49
|
-
model.__class__, conditions=[clearskies.query.Condition(f"{model.id_column_name}={id}")]
|
|
50
|
-
)
|
|
51
|
-
)[0]
|
|
48
|
+
return self.records(Query(model.__class__, conditions=[Condition(f"{model.id_column_name}={id}")]))[0]
|
|
52
49
|
|
|
53
|
-
def create(self, data: dict[str, Any], model:
|
|
50
|
+
def create(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
54
51
|
if not model.id_column_name in data:
|
|
55
52
|
raise ValueError(
|
|
56
53
|
f"You must provide '{model.id_column_name}' when creating a record with the secrets backend"
|
|
57
54
|
)
|
|
58
55
|
return self.update(data[model.id_column_name], data, model)
|
|
59
56
|
|
|
60
|
-
def delete(self, id: str, model:
|
|
57
|
+
def delete(self, id: str, model: Model) -> bool: # type: ignore[override]
|
|
61
58
|
"""
|
|
62
59
|
Delete the record with the given id.
|
|
63
60
|
|
|
@@ -65,9 +62,7 @@ class SecretsBackend(Backend):
|
|
|
65
62
|
"""
|
|
66
63
|
return True
|
|
67
64
|
|
|
68
|
-
def records(
|
|
69
|
-
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
70
|
-
) -> list[dict[str, Any]]:
|
|
65
|
+
def records(self, query: Query, next_page_data: dict[str, str | int] | None = None) -> list[dict[str, Any]]:
|
|
71
66
|
"""Return a list of records that match the given query configuration."""
|
|
72
67
|
self.check_query(query)
|
|
73
68
|
for condition in query.conditions:
|
clearskies/column.py
CHANGED
|
@@ -1,30 +1,22 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import TYPE_CHECKING, Any, Callable, Self,
|
|
4
|
-
|
|
5
|
-
import
|
|
6
|
-
import clearskies.configs.boolean
|
|
7
|
-
import clearskies.configs.select
|
|
8
|
-
import clearskies.configs.string
|
|
9
|
-
import clearskies.configs.string_or_callable
|
|
10
|
-
import clearskies.configs.validators
|
|
11
|
-
import clearskies.configurable
|
|
12
|
-
import clearskies.decorators
|
|
13
|
-
import clearskies.di
|
|
14
|
-
import clearskies.model
|
|
15
|
-
import clearskies.typing
|
|
16
|
-
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable, Self, overload
|
|
4
|
+
|
|
5
|
+
from clearskies import configs, configurable, decorators
|
|
17
6
|
from clearskies.autodoc.schema import String as AutoDocString
|
|
18
|
-
from clearskies.
|
|
7
|
+
from clearskies.di import InjectableProperties, inject
|
|
8
|
+
from clearskies.query.condition import ParsedCondition
|
|
19
9
|
from clearskies.validator import Validator
|
|
20
10
|
|
|
21
11
|
if TYPE_CHECKING:
|
|
22
|
-
from clearskies import Model, Schema
|
|
12
|
+
from clearskies import Model, Schema, typing
|
|
13
|
+
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
14
|
+
from clearskies.query.condition import Condition
|
|
23
15
|
|
|
24
16
|
|
|
25
|
-
class Column(
|
|
17
|
+
class Column(configurable.Configurable, InjectableProperties):
|
|
26
18
|
"""
|
|
27
|
-
Columns are used to build schemes and enable a variety of levels of automation with
|
|
19
|
+
Columns are used to build schemes and enable a variety of levels of automation with.
|
|
28
20
|
|
|
29
21
|
Columns are used to define your schemas in clearskies, especially via models. The column definitions are then used by endpoints
|
|
30
22
|
and other aspects of the clearskies framework to automate things like input validation, front-end/backend-transformations, and more.
|
|
@@ -33,7 +25,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
33
25
|
"""
|
|
34
26
|
The column class gets the full DI container, because it does a lot of object building itself
|
|
35
27
|
"""
|
|
36
|
-
di =
|
|
28
|
+
di = inject.Di()
|
|
37
29
|
|
|
38
30
|
"""
|
|
39
31
|
A default value to set for this column.
|
|
@@ -79,7 +71,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
79
71
|
}
|
|
80
72
|
```
|
|
81
73
|
"""
|
|
82
|
-
default =
|
|
74
|
+
default = configs.string.String(default=None)
|
|
83
75
|
|
|
84
76
|
"""
|
|
85
77
|
A value to set for this column during a save operation.
|
|
@@ -143,7 +135,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
143
135
|
e.g., `date_of_birth` is `age` years behind the current time (as recorded in the `created` timestamp).
|
|
144
136
|
|
|
145
137
|
"""
|
|
146
|
-
setable =
|
|
138
|
+
setable = configs.string_or_callable.StringOrCallable(default=None)
|
|
147
139
|
|
|
148
140
|
"""
|
|
149
141
|
Whether or not this column can be converted to JSON and included in an API response.
|
|
@@ -151,7 +143,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
151
143
|
If this is set to False for a column and you attempt to set that column as a readable_column in an endpoint,
|
|
152
144
|
clearskies will throw an exception.
|
|
153
145
|
"""
|
|
154
|
-
is_readable =
|
|
146
|
+
is_readable = configs.boolean.Boolean(default=True)
|
|
155
147
|
|
|
156
148
|
"""
|
|
157
149
|
Whether or not this column can be set via an API call.
|
|
@@ -159,7 +151,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
159
151
|
If this is set to False for a column and you attempt to set the column as a writeable column in an endpoint,
|
|
160
152
|
clearskies will throw an exception.
|
|
161
153
|
"""
|
|
162
|
-
is_writeable =
|
|
154
|
+
is_writeable = configs.boolean.Boolean(default=True)
|
|
163
155
|
|
|
164
156
|
"""
|
|
165
157
|
Whether or not it is possible to search by this column
|
|
@@ -167,7 +159,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
167
159
|
If this is set to False for a column and you attempt to set the column as a searchable column in an endpoint,
|
|
168
160
|
clearskies will throw an exception.
|
|
169
161
|
"""
|
|
170
|
-
is_searchable =
|
|
162
|
+
is_searchable = configs.boolean.Boolean(default=True)
|
|
171
163
|
|
|
172
164
|
"""
|
|
173
165
|
Whether or not this column is temporary. A temporary column is not persisted to the backend.
|
|
@@ -228,7 +220,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
228
220
|
If you were using an SQL database, you would not have to put a `date_of_birth` column in your table.
|
|
229
221
|
|
|
230
222
|
"""
|
|
231
|
-
is_temporary =
|
|
223
|
+
is_temporary = configs.boolean.Boolean(default=False)
|
|
232
224
|
|
|
233
225
|
"""
|
|
234
226
|
Validators to use when checking the input for this column during write operations from the API.
|
|
@@ -319,7 +311,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
319
311
|
}
|
|
320
312
|
```
|
|
321
313
|
"""
|
|
322
|
-
validators =
|
|
314
|
+
validators = configs.validators.Validators(default=[])
|
|
323
315
|
|
|
324
316
|
"""
|
|
325
317
|
Actions to take during the pre-save step of the save process if the column has changed during the active save operation.
|
|
@@ -403,7 +395,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
403
395
|
```
|
|
404
396
|
|
|
405
397
|
"""
|
|
406
|
-
on_change_pre_save =
|
|
398
|
+
on_change_pre_save = configs.actions.Actions(default=[])
|
|
407
399
|
|
|
408
400
|
"""
|
|
409
401
|
Actions to take during the post-save step of the process if the column has changed during the active save.
|
|
@@ -521,7 +513,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
521
513
|
```
|
|
522
514
|
|
|
523
515
|
"""
|
|
524
|
-
on_change_post_save =
|
|
516
|
+
on_change_post_save = configs.actions.Actions(default=[])
|
|
525
517
|
|
|
526
518
|
"""
|
|
527
519
|
Actions to take during the save-finished step of the save process if the column has changed in the save.
|
|
@@ -538,7 +530,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
538
530
|
Unlike pre_save and post_save, `data` is not provided because this data has already been merged into the
|
|
539
531
|
model. If you need some context from the completed save operation, use methods like `was_changed` and `previous_value`.
|
|
540
532
|
"""
|
|
541
|
-
on_change_save_finished =
|
|
533
|
+
on_change_save_finished = configs.actions.Actions(default=[])
|
|
542
534
|
|
|
543
535
|
"""
|
|
544
536
|
Use in conjunction with `created_by_source_type` to have this column automatically populated by data from an HTTP request.
|
|
@@ -554,7 +546,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
554
546
|
|
|
555
547
|
See created_by_source_type for usage examples.
|
|
556
548
|
"""
|
|
557
|
-
created_by_source_key =
|
|
549
|
+
created_by_source_key = configs.string.String(default="")
|
|
558
550
|
|
|
559
551
|
"""
|
|
560
552
|
Use in conjunction with `created_by_source_key` to have this column automatically populated by data from ann HTTP request.
|
|
@@ -614,20 +606,20 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
614
606
|
```
|
|
615
607
|
|
|
616
608
|
"""
|
|
617
|
-
created_by_source_type =
|
|
609
|
+
created_by_source_type = configs.select.Select(
|
|
618
610
|
["authorization_data", "http_header", "routing_data", ""], default=""
|
|
619
611
|
)
|
|
620
612
|
|
|
621
613
|
"""
|
|
622
614
|
If True, and the key requested via created_by_source_key doesn't exist in the designated source, an error will be raised.
|
|
623
615
|
"""
|
|
624
|
-
created_by_source_strict =
|
|
616
|
+
created_by_source_strict = configs.boolean.Boolean(default=True)
|
|
625
617
|
|
|
626
618
|
""" The model class this column is associated with. """
|
|
627
|
-
model_class =
|
|
619
|
+
model_class = configs.Schema()
|
|
628
620
|
|
|
629
621
|
""" The name of this column. """
|
|
630
|
-
name =
|
|
622
|
+
name = configs.string.String()
|
|
631
623
|
|
|
632
624
|
"""
|
|
633
625
|
Simple flag to denote if the column is unique or not.
|
|
@@ -669,7 +661,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
669
661
|
"""
|
|
670
662
|
auto_doc_class: type[AutoDocSchema] = AutoDocString
|
|
671
663
|
|
|
672
|
-
@
|
|
664
|
+
@decorators.parameters_to_properties
|
|
673
665
|
def __init__(
|
|
674
666
|
self,
|
|
675
667
|
default: str | None = None,
|
|
@@ -678,10 +670,10 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
678
670
|
is_writeable: bool = True,
|
|
679
671
|
is_searchable: bool = True,
|
|
680
672
|
is_temporary: bool = False,
|
|
681
|
-
validators:
|
|
682
|
-
on_change_pre_save:
|
|
683
|
-
on_change_post_save:
|
|
684
|
-
on_change_save_finished:
|
|
673
|
+
validators: typing.validator | list[typing.validator] = [],
|
|
674
|
+
on_change_pre_save: typing.action | list[typing.action] = [],
|
|
675
|
+
on_change_post_save: typing.action | list[typing.action] = [],
|
|
676
|
+
on_change_save_finished: typing.action | list[typing.action] = [],
|
|
685
677
|
created_by_source_type: str = "",
|
|
686
678
|
created_by_source_key: str = "",
|
|
687
679
|
created_by_source_strict: bool = True,
|
|
@@ -810,11 +802,11 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
810
802
|
}
|
|
811
803
|
return additional_write_columns
|
|
812
804
|
|
|
813
|
-
def to_json(self, model:
|
|
805
|
+
def to_json(self, model: Model) -> dict[str, Any]:
|
|
814
806
|
"""Grabs the column out of the model and converts it into a representation that can be turned into JSON."""
|
|
815
807
|
return {self.name: self.__get__(model, model.__class__)}
|
|
816
808
|
|
|
817
|
-
def input_errors(self, model:
|
|
809
|
+
def input_errors(self, model: Model, data: dict[str, Any]) -> dict[str, Any]:
|
|
818
810
|
"""
|
|
819
811
|
Check the given dictionary of data for any possible input errors.
|
|
820
812
|
|
|
@@ -885,7 +877,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
885
877
|
"""
|
|
886
878
|
return ""
|
|
887
879
|
|
|
888
|
-
def pre_save(self, data: dict[str, Any], model:
|
|
880
|
+
def pre_save(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
889
881
|
"""
|
|
890
882
|
Make any necessary changes to the data before starting the save process.
|
|
891
883
|
|
|
@@ -914,7 +906,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
914
906
|
data = self.execute_actions_with_data(self.on_change_pre_save, model, data)
|
|
915
907
|
return data
|
|
916
908
|
|
|
917
|
-
def post_save(self, data: dict[str, Any], model:
|
|
909
|
+
def post_save(self, data: dict[str, Any], model: Model, id: int | str) -> None:
|
|
918
910
|
"""
|
|
919
911
|
Make any changes needed after persisting data to the backend.
|
|
920
912
|
|
|
@@ -940,7 +932,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
940
932
|
require_dict_return_value=False,
|
|
941
933
|
)
|
|
942
934
|
|
|
943
|
-
def save_finished(self, model:
|
|
935
|
+
def save_finished(self, model: Model) -> None:
|
|
944
936
|
"""
|
|
945
937
|
Make any necessary changes needed after a save has completely finished.
|
|
946
938
|
|
|
@@ -980,8 +972,8 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
980
972
|
|
|
981
973
|
def execute_actions_with_data(
|
|
982
974
|
self,
|
|
983
|
-
actions: list[
|
|
984
|
-
model:
|
|
975
|
+
actions: list[typing.action],
|
|
976
|
+
model: Model,
|
|
985
977
|
data: dict[str, Any],
|
|
986
978
|
id: int | str | None = None,
|
|
987
979
|
context: str = "on_change_pre_save",
|
|
@@ -1016,8 +1008,8 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
1016
1008
|
|
|
1017
1009
|
def execute_actions(
|
|
1018
1010
|
self,
|
|
1019
|
-
actions: list[
|
|
1020
|
-
model:
|
|
1011
|
+
actions: list[typing.action],
|
|
1012
|
+
model: Model,
|
|
1021
1013
|
) -> None:
|
|
1022
1014
|
"""Execute a given set of actions."""
|
|
1023
1015
|
input_output = self.di.build("input_output", cache=True)
|
|
@@ -1036,9 +1028,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
1036
1028
|
"""
|
|
1037
1029
|
return value_1 == value_2
|
|
1038
1030
|
|
|
1039
|
-
def add_search(
|
|
1040
|
-
self, model: clearskies.model.Model, value: str, operator: str = "", relationship_reference: str = ""
|
|
1041
|
-
) -> clearskies.model.Model:
|
|
1031
|
+
def add_search(self, model: Model, value: str, operator: str = "", relationship_reference: str = "") -> Model:
|
|
1042
1032
|
return model.where(self.condition(operator, value))
|
|
1043
1033
|
|
|
1044
1034
|
def build_condition(self, value: str, operator: str = "", column_prefix: str = ""):
|
|
@@ -1068,9 +1058,7 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
1068
1058
|
"""Process user data to decide if the end-user is specifying an allowed operator."""
|
|
1069
1059
|
return operator.lower() in self._allowed_search_operators
|
|
1070
1060
|
|
|
1071
|
-
def n_plus_one_add_joins(
|
|
1072
|
-
self, model: clearskies.model.Model, column_names: list[str] = []
|
|
1073
|
-
) -> clearskies.model.Model:
|
|
1061
|
+
def n_plus_one_add_joins(self, model: Model, column_names: list[str] = []) -> Model:
|
|
1074
1062
|
"""Add any additional joins to solve the N+1 problem."""
|
|
1075
1063
|
return model
|
|
1076
1064
|
|
|
@@ -1096,11 +1084,11 @@ class Column(clearskies.configurable.Configurable, clearskies.di.InjectablePrope
|
|
|
1096
1084
|
|
|
1097
1085
|
def where_for_request(
|
|
1098
1086
|
self,
|
|
1099
|
-
model:
|
|
1087
|
+
model: Model,
|
|
1100
1088
|
routing_data: dict[str, str],
|
|
1101
1089
|
authorization_data: dict[str, Any],
|
|
1102
1090
|
input_output,
|
|
1103
|
-
) ->
|
|
1091
|
+
) -> Model:
|
|
1104
1092
|
"""
|
|
1105
1093
|
Create a hook to automatically apply filtering whenever the column makes an appearance in a get/update/list/search handler.
|
|
1106
1094
|
|
clearskies/columns/audit.py
CHANGED
|
@@ -1,13 +1,14 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from typing import Any
|
|
3
|
+
from typing import TYPE_CHECKING, Any
|
|
4
4
|
|
|
5
|
-
import
|
|
6
|
-
import clearskies.typing
|
|
7
|
-
from clearskies import configs
|
|
5
|
+
from clearskies import configs, decorators
|
|
8
6
|
from clearskies.column import Column
|
|
9
7
|
from clearskies.columns.has_many import HasMany
|
|
10
8
|
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from clearskies import Model, typing
|
|
11
|
+
|
|
11
12
|
|
|
12
13
|
class Audit(HasMany):
|
|
13
14
|
"""
|
|
@@ -66,7 +67,7 @@ class Audit(HasMany):
|
|
|
66
67
|
_descriptor_config_map = None
|
|
67
68
|
_parent_columns: dict[str, Column] | None
|
|
68
69
|
|
|
69
|
-
@
|
|
70
|
+
@decorators.parameters_to_properties
|
|
70
71
|
def __init__(
|
|
71
72
|
self,
|
|
72
73
|
audit_model_class,
|
|
@@ -74,17 +75,17 @@ class Audit(HasMany):
|
|
|
74
75
|
mask_columns: list[str] = [],
|
|
75
76
|
foreign_column_name: str | None = None,
|
|
76
77
|
readable_child_columns: list[str] = [],
|
|
77
|
-
where:
|
|
78
|
+
where: typing.condition | list[typing.condition] = [],
|
|
78
79
|
default: str | None = None,
|
|
79
80
|
is_readable: bool = True,
|
|
80
81
|
is_temporary: bool = False,
|
|
81
|
-
on_change_pre_save:
|
|
82
|
-
on_change_post_save:
|
|
83
|
-
on_change_save_finished:
|
|
82
|
+
on_change_pre_save: typing.action | list[typing.action] = [],
|
|
83
|
+
on_change_post_save: typing.action | list[typing.action] = [],
|
|
84
|
+
on_change_save_finished: typing.action | list[typing.action] = [],
|
|
84
85
|
):
|
|
85
86
|
self.child_model_class = self.audit_model_class
|
|
86
87
|
|
|
87
|
-
def save_finished(self, model):
|
|
88
|
+
def save_finished(self, model: Model):
|
|
88
89
|
super().save_finished(model)
|
|
89
90
|
old_data: dict[str, Any] = model._previous_data
|
|
90
91
|
new_data: dict[str, Any] = model.get_raw_data()
|
|
@@ -114,8 +115,8 @@ class Audit(HasMany):
|
|
|
114
115
|
# note that this is fairly simple logic to get started. It's not going to detect changes that happen
|
|
115
116
|
# in other "tables". For instance, disconnecting a record by deleting an entry in a many-to-many relationship
|
|
116
117
|
# won't be picked up by this.
|
|
117
|
-
old_model = model.
|
|
118
|
-
old_model.
|
|
118
|
+
old_model = model.empty()
|
|
119
|
+
old_model._data = old_data
|
|
119
120
|
from_data: dict[str, Any] = {}
|
|
120
121
|
to_data: dict[str, Any] = {}
|
|
121
122
|
for column, new_value in new_data.items():
|
|
@@ -187,7 +188,7 @@ class Audit(HasMany):
|
|
|
187
188
|
def parent_columns(self) -> dict[str, Column]:
|
|
188
189
|
if self._parent_columns == None:
|
|
189
190
|
self._parent_columns = self.di.build(self.model_class, cache=True).columns()
|
|
190
|
-
return self._parent_columns
|
|
191
|
+
return self._parent_columns # type: ignore[return-value]
|
|
191
192
|
|
|
192
193
|
def record(self, model, action, data=None, record_data=None):
|
|
193
194
|
audit_data = {
|
|
@@ -1,11 +1,8 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
from collections import OrderedDict
|
|
4
3
|
from typing import TYPE_CHECKING, Any, Callable
|
|
5
4
|
|
|
6
|
-
import
|
|
7
|
-
import clearskies.typing
|
|
8
|
-
from clearskies import configs
|
|
5
|
+
from clearskies import configs, decorators
|
|
9
6
|
from clearskies.autodoc.schema import Object as AutoDocObject
|
|
10
7
|
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
11
8
|
from clearskies.autodoc.schema import String as AutoDocString
|
|
@@ -14,7 +11,7 @@ from clearskies.di.inject import InputOutput
|
|
|
14
11
|
from clearskies.functional import validations
|
|
15
12
|
|
|
16
13
|
if TYPE_CHECKING:
|
|
17
|
-
from clearskies import
|
|
14
|
+
from clearskies import Model, typing
|
|
18
15
|
|
|
19
16
|
|
|
20
17
|
class BelongsToId(String):
|
|
@@ -144,7 +141,7 @@ class BelongsToId(String):
|
|
|
144
141
|
import models.category_reference
|
|
145
142
|
|
|
146
143
|
|
|
147
|
-
class Product(clearskies.
|
|
144
|
+
class Product(clearskies.Model):
|
|
148
145
|
id_column_name = "id"
|
|
149
146
|
backend = clearskies.backends.MemoryBackend()
|
|
150
147
|
|
|
@@ -299,23 +296,23 @@ class BelongsToId(String):
|
|
|
299
296
|
_allowed_search_operators = ["="]
|
|
300
297
|
_descriptor_config_map = None
|
|
301
298
|
|
|
302
|
-
@
|
|
299
|
+
@decorators.parameters_to_properties
|
|
303
300
|
def __init__(
|
|
304
301
|
self,
|
|
305
302
|
parent_model_class,
|
|
306
303
|
readable_parent_columns: list[str] = [],
|
|
307
304
|
join_type: str | None = None,
|
|
308
|
-
where:
|
|
305
|
+
where: typing.condition | list[typing.condition] = [],
|
|
309
306
|
default: str | None = None,
|
|
310
307
|
setable: str | Callable | None = None,
|
|
311
308
|
is_readable: bool = True,
|
|
312
309
|
is_writeable: bool = True,
|
|
313
310
|
is_searchable: bool = True,
|
|
314
311
|
is_temporary: bool = False,
|
|
315
|
-
validators:
|
|
316
|
-
on_change_pre_save:
|
|
317
|
-
on_change_post_save:
|
|
318
|
-
on_change_save_finished:
|
|
312
|
+
validators: typing.validator | list[typing.validator] = [],
|
|
313
|
+
on_change_pre_save: typing.action | list[typing.action] = [],
|
|
314
|
+
on_change_post_save: typing.action | list[typing.action] = [],
|
|
315
|
+
on_change_save_finished: typing.action | list[typing.action] = [],
|
|
319
316
|
created_by_source_type: str = "",
|
|
320
317
|
created_by_source_key: str = "",
|
|
321
318
|
created_by_source_strict: bool = True,
|
|
@@ -441,9 +438,7 @@ class BelongsToId(String):
|
|
|
441
438
|
)
|
|
442
439
|
return self.parent_columns[relationship_reference].allowed_search_operators()
|
|
443
440
|
|
|
444
|
-
def add_search(
|
|
445
|
-
self, model: clearskies.model.Model, value: str, operator: str = "", relationship_reference: str = ""
|
|
446
|
-
) -> clearskies.model.Model:
|
|
441
|
+
def add_search(self, model: Model, value: str, operator: str = "", relationship_reference: str = "") -> Model:
|
|
447
442
|
if not relationship_reference:
|
|
448
443
|
return super().add_search(model, value, operator=operator)
|
|
449
444
|
|