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
|
@@ -5,13 +5,7 @@ from typing import TYPE_CHECKING, Any, Callable
|
|
|
5
5
|
|
|
6
6
|
import requests
|
|
7
7
|
|
|
8
|
-
import
|
|
9
|
-
import clearskies.columns.json
|
|
10
|
-
import clearskies.configs
|
|
11
|
-
import clearskies.configurable
|
|
12
|
-
import clearskies.decorators
|
|
13
|
-
import clearskies.model
|
|
14
|
-
import clearskies.query
|
|
8
|
+
from clearskies import columns, configs, configurable, decorators
|
|
15
9
|
from clearskies.autodoc.schema import Integer as AutoDocInteger
|
|
16
10
|
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
17
11
|
from clearskies.autodoc.schema import String as AutoDocString
|
|
@@ -20,10 +14,12 @@ from clearskies.di import InjectableProperties, inject
|
|
|
20
14
|
from clearskies.functional import routing, string
|
|
21
15
|
|
|
22
16
|
if TYPE_CHECKING:
|
|
23
|
-
import
|
|
17
|
+
from clearskies import Column, Model
|
|
18
|
+
from clearskies.authentication import Authentication
|
|
19
|
+
from clearskies.query import Query
|
|
24
20
|
|
|
25
21
|
|
|
26
|
-
class ApiBackend(
|
|
22
|
+
class ApiBackend(configurable.Configurable, Backend, InjectableProperties):
|
|
27
23
|
"""
|
|
28
24
|
Fetch and store data from an API endpoint.
|
|
29
25
|
|
|
@@ -314,14 +310,14 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
314
310
|
|
|
315
311
|
Note: this is treated as a 'folder' path: if set, it becomes the URL prefix and is followed with a '/'
|
|
316
312
|
"""
|
|
317
|
-
base_url =
|
|
313
|
+
base_url = configs.String(default="")
|
|
318
314
|
|
|
319
315
|
"""
|
|
320
316
|
A suffix to append to the end of the URL.
|
|
321
317
|
|
|
322
318
|
Note: this is treated as a 'folder' path: if set, it becomes the URL suffix and is prefixed with a '/'
|
|
323
319
|
"""
|
|
324
|
-
url_suffix =
|
|
320
|
+
url_suffix = configs.String(default="")
|
|
325
321
|
|
|
326
322
|
"""
|
|
327
323
|
An instance of clearskies.authentication.Authentication that handles authentication to the API.
|
|
@@ -392,12 +388,12 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
392
388
|
|
|
393
389
|
```
|
|
394
390
|
"""
|
|
395
|
-
authentication =
|
|
391
|
+
authentication = configs.Authentication(default=None)
|
|
396
392
|
|
|
397
393
|
"""
|
|
398
394
|
A dictionary of headers to attach to all outgoing API requests
|
|
399
395
|
"""
|
|
400
|
-
headers =
|
|
396
|
+
headers = configs.StringDict(default={})
|
|
401
397
|
|
|
402
398
|
"""
|
|
403
399
|
The casing used in the model (snake_case, camelCase, TitleCase)
|
|
@@ -478,14 +474,14 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
478
474
|
}
|
|
479
475
|
```
|
|
480
476
|
"""
|
|
481
|
-
model_casing =
|
|
477
|
+
model_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
482
478
|
|
|
483
479
|
"""
|
|
484
480
|
The casing used by the API response (snake_case, camelCase, TitleCase)
|
|
485
481
|
|
|
486
482
|
See model_casing for details and usage.
|
|
487
483
|
"""
|
|
488
|
-
api_casing =
|
|
484
|
+
api_casing = configs.Select(["snake_case", "camelCase", "TitleCase"], default="snake_case")
|
|
489
485
|
|
|
490
486
|
"""
|
|
491
487
|
A mapping from the data keys returned by the API to the data keys expected in the model
|
|
@@ -554,24 +550,24 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
554
550
|
}
|
|
555
551
|
```
|
|
556
552
|
"""
|
|
557
|
-
api_to_model_map =
|
|
553
|
+
api_to_model_map = configs.StringDict(default={})
|
|
558
554
|
|
|
559
555
|
"""
|
|
560
556
|
The name of the pagination parameter
|
|
561
557
|
"""
|
|
562
|
-
pagination_parameter_name =
|
|
558
|
+
pagination_parameter_name = configs.String(default="start")
|
|
563
559
|
|
|
564
560
|
"""
|
|
565
561
|
The expected 'type' of the pagination parameter: must be either 'int' or 'str'
|
|
566
562
|
|
|
567
563
|
Note: this is set as a literal string, not as a type.
|
|
568
564
|
"""
|
|
569
|
-
pagination_parameter_type =
|
|
565
|
+
pagination_parameter_type = configs.Select(["int", "str"], default="str")
|
|
570
566
|
|
|
571
567
|
"""
|
|
572
568
|
The name of the parameter that sets the number of records per page (if empty, setting the page size will not be allowed)
|
|
573
569
|
"""
|
|
574
|
-
limit_parameter_name =
|
|
570
|
+
limit_parameter_name = configs.String(default="limit")
|
|
575
571
|
|
|
576
572
|
"""
|
|
577
573
|
The requests instance.
|
|
@@ -586,11 +582,11 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
586
582
|
_auth_injected = False
|
|
587
583
|
_response_to_model_map: dict[str, str] = None # type: ignore
|
|
588
584
|
|
|
589
|
-
@
|
|
585
|
+
@decorators.parameters_to_properties
|
|
590
586
|
def __init__(
|
|
591
587
|
self,
|
|
592
588
|
base_url: str,
|
|
593
|
-
authentication:
|
|
589
|
+
authentication: Authentication | None = None,
|
|
594
590
|
model_casing: str = "snake_case",
|
|
595
591
|
api_casing: str = "snake_case",
|
|
596
592
|
api_to_model_map: dict[str, str] = {},
|
|
@@ -658,7 +654,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
658
654
|
"""
|
|
659
655
|
return self.finalize_url(url, data, operation)
|
|
660
656
|
|
|
661
|
-
def finalize_url_from_query(self, query:
|
|
657
|
+
def finalize_url_from_query(self, query: Query, operation: str) -> tuple[str, list[str]]:
|
|
662
658
|
"""
|
|
663
659
|
Create the URL using a query to fill in any URL parameters.
|
|
664
660
|
|
|
@@ -671,7 +667,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
671
667
|
available_routing_data[condition.column_name] = condition.values[0]
|
|
672
668
|
return self.finalize_url(query.model_class.destination_name(), available_routing_data, operation)
|
|
673
669
|
|
|
674
|
-
def create_url(self, data: dict[str, Any], model:
|
|
670
|
+
def create_url(self, data: dict[str, Any], model: Model) -> tuple[str, list[str]]:
|
|
675
671
|
"""
|
|
676
672
|
Calculate the URL to use for a create requst. Also, return the list of ay data parameters used to construct the URL.
|
|
677
673
|
|
|
@@ -679,11 +675,11 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
679
675
|
"""
|
|
680
676
|
return self.finalize_url_from_data(model.destination_name(), data, "create")
|
|
681
677
|
|
|
682
|
-
def create_method(self, data: dict[str, Any], model:
|
|
678
|
+
def create_method(self, data: dict[str, Any], model: Model) -> str:
|
|
683
679
|
"""Return the request method to use with a create request."""
|
|
684
680
|
return "POST"
|
|
685
681
|
|
|
686
|
-
def records_url(self, query:
|
|
682
|
+
def records_url(self, query: Query) -> tuple[str, list[str]]:
|
|
687
683
|
"""
|
|
688
684
|
Calculate the URL to use for a records request. Also, return the list of any query parameters used to construct the URL.
|
|
689
685
|
|
|
@@ -691,11 +687,11 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
691
687
|
"""
|
|
692
688
|
return self.finalize_url_from_query(query, "records")
|
|
693
689
|
|
|
694
|
-
def records_method(self, query:
|
|
690
|
+
def records_method(self, query: Query) -> str:
|
|
695
691
|
"""Return the request method to use when fetching records from the API."""
|
|
696
692
|
return "GET"
|
|
697
693
|
|
|
698
|
-
def count_url(self, query:
|
|
694
|
+
def count_url(self, query: Query) -> tuple[str, list[str]]:
|
|
699
695
|
"""
|
|
700
696
|
Calculate the URL to use for a request to get a record count.. Also, return the list of any query parameters used to construct the URL.
|
|
701
697
|
|
|
@@ -703,11 +699,11 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
703
699
|
"""
|
|
704
700
|
return self.records_url(query)
|
|
705
701
|
|
|
706
|
-
def count_method(self, query:
|
|
702
|
+
def count_method(self, query: Query) -> str:
|
|
707
703
|
"""Return the request method to use when making a request for a record count."""
|
|
708
704
|
return self.records_method(query)
|
|
709
705
|
|
|
710
|
-
def delete_url(self, id: int | str, model:
|
|
706
|
+
def delete_url(self, id: int | str, model: Model) -> tuple[str, list[str]]:
|
|
711
707
|
"""
|
|
712
708
|
Calculate the URL to use for a delete request. Also, return the list of any query parameters used to construct the URL.
|
|
713
709
|
|
|
@@ -716,11 +712,11 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
716
712
|
model_base_url = model.destination_name().strip("/") + "/" if model.destination_name() else ""
|
|
717
713
|
return self.finalize_url_from_data(f"{model_base_url}{id}", model.get_raw_data(), "delete")
|
|
718
714
|
|
|
719
|
-
def delete_method(self, id: int | str, model:
|
|
715
|
+
def delete_method(self, id: int | str, model: Model) -> str:
|
|
720
716
|
"""Return the request method to use when deleting records via the API."""
|
|
721
717
|
return "DELETE"
|
|
722
718
|
|
|
723
|
-
def update_url(self, id: int | str, data: dict[str, Any], model:
|
|
719
|
+
def update_url(self, id: int | str, data: dict[str, Any], model: Model) -> tuple[str, list[str]]:
|
|
724
720
|
"""
|
|
725
721
|
Calculate the URL to use for an update request. Also, return the list of any query parameters used to construct the URL.
|
|
726
722
|
|
|
@@ -729,11 +725,11 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
729
725
|
model_base_url = model.destination_name().strip("/") + "/" if model.destination_name() else ""
|
|
730
726
|
return self.finalize_url_from_data(f"{model_base_url}{id}", {**model.get_raw_data(), **data}, "update")
|
|
731
727
|
|
|
732
|
-
def update_method(self, id: int | str, data: dict[str, Any], model:
|
|
728
|
+
def update_method(self, id: int | str, data: dict[str, Any], model: Model) -> str:
|
|
733
729
|
"""Return the request method to use for an update request."""
|
|
734
730
|
return "PATCH"
|
|
735
731
|
|
|
736
|
-
def update(self, id: int | str, data: dict[str, Any], model:
|
|
732
|
+
def update(self, id: int | str, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
737
733
|
"""Update a record."""
|
|
738
734
|
data = {**data}
|
|
739
735
|
(url, used_routing_parameters) = self.update_url(id, data, model)
|
|
@@ -748,7 +744,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
748
744
|
new_record = {**new_record, **self.map_update_response(response.json(), model)}
|
|
749
745
|
return new_record
|
|
750
746
|
|
|
751
|
-
def map_update_response(self, response_data: dict[str, Any], model:
|
|
747
|
+
def map_update_response(self, response_data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
752
748
|
"""
|
|
753
749
|
Take the response from the API endpoint for an update request and figure out where the data lives/return it to build a new model.
|
|
754
750
|
|
|
@@ -756,7 +752,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
756
752
|
"""
|
|
757
753
|
return self.map_record_response(response_data, model.get_columns(), "update")
|
|
758
754
|
|
|
759
|
-
def create(self, data: dict[str, Any], model:
|
|
755
|
+
def create(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
760
756
|
"""Create a record."""
|
|
761
757
|
data = {**data}
|
|
762
758
|
(url, used_routing_parameters) = self.create_url(data, model)
|
|
@@ -770,19 +766,17 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
770
766
|
return self.map_create_response(response.json(), model)
|
|
771
767
|
return {}
|
|
772
768
|
|
|
773
|
-
def map_create_response(self, response_data: dict[str, Any], model:
|
|
769
|
+
def map_create_response(self, response_data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
774
770
|
return self.map_record_response(response_data, model.get_columns(), "create")
|
|
775
771
|
|
|
776
|
-
def delete(self, id: int | str, model:
|
|
772
|
+
def delete(self, id: int | str, model: Model) -> bool:
|
|
777
773
|
(url, used_routing_parameters) = self.delete_url(id, model)
|
|
778
774
|
request_method = self.delete_method(id, model)
|
|
779
775
|
|
|
780
776
|
response = self.execute_request(url, request_method)
|
|
781
777
|
return True
|
|
782
778
|
|
|
783
|
-
def records(
|
|
784
|
-
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
785
|
-
) -> list[dict[str, Any]]:
|
|
779
|
+
def records(self, query: Query, next_page_data: dict[str, str | int] | None = None) -> list[dict[str, Any]]:
|
|
786
780
|
self.check_query(query)
|
|
787
781
|
(url, method, body, headers) = self.build_records_request(query)
|
|
788
782
|
response = self.execute_request(url, method, json=body, headers=headers)
|
|
@@ -791,7 +785,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
791
785
|
self.set_next_page_data_from_response(next_page_data, query, response)
|
|
792
786
|
return records
|
|
793
787
|
|
|
794
|
-
def build_records_request(self, query:
|
|
788
|
+
def build_records_request(self, query: Query) -> tuple[str, str, dict[str, Any], dict[str, str]]:
|
|
795
789
|
(url, used_routing_parameters) = self.records_url(query)
|
|
796
790
|
|
|
797
791
|
(condition_route_id, condition_url_parameters, condition_body_parameters) = (
|
|
@@ -825,7 +819,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
825
819
|
)
|
|
826
820
|
|
|
827
821
|
def conditions_to_request_parameters(
|
|
828
|
-
self, query:
|
|
822
|
+
self, query: Query, used_routing_parameters: list[str]
|
|
829
823
|
) -> tuple[str, dict[str, str], dict[str, Any]]:
|
|
830
824
|
route_id = ""
|
|
831
825
|
|
|
@@ -844,7 +838,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
844
838
|
|
|
845
839
|
return (route_id, url_parameters, {})
|
|
846
840
|
|
|
847
|
-
def pagination_to_request_parameters(self, query:
|
|
841
|
+
def pagination_to_request_parameters(self, query: Query) -> tuple[dict[str, str], dict[str, Any]]:
|
|
848
842
|
url_parameters = {}
|
|
849
843
|
if query.limit:
|
|
850
844
|
if not self.limit_parameter_name:
|
|
@@ -858,7 +852,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
858
852
|
|
|
859
853
|
return (url_parameters, {})
|
|
860
854
|
|
|
861
|
-
def sorts_to_request_parameters(self, query:
|
|
855
|
+
def sorts_to_request_parameters(self, query: Query) -> tuple[dict[str, str], dict[str, Any]]:
|
|
862
856
|
if not query.sorts:
|
|
863
857
|
return ({}, {})
|
|
864
858
|
|
|
@@ -873,7 +867,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
873
867
|
)
|
|
874
868
|
|
|
875
869
|
def map_records_response(
|
|
876
|
-
self, response_data: Any, query:
|
|
870
|
+
self, response_data: Any, query: Query, query_data: dict[str, Any] | None = None
|
|
877
871
|
) -> list[dict[str, Any]]:
|
|
878
872
|
"""Take the response from an API endpoint that returns a list of records and find the actual list of records."""
|
|
879
873
|
columns = query.model_class.get_columns()
|
|
@@ -918,7 +912,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
918
912
|
)
|
|
919
913
|
|
|
920
914
|
def map_record_response(
|
|
921
|
-
self, response_data: dict[str, Any], columns: dict[str,
|
|
915
|
+
self, response_data: dict[str, Any], columns: dict[str, Column], operation: str
|
|
922
916
|
) -> dict[str, Any]:
|
|
923
917
|
"""
|
|
924
918
|
Take the response from an API endpoint that returns a single record (typically update and create requests) and return the data for a new model.
|
|
@@ -948,7 +942,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
948
942
|
def check_dict_and_map_to_model(
|
|
949
943
|
self,
|
|
950
944
|
response_data: dict[str, Any],
|
|
951
|
-
columns: dict[str,
|
|
945
|
+
columns: dict[str, Column],
|
|
952
946
|
query_data: dict[str, Any] = {},
|
|
953
947
|
) -> dict[str, Any] | None:
|
|
954
948
|
"""
|
|
@@ -987,7 +981,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
987
981
|
|
|
988
982
|
return {**query_data, **mapped}
|
|
989
983
|
|
|
990
|
-
def build_response_to_model_map(self, columns: dict[str,
|
|
984
|
+
def build_response_to_model_map(self, columns: dict[str, Column]) -> dict[str, str]:
|
|
991
985
|
if self._response_to_model_map is not None:
|
|
992
986
|
return self._response_to_model_map
|
|
993
987
|
|
|
@@ -1003,7 +997,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
1003
997
|
def set_next_page_data_from_response(
|
|
1004
998
|
self,
|
|
1005
999
|
next_page_data: dict[str, Any],
|
|
1006
|
-
query:
|
|
1000
|
+
query: Query,
|
|
1007
1001
|
response: requests.Response, # type: ignore
|
|
1008
1002
|
) -> None:
|
|
1009
1003
|
"""
|
|
@@ -1042,7 +1036,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
1042
1036
|
)
|
|
1043
1037
|
next_page_data[self.pagination_parameter_name] = query_parameters[self.pagination_parameter_name][0]
|
|
1044
1038
|
|
|
1045
|
-
def count(self, query:
|
|
1039
|
+
def count(self, query: Query) -> int:
|
|
1046
1040
|
raise NotImplementedError(
|
|
1047
1041
|
f"The {self.__class__.__name__} backend does not support count operations, so you can't use the `len` or `bool` function for any models using it."
|
|
1048
1042
|
)
|
|
@@ -1104,7 +1098,7 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
1104
1098
|
|
|
1105
1099
|
return response
|
|
1106
1100
|
|
|
1107
|
-
def check_query(self, query:
|
|
1101
|
+
def check_query(self, query: Query) -> None:
|
|
1108
1102
|
for key in ["joins", "group_by", "selects"]:
|
|
1109
1103
|
if getattr(query, key):
|
|
1110
1104
|
raise ValueError(f"{self.__class__.__name__} does not support queries with {key}")
|
|
@@ -1155,20 +1149,20 @@ class ApiBackend(clearskies.configurable.Configurable, Backend, InjectableProper
|
|
|
1155
1149
|
)
|
|
1156
1150
|
]
|
|
1157
1151
|
|
|
1158
|
-
def column_from_backend(self, column:
|
|
1152
|
+
def column_from_backend(self, column: Column, value: Any) -> Any:
|
|
1159
1153
|
"""We have a couple columns we want to override transformations for."""
|
|
1160
1154
|
# most importantly, there's no need to transform a JSON column in either direction
|
|
1161
|
-
if isinstance(column,
|
|
1155
|
+
if isinstance(column, columns.json.Json):
|
|
1162
1156
|
return value
|
|
1163
1157
|
return super().column_from_backend(column, value)
|
|
1164
1158
|
|
|
1165
|
-
def column_to_backend(self, column:
|
|
1159
|
+
def column_to_backend(self, column: Column, backend_data: dict[str, Any]) -> dict[str, Any]:
|
|
1166
1160
|
"""We have a couple columns we want to override transformations for."""
|
|
1167
1161
|
# most importantly, there's no need to transform a JSON column in either direction
|
|
1168
|
-
if isinstance(column,
|
|
1162
|
+
if isinstance(column, columns.json.Json):
|
|
1169
1163
|
return backend_data
|
|
1170
1164
|
# also, APIs tend to have a different format for dates than SQL
|
|
1171
|
-
if isinstance(column,
|
|
1165
|
+
if isinstance(column, columns.datetime.Datetime) and column.name in backend_data:
|
|
1172
1166
|
as_date = (
|
|
1173
1167
|
backend_data[column.name].isoformat()
|
|
1174
1168
|
if type(backend_data[column.name]) != str
|
clearskies/backends/backend.py
CHANGED
|
@@ -1,12 +1,14 @@
|
|
|
1
|
-
import
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
2
3
|
from abc import ABC, abstractmethod
|
|
3
|
-
from typing import Any, Callable
|
|
4
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
4
5
|
|
|
5
|
-
import clearskies.column
|
|
6
|
-
import clearskies.model
|
|
7
|
-
import clearskies.query
|
|
8
6
|
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
9
7
|
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from clearskies import Column, Model
|
|
10
|
+
from clearskies.query import Query
|
|
11
|
+
|
|
10
12
|
|
|
11
13
|
class Backend(ABC):
|
|
12
14
|
"""
|
|
@@ -26,29 +28,27 @@ class Backend(ABC):
|
|
|
26
28
|
can_count = True
|
|
27
29
|
|
|
28
30
|
@abstractmethod
|
|
29
|
-
def update(self, id: int | str, data: dict[str, Any], model:
|
|
31
|
+
def update(self, id: int | str, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
30
32
|
"""Update the record with the given id with the information from the data dictionary."""
|
|
31
33
|
pass
|
|
32
34
|
|
|
33
35
|
@abstractmethod
|
|
34
|
-
def create(self, data: dict[str, Any], model:
|
|
36
|
+
def create(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
35
37
|
"""Create a record with the information from the data dictionary."""
|
|
36
38
|
pass
|
|
37
39
|
|
|
38
40
|
@abstractmethod
|
|
39
|
-
def delete(self, id: int | str, model:
|
|
41
|
+
def delete(self, id: int | str, model: Model) -> bool:
|
|
40
42
|
"""Delete the record with the given id."""
|
|
41
43
|
pass
|
|
42
44
|
|
|
43
45
|
@abstractmethod
|
|
44
|
-
def count(self, query:
|
|
46
|
+
def count(self, query: Query) -> int:
|
|
45
47
|
"""Return the number of records which match the given query configuration."""
|
|
46
48
|
pass
|
|
47
49
|
|
|
48
50
|
@abstractmethod
|
|
49
|
-
def records(
|
|
50
|
-
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
51
|
-
) -> list[dict[str, Any]]:
|
|
51
|
+
def records(self, query: Query, next_page_data: dict[str, str | int] | None = None) -> list[dict[str, Any]]:
|
|
52
52
|
"""
|
|
53
53
|
Return a list of records that match the given query configuration.
|
|
54
54
|
|
|
@@ -107,7 +107,7 @@ class Backend(ABC):
|
|
|
107
107
|
"""
|
|
108
108
|
pass
|
|
109
109
|
|
|
110
|
-
def column_from_backend(self, column:
|
|
110
|
+
def column_from_backend(self, column: Column, value: Any) -> Any:
|
|
111
111
|
"""
|
|
112
112
|
Manage transformations from the backend.
|
|
113
113
|
|
|
@@ -121,7 +121,7 @@ class Backend(ABC):
|
|
|
121
121
|
"""
|
|
122
122
|
return column.from_backend(value)
|
|
123
123
|
|
|
124
|
-
def column_to_backend(self, column:
|
|
124
|
+
def column_to_backend(self, column: Column, backend_data: dict[str, Any]) -> dict[str, Any]:
|
|
125
125
|
"""
|
|
126
126
|
Manage transformations to the backend.
|
|
127
127
|
|
|
@@ -1,11 +1,15 @@
|
|
|
1
|
-
from
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING, Any, Callable
|
|
2
4
|
|
|
3
|
-
import clearskies.model
|
|
4
|
-
import clearskies.query
|
|
5
5
|
from clearskies.autodoc.schema import Integer as AutoDocInteger
|
|
6
6
|
from clearskies.autodoc.schema import Schema as AutoDocSchema
|
|
7
7
|
from clearskies.backends.backend import Backend
|
|
8
8
|
from clearskies.di import InjectableProperties, inject
|
|
9
|
+
from clearskies.query import Condition, Query
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from clearskies import Model
|
|
9
13
|
|
|
10
14
|
|
|
11
15
|
class CursorBackend(Backend, InjectableProperties):
|
|
@@ -133,7 +137,7 @@ class CursorBackend(Backend, InjectableProperties):
|
|
|
133
137
|
+ self.table_escape_character
|
|
134
138
|
)
|
|
135
139
|
|
|
136
|
-
def update(self, id: int | str, data: dict[str, Any], model:
|
|
140
|
+
def update(self, id: int | str, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
137
141
|
query_parts = []
|
|
138
142
|
parameters = []
|
|
139
143
|
escape = self.column_escape_character
|
|
@@ -149,13 +153,9 @@ class CursorBackend(Backend, InjectableProperties):
|
|
|
149
153
|
)
|
|
150
154
|
|
|
151
155
|
# and now query again to fetch the updated record.
|
|
152
|
-
return self.records(
|
|
153
|
-
clearskies.query.Query(
|
|
154
|
-
model.__class__, conditions=[clearskies.query.Condition(f"{model.id_column_name}={id}")]
|
|
155
|
-
)
|
|
156
|
-
)[0]
|
|
156
|
+
return self.records(Query(model.__class__, conditions=[Condition(f"{model.id_column_name}={id}")]))[0]
|
|
157
157
|
|
|
158
|
-
def create(self, data: dict[str, Any], model:
|
|
158
|
+
def create(self, data: dict[str, Any], model: Model) -> dict[str, Any]:
|
|
159
159
|
escape = self.column_escape_character
|
|
160
160
|
columns = escape + f"{escape}, {escape}".join(data.keys()) + escape
|
|
161
161
|
placeholders = ", ".join(["%s" for i in range(len(data))])
|
|
@@ -168,27 +168,21 @@ class CursorBackend(Backend, InjectableProperties):
|
|
|
168
168
|
if not new_id:
|
|
169
169
|
raise ValueError("I can't figure out what the id is for a newly created record :(")
|
|
170
170
|
|
|
171
|
-
return self.records(
|
|
172
|
-
clearskies.query.Query(
|
|
173
|
-
model.__class__, conditions=[clearskies.query.Condition(f"{model.id_column_name}={new_id}")]
|
|
174
|
-
)
|
|
175
|
-
)[0]
|
|
171
|
+
return self.records(Query(model.__class__, conditions=[Condition(f"{model.id_column_name}={new_id}")]))[0]
|
|
176
172
|
|
|
177
|
-
def delete(self, id: int | str, model:
|
|
173
|
+
def delete(self, id: int | str, model: Model) -> bool:
|
|
178
174
|
table_name = self._finalize_table_name(model.destination_name())
|
|
179
175
|
self.cursor.execute(f"DELETE FROM {table_name} WHERE {model.id_column_name}=%s", (id,))
|
|
180
176
|
return True
|
|
181
177
|
|
|
182
|
-
def count(self, query:
|
|
178
|
+
def count(self, query: Query) -> int:
|
|
183
179
|
(sql, parameters) = self.as_count_sql(query)
|
|
184
180
|
self.cursor.execute(sql, parameters)
|
|
185
181
|
for row in self.cursor:
|
|
186
182
|
return row[0] if type(row) == tuple else row["count"]
|
|
187
183
|
return 0
|
|
188
184
|
|
|
189
|
-
def records(
|
|
190
|
-
self, query: clearskies.query.Query, next_page_data: dict[str, str | int] | None = None
|
|
191
|
-
) -> list[dict[str, Any]]:
|
|
185
|
+
def records(self, query: Query, next_page_data: dict[str, str | int] | None = None) -> list[dict[str, Any]]:
|
|
192
186
|
# I was going to get fancy and have this return an iterator, but since I'm going to load up
|
|
193
187
|
# everything into a list anyway, I may as well just return the list, right?
|
|
194
188
|
(sql, parameters) = self.as_sql(query)
|
|
@@ -201,7 +195,7 @@ class CursorBackend(Backend, InjectableProperties):
|
|
|
201
195
|
next_page_data["start"] = int(start) + int(limit)
|
|
202
196
|
return records
|
|
203
197
|
|
|
204
|
-
def as_sql(self, query:
|
|
198
|
+
def as_sql(self, query: Query) -> tuple[str, tuple[Any]]:
|
|
205
199
|
escape = self.column_escape_character
|
|
206
200
|
table_name = query.model_class.destination_name()
|
|
207
201
|
(wheres, parameters) = self.conditions_as_wheres_and_parameters(
|
|
@@ -243,7 +237,7 @@ class CursorBackend(Backend, InjectableProperties):
|
|
|
243
237
|
parameters,
|
|
244
238
|
)
|
|
245
239
|
|
|
246
|
-
def as_count_sql(self, query:
|
|
240
|
+
def as_count_sql(self, query: Query) -> tuple[str, tuple[Any]]:
|
|
247
241
|
escape = self.column_escape_character
|
|
248
242
|
# note that this won't work if we start including a HAVING clause
|
|
249
243
|
(wheres, parameters) = self.conditions_as_wheres_and_parameters(
|
|
@@ -267,7 +261,7 @@ class CursorBackend(Backend, InjectableProperties):
|
|
|
267
261
|
return (query_string, parameters)
|
|
268
262
|
|
|
269
263
|
def conditions_as_wheres_and_parameters(
|
|
270
|
-
self, conditions: list[
|
|
264
|
+
self, conditions: list[Condition], default_table_name: str
|
|
271
265
|
) -> tuple[str, tuple[Any]]:
|
|
272
266
|
if not conditions:
|
|
273
267
|
return ("", ()) # type: ignore
|