shopware-api-client 1.0.118__py3-none-any.whl → 1.1.1__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.
- shopware_api_client/base.py +338 -176
- shopware_api_client/cache.py +5 -10
- shopware_api_client/endpoints/admin/__init__.py +11 -4
- shopware_api_client/endpoints/admin/commercial/b2b_components_role.py +5 -20
- shopware_api_client/endpoints/admin/commercial/b2b_components_shopping_list.py +5 -25
- shopware_api_client/endpoints/admin/commercial/b2b_components_shopping_list_line_item.py +7 -24
- shopware_api_client/endpoints/admin/commercial/b2b_employee.py +5 -27
- shopware_api_client/endpoints/admin/commercial/dynamic_access.py +9 -19
- shopware_api_client/endpoints/admin/core/acl_role.py +5 -19
- shopware_api_client/endpoints/admin/core/api_info.py +11 -12
- shopware_api_client/endpoints/admin/core/app.py +5 -42
- shopware_api_client/endpoints/admin/core/app_script_condition.py +5 -24
- shopware_api_client/endpoints/admin/core/category.py +5 -56
- shopware_api_client/endpoints/admin/core/cms_block.py +5 -34
- shopware_api_client/endpoints/admin/core/cms_page.py +5 -26
- shopware_api_client/endpoints/admin/core/cms_section.py +5 -35
- shopware_api_client/endpoints/admin/core/cms_slot.py +5 -31
- shopware_api_client/endpoints/admin/core/country.py +5 -37
- shopware_api_client/endpoints/admin/core/country_state.py +5 -23
- shopware_api_client/endpoints/admin/core/currency.py +5 -33
- shopware_api_client/endpoints/admin/core/currency_country_rounding.py +5 -20
- shopware_api_client/endpoints/admin/core/custom_entity.py +4 -26
- shopware_api_client/endpoints/admin/core/custom_field.py +4 -14
- shopware_api_client/endpoints/admin/core/customer.py +5 -57
- shopware_api_client/endpoints/admin/core/customer_address.py +6 -33
- shopware_api_client/endpoints/admin/core/customer_group.py +5 -24
- shopware_api_client/endpoints/admin/core/customer_recovery.py +5 -16
- shopware_api_client/endpoints/admin/core/customer_wishlist.py +5 -19
- shopware_api_client/endpoints/admin/core/customer_wishlist_product.py +5 -19
- shopware_api_client/endpoints/admin/core/delivery_time.py +5 -21
- shopware_api_client/endpoints/admin/core/document.py +5 -28
- shopware_api_client/endpoints/admin/core/document_base_config.py +5 -27
- shopware_api_client/endpoints/admin/core/document_base_config_sales_channel.py +7 -20
- shopware_api_client/endpoints/admin/core/document_type.py +5 -19
- shopware_api_client/endpoints/admin/core/integration.py +5 -25
- shopware_api_client/endpoints/admin/core/landing_page.py +5 -28
- shopware_api_client/endpoints/admin/core/language.py +5 -21
- shopware_api_client/endpoints/admin/core/locale.py +5 -20
- shopware_api_client/endpoints/admin/core/main_category.py +5 -19
- shopware_api_client/endpoints/admin/core/media.py +25 -37
- shopware_api_client/endpoints/admin/core/media_default_folder.py +4 -17
- shopware_api_client/endpoints/admin/core/media_folder.py +5 -26
- shopware_api_client/endpoints/admin/core/media_folder_configuration.py +5 -21
- shopware_api_client/endpoints/admin/core/media_thumbnail.py +5 -26
- shopware_api_client/endpoints/admin/core/media_thumbnail_size.py +7 -18
- shopware_api_client/endpoints/admin/core/order.py +12 -48
- shopware_api_client/endpoints/admin/core/order_address.py +6 -34
- shopware_api_client/endpoints/admin/core/order_customer.py +6 -29
- shopware_api_client/endpoints/admin/core/order_delivery.py +5 -30
- shopware_api_client/endpoints/admin/core/order_delivery_position.py +5 -26
- shopware_api_client/endpoints/admin/core/order_line_item.py +7 -44
- shopware_api_client/endpoints/admin/core/order_line_item_download.py +5 -23
- shopware_api_client/endpoints/admin/core/order_transaction.py +5 -25
- shopware_api_client/endpoints/admin/core/order_transaction_capture.py +5 -27
- shopware_api_client/endpoints/admin/core/order_transaction_capture_refund.py +7 -28
- shopware_api_client/endpoints/admin/core/order_transaction_capture_refund_position.py +8 -29
- shopware_api_client/endpoints/admin/core/payment_method.py +5 -52
- shopware_api_client/endpoints/admin/core/product.py +24 -90
- shopware_api_client/endpoints/admin/core/product_configurator_setting.py +5 -26
- shopware_api_client/endpoints/admin/core/product_cross_selling.py +5 -27
- shopware_api_client/endpoints/admin/core/product_cross_selling_assigned_products.py +7 -21
- shopware_api_client/endpoints/admin/core/product_download.py +5 -22
- shopware_api_client/endpoints/admin/core/product_export.py +5 -34
- shopware_api_client/endpoints/admin/core/product_feature_set.py +5 -19
- shopware_api_client/endpoints/admin/core/product_manufacturer.py +5 -23
- shopware_api_client/endpoints/admin/core/product_media.py +5 -22
- shopware_api_client/endpoints/admin/core/product_price.py +5 -24
- shopware_api_client/endpoints/admin/core/product_review.py +5 -29
- shopware_api_client/endpoints/admin/core/product_search_keyword.py +5 -20
- shopware_api_client/endpoints/admin/core/product_stream.py +5 -23
- shopware_api_client/endpoints/admin/core/product_visibility.py +5 -18
- shopware_api_client/endpoints/admin/core/product_warehouse.py +5 -17
- shopware_api_client/endpoints/admin/core/promotion.py +5 -38
- shopware_api_client/endpoints/admin/core/promotion_discount.py +5 -24
- shopware_api_client/endpoints/admin/core/promotion_discount_prices.py +5 -19
- shopware_api_client/endpoints/admin/core/property_group.py +5 -24
- shopware_api_client/endpoints/admin/core/property_group_option.py +5 -23
- shopware_api_client/endpoints/admin/core/rule.py +5 -24
- shopware_api_client/endpoints/admin/core/rule_condition.py +5 -23
- shopware_api_client/endpoints/admin/core/sales_channel.py +6 -53
- shopware_api_client/endpoints/admin/core/sales_channel_domain.py +5 -23
- shopware_api_client/endpoints/admin/core/salutation.py +5 -20
- shopware_api_client/endpoints/admin/core/seo_url.py +5 -30
- shopware_api_client/endpoints/admin/core/shipping_method.py +8 -30
- shopware_api_client/endpoints/admin/core/shipping_method_price.py +20 -0
- shopware_api_client/endpoints/admin/core/state_machine.py +5 -21
- shopware_api_client/endpoints/admin/core/state_machine_history.py +5 -25
- shopware_api_client/endpoints/admin/core/state_machine_state.py +6 -20
- shopware_api_client/endpoints/admin/core/state_machine_transition.py +5 -23
- shopware_api_client/endpoints/admin/core/system_config.py +5 -19
- shopware_api_client/endpoints/admin/core/tag.py +5 -14
- shopware_api_client/endpoints/admin/core/tax.py +5 -21
- shopware_api_client/endpoints/admin/core/tax_rule.py +5 -22
- shopware_api_client/endpoints/admin/core/tax_rule_type.py +5 -21
- shopware_api_client/endpoints/admin/core/unit.py +5 -19
- shopware_api_client/endpoints/admin/core/user.py +5 -31
- shopware_api_client/endpoints/admin/core/warehouse.py +5 -15
- shopware_api_client/endpoints/admin/core/warehouse_group.py +6 -18
- shopware_api_client/endpoints/admin/core/warehouse_group_warehouse.py +6 -18
- shopware_api_client/endpoints/base_fields.py +13 -22
- shopware_api_client/endpoints/relations.py +36 -24
- shopware_api_client/endpoints/store/__init__.py +37 -4
- shopware_api_client/endpoints/store/core/address.py +51 -68
- shopware_api_client/endpoints/store/core/cart.py +39 -85
- shopware_api_client/endpoints/store/core/category.py +16 -0
- shopware_api_client/endpoints/store/core/cms_block.py +10 -0
- shopware_api_client/endpoints/store/core/cms_page.py +12 -0
- shopware_api_client/endpoints/store/core/cms_section.py +12 -0
- shopware_api_client/endpoints/store/core/cms_slot.py +8 -0
- shopware_api_client/endpoints/store/core/context.py +58 -0
- shopware_api_client/endpoints/store/core/country.py +15 -0
- shopware_api_client/endpoints/store/core/country_state.py +19 -0
- shopware_api_client/endpoints/store/core/currency.py +12 -0
- shopware_api_client/endpoints/store/core/customer.py +34 -0
- shopware_api_client/endpoints/store/core/customer_group.py +5 -0
- shopware_api_client/endpoints/store/core/delivery_time.py +5 -0
- shopware_api_client/endpoints/store/core/document.py +15 -0
- shopware_api_client/endpoints/store/core/document_type.py +5 -0
- shopware_api_client/endpoints/store/core/landing_page.py +10 -0
- shopware_api_client/endpoints/store/core/language.py +18 -0
- shopware_api_client/endpoints/store/core/locale.py +5 -0
- shopware_api_client/endpoints/store/core/main_category.py +5 -0
- shopware_api_client/endpoints/store/core/media.py +8 -0
- shopware_api_client/endpoints/store/core/media_thumbnail.py +5 -0
- shopware_api_client/endpoints/store/core/order.py +67 -0
- shopware_api_client/endpoints/store/core/order_address.py +12 -0
- shopware_api_client/endpoints/store/core/order_customer.py +8 -0
- shopware_api_client/endpoints/store/core/order_delivery.py +14 -0
- shopware_api_client/endpoints/store/core/order_delivery_position.py +5 -0
- shopware_api_client/endpoints/store/core/order_line_item.py +14 -0
- shopware_api_client/endpoints/store/core/order_transaction.py +12 -0
- shopware_api_client/endpoints/store/core/order_transaction_capture.py +12 -0
- shopware_api_client/endpoints/store/core/order_transaction_capture_refund.py +12 -0
- shopware_api_client/endpoints/store/core/order_transaction_capture_refund_position.py +11 -0
- shopware_api_client/endpoints/store/core/payment_method.py +8 -0
- shopware_api_client/endpoints/store/core/product.py +60 -0
- shopware_api_client/endpoints/store/core/product_configurator_setting.py +10 -0
- shopware_api_client/endpoints/store/core/product_cross_selling.py +5 -0
- shopware_api_client/endpoints/store/core/product_download.py +10 -0
- shopware_api_client/endpoints/store/core/product_manufacturer.py +8 -0
- shopware_api_client/endpoints/store/core/product_media.py +10 -0
- shopware_api_client/endpoints/store/core/product_review.py +5 -0
- shopware_api_client/endpoints/store/core/product_stream.py +5 -0
- shopware_api_client/endpoints/store/core/property_group.py +8 -0
- shopware_api_client/endpoints/store/core/property_group_option.py +10 -0
- shopware_api_client/endpoints/store/core/rule.py +5 -0
- shopware_api_client/endpoints/store/core/sales_channel.py +23 -0
- shopware_api_client/endpoints/store/core/sales_channel_domain.py +12 -0
- shopware_api_client/endpoints/store/core/salutation.py +12 -0
- shopware_api_client/endpoints/store/core/seo_url.py +5 -0
- shopware_api_client/endpoints/store/core/shipping_method.py +18 -0
- shopware_api_client/endpoints/store/core/shipping_method_price.py +5 -0
- shopware_api_client/endpoints/store/core/state_machine_state.py +5 -0
- shopware_api_client/endpoints/store/core/tag.py +5 -0
- shopware_api_client/endpoints/store/core/tax.py +5 -0
- shopware_api_client/endpoints/store/core/unit.py +5 -0
- shopware_api_client/exceptions.py +4 -0
- shopware_api_client/fieldsets.py +12 -0
- shopware_api_client/models/__init__.py +0 -0
- shopware_api_client/models/acl_role.py +11 -0
- shopware_api_client/models/app.py +33 -0
- shopware_api_client/models/app_script_condition.py +16 -0
- shopware_api_client/models/b2b_components_role.py +12 -0
- shopware_api_client/models/b2b_components_shopping_list.py +13 -0
- shopware_api_client/models/b2b_components_shopping_list_line_item.py +14 -0
- shopware_api_client/models/b2b_employee.py +17 -0
- shopware_api_client/models/category.py +44 -0
- shopware_api_client/models/cms_block.py +23 -0
- shopware_api_client/models/cms_page.py +15 -0
- shopware_api_client/models/cms_section.py +20 -0
- shopware_api_client/models/cms_slot.py +17 -0
- shopware_api_client/models/country.py +27 -0
- shopware_api_client/models/country_state.py +12 -0
- shopware_api_client/models/currency.py +20 -0
- shopware_api_client/models/currency_country_rounding.py +11 -0
- shopware_api_client/models/custom_entity.py +19 -0
- shopware_api_client/models/custom_field.py +8 -0
- shopware_api_client/models/customer.py +47 -0
- shopware_api_client/models/customer_address.py +22 -0
- shopware_api_client/models/customer_group.py +13 -0
- shopware_api_client/models/customer_recovery.py +9 -0
- shopware_api_client/models/customer_wishlist.py +9 -0
- shopware_api_client/models/customer_wishlist_product.py +10 -0
- shopware_api_client/models/delivery_time.py +12 -0
- shopware_api_client/models/document.py +20 -0
- shopware_api_client/models/document_base_config.py +19 -0
- shopware_api_client/models/document_base_config_sales_channel.py +10 -0
- shopware_api_client/models/document_type.py +8 -0
- shopware_api_client/models/dynamic_access.py +9 -0
- shopware_api_client/models/integration.py +15 -0
- shopware_api_client/models/landing_page.py +15 -0
- shopware_api_client/models/language.py +11 -0
- shopware_api_client/models/locale.py +9 -0
- shopware_api_client/models/main_category.py +12 -0
- shopware_api_client/models/media.py +26 -0
- shopware_api_client/models/media_default_folder.py +7 -0
- shopware_api_client/models/media_folder.py +16 -0
- shopware_api_client/models/media_folder_configuration.py +11 -0
- shopware_api_client/models/media_thumbnail.py +15 -0
- shopware_api_client/models/media_thumbnail_size.py +8 -0
- shopware_api_client/models/order.py +36 -0
- shopware_api_client/models/order_address.py +23 -0
- shopware_api_client/models/order_customer.py +18 -0
- shopware_api_client/models/order_delivery.py +19 -0
- shopware_api_client/models/order_delivery_position.py +15 -0
- shopware_api_client/models/order_line_item.py +34 -0
- shopware_api_client/models/order_line_item_download.py +12 -0
- shopware_api_client/models/order_transaction.py +15 -0
- shopware_api_client/models/order_transaction_capture.py +15 -0
- shopware_api_client/models/order_transaction_capture_refund.py +16 -0
- shopware_api_client/models/order_transaction_capture_refund_position.py +15 -0
- shopware_api_client/models/payment_method.py +29 -0
- shopware_api_client/models/product.py +72 -0
- shopware_api_client/models/product_configurator_setting.py +14 -0
- shopware_api_client/models/product_cross_selling.py +17 -0
- shopware_api_client/models/product_cross_selling_assigned_products.py +11 -0
- shopware_api_client/models/product_download.py +11 -0
- shopware_api_client/models/product_export.py +27 -0
- shopware_api_client/models/product_feature_set.py +11 -0
- shopware_api_client/models/product_manufacturer.py +11 -0
- shopware_api_client/models/product_media.py +11 -0
- shopware_api_client/models/product_price.py +15 -0
- shopware_api_client/models/product_review.py +19 -0
- shopware_api_client/models/product_search_keyword.py +13 -0
- shopware_api_client/models/product_stream.py +14 -0
- shopware_api_client/models/product_visibility.py +11 -0
- shopware_api_client/models/product_warehouse.py +10 -0
- shopware_api_client/models/promotion.py +29 -0
- shopware_api_client/models/promotion_discount.py +17 -0
- shopware_api_client/models/promotion_discount_prices.py +10 -0
- shopware_api_client/models/property_group.py +13 -0
- shopware_api_client/models/property_group_option.py +12 -0
- shopware_api_client/models/rule.py +16 -0
- shopware_api_client/models/rule_condition.py +15 -0
- shopware_api_client/models/sales_channel.py +44 -0
- shopware_api_client/models/sales_channel_domain.py +13 -0
- shopware_api_client/models/salutation.py +9 -0
- shopware_api_client/models/seo_url.py +19 -0
- shopware_api_client/models/shipping_method.py +18 -0
- shopware_api_client/models/shipping_method_price.py +15 -0
- shopware_api_client/models/state_machine.py +10 -0
- shopware_api_client/models/state_machine_history.py +18 -0
- shopware_api_client/models/state_machine_state.py +8 -0
- shopware_api_client/models/state_machine_transition.py +11 -0
- shopware_api_client/models/system_config.py +12 -0
- shopware_api_client/models/tag.py +7 -0
- shopware_api_client/models/tax.py +9 -0
- shopware_api_client/models/tax_rule.py +15 -0
- shopware_api_client/models/tax_rule_type.py +11 -0
- shopware_api_client/models/unit.py +8 -0
- shopware_api_client/models/user.py +21 -0
- shopware_api_client/models/warehouse.py +8 -0
- shopware_api_client/models/warehouse_group.py +11 -0
- shopware_api_client/models/warehouse_group_warehouse.py +11 -0
- shopware_api_client/structs/__init__.py +0 -0
- shopware_api_client/structs/absolute_price_definition.py +6 -0
- shopware_api_client/structs/calculated_cheapest_price.py +6 -0
- shopware_api_client/structs/calculated_price.py +17 -0
- shopware_api_client/structs/calculated_tax.py +8 -0
- shopware_api_client/structs/cart.py +21 -0
- shopware_api_client/structs/cart_price.py +15 -0
- shopware_api_client/structs/cash_rounding_config.py +7 -0
- shopware_api_client/structs/context.py +15 -0
- shopware_api_client/structs/delivery.py +16 -0
- shopware_api_client/structs/delivery_date.py +8 -0
- shopware_api_client/structs/delivery_information.py +13 -0
- shopware_api_client/structs/delivery_position.py +12 -0
- shopware_api_client/structs/delivery_time.py +8 -0
- shopware_api_client/structs/language_info.py +6 -0
- shopware_api_client/structs/line_item.py +39 -0
- shopware_api_client/structs/list_price.py +7 -0
- shopware_api_client/structs/measurement_units.py +8 -0
- shopware_api_client/structs/percentage_price_definition.py +6 -0
- shopware_api_client/structs/price.py +14 -0
- shopware_api_client/structs/quantity_information.py +7 -0
- shopware_api_client/structs/quantity_price_definition.py +14 -0
- shopware_api_client/structs/reference_price.py +5 -0
- shopware_api_client/structs/reference_price_definition.py +7 -0
- shopware_api_client/structs/regulation_price.py +5 -0
- shopware_api_client/structs/sales_channel_context.py +33 -0
- shopware_api_client/structs/shipping_location.py +12 -0
- shopware_api_client/structs/tax_free_config.py +8 -0
- shopware_api_client/structs/tax_rule.py +6 -0
- shopware_api_client/structs/transaction.py +8 -0
- shopware_api_client/structs/variant_listing_config.py +8 -0
- {shopware_api_client-1.0.118.dist-info → shopware_api_client-1.1.1.dist-info}/METADATA +45 -273
- shopware_api_client-1.1.1.dist-info/RECORD +298 -0
- shopware_api_client-1.0.118.dist-info/RECORD +0 -117
- {shopware_api_client-1.0.118.dist-info → shopware_api_client-1.1.1.dist-info}/WHEEL +0 -0
- {shopware_api_client-1.0.118.dist-info → shopware_api_client-1.1.1.dist-info}/licenses/LICENSE +0 -0
shopware_api_client/base.py
CHANGED
|
@@ -29,6 +29,7 @@ from pydantic import (
|
|
|
29
29
|
Field,
|
|
30
30
|
ValidationError,
|
|
31
31
|
model_serializer,
|
|
32
|
+
PydanticUserError,
|
|
32
33
|
)
|
|
33
34
|
from pydantic.alias_generators import to_camel
|
|
34
35
|
from pydantic.main import IncEx
|
|
@@ -47,6 +48,7 @@ from .exceptions import (
|
|
|
47
48
|
SWFilterException,
|
|
48
49
|
SWNoClientProvided,
|
|
49
50
|
)
|
|
51
|
+
from .fieldsets import FieldSetBase
|
|
50
52
|
from .logging import logger
|
|
51
53
|
|
|
52
54
|
if TYPE_CHECKING:
|
|
@@ -54,8 +56,12 @@ if TYPE_CHECKING:
|
|
|
54
56
|
|
|
55
57
|
APPLICATION_JSON = "application/json"
|
|
56
58
|
|
|
57
|
-
EndpointClass = TypeVar("EndpointClass", bound="EndpointBase
|
|
58
|
-
|
|
59
|
+
EndpointClass = TypeVar("EndpointClass", bound="EndpointBase")
|
|
60
|
+
AdminEndpointClass = TypeVar("AdminEndpointClass", bound="AdminEndpoint")
|
|
61
|
+
ModelClass = TypeVar("ModelClass", bound="ApiModelBase")
|
|
62
|
+
AdminModelClass = TypeVar("AdminModelClass", bound="AdminModel")
|
|
63
|
+
FieldSet = TypeVar("FieldSet", bound="FieldSetBase")
|
|
64
|
+
|
|
59
65
|
RETRY_CACHE_KEY = "shopware-api-client:retry:{url}:{method}"
|
|
60
66
|
HEADER_X_RATE_LIMIT_LIMIT = "X-Rate-Limit-Limit"
|
|
61
67
|
HEADER_X_RATE_LIMIT_REMAINING = "X-Rate-Limit-Remaining"
|
|
@@ -64,11 +70,11 @@ HEADER_X_RATE_LIMIT_RESET = "X-Rate-Limit-Reset"
|
|
|
64
70
|
|
|
65
71
|
class ConfigBase:
|
|
66
72
|
def __init__(
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
73
|
+
self,
|
|
74
|
+
url: str,
|
|
75
|
+
retry_after_threshold: int = 60,
|
|
76
|
+
redis_client: "Redis | None" = None,
|
|
77
|
+
local_cache_cleanup_cycle_seconds: int = 10,
|
|
72
78
|
) -> None:
|
|
73
79
|
self.url = url.rstrip("/")
|
|
74
80
|
self.retry_after_threshold = retry_after_threshold
|
|
@@ -140,7 +146,7 @@ class ClientBase:
|
|
|
140
146
|
|
|
141
147
|
async def sleep_and_increment(self, retry_wait_base: int, retry_count: int) -> int:
|
|
142
148
|
retry_count += 1
|
|
143
|
-
sleep_and_increment = retry_wait_base**retry_count
|
|
149
|
+
sleep_and_increment = retry_wait_base ** retry_count
|
|
144
150
|
logger.debug(f"Try failed, retrying in {sleep_and_increment} seconds.")
|
|
145
151
|
await asyncio.sleep(sleep_and_increment)
|
|
146
152
|
return retry_count
|
|
@@ -285,10 +291,10 @@ class ClientBase:
|
|
|
285
291
|
error = error.errors[0]
|
|
286
292
|
|
|
287
293
|
if isinstance(error, SWAPIErrorList):
|
|
288
|
-
if any(
|
|
294
|
+
if any(isinstance(err, no_retry_errors) for err in error.errors):
|
|
289
295
|
raise error
|
|
290
296
|
|
|
291
|
-
if not any(
|
|
297
|
+
if not any(isinstance(err, retry_errors) for err in error.errors):
|
|
292
298
|
raise error
|
|
293
299
|
|
|
294
300
|
elif isinstance(error, no_retry_errors) or not isinstance(error, retry_errors):
|
|
@@ -310,7 +316,7 @@ class ClientBase:
|
|
|
310
316
|
exception = SWAPIError.from_response(response)
|
|
311
317
|
# prefix details with x-trace-header to
|
|
312
318
|
exception.detail = (
|
|
313
|
-
|
|
319
|
+
f"x-trace-id: {str(response.headers.get('x-trace-id', 'not-set'))}" + exception.detail
|
|
314
320
|
)
|
|
315
321
|
raise exception
|
|
316
322
|
|
|
@@ -343,28 +349,51 @@ class ClientBase:
|
|
|
343
349
|
await self.http_client.aclose()
|
|
344
350
|
|
|
345
351
|
async def bulk_upsert(
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
352
|
+
self,
|
|
353
|
+
name: str,
|
|
354
|
+
objs: list[ModelClass] | list[dict[str, Any]],
|
|
355
|
+
fail_silently: bool = False,
|
|
356
|
+
**request_kwargs: Any,
|
|
351
357
|
) -> dict[str, Any]:
|
|
352
358
|
raise SWAPIException("bulk_upsert is only supported in the admin API")
|
|
353
359
|
|
|
354
360
|
async def bulk_delete(
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
361
|
+
self,
|
|
362
|
+
name: str,
|
|
363
|
+
objs: list[ModelClass] | list[dict[str, Any]],
|
|
364
|
+
fail_silently: bool = False,
|
|
365
|
+
**request_kwargs: Any,
|
|
360
366
|
) -> dict[str, Any]:
|
|
361
367
|
raise SWAPIException("bulk_delete is only supported in the admin API")
|
|
362
368
|
|
|
363
|
-
def set_language(self, language_id: IdField | None) -> None:
|
|
369
|
+
def set_language(self, language_id: "IdField | None") -> None:
|
|
364
370
|
self.language_id = language_id
|
|
365
371
|
|
|
366
372
|
|
|
367
|
-
class
|
|
373
|
+
class EndpointMixin(Generic[EndpointClass]):
|
|
374
|
+
def __init__(self, client: ClientBase | None = None, **kwargs: dict[str, Any]) -> None:
|
|
375
|
+
self._client: ClientBase | None = client
|
|
376
|
+
super().__init__(**kwargs)
|
|
377
|
+
|
|
378
|
+
@classmethod
|
|
379
|
+
def using(cls, client: ClientBase) -> EndpointClass:
|
|
380
|
+
# we want a fresh endpoint
|
|
381
|
+
endpoint: EndpointClass = getattr(client, cls._identifier.get_default()).__class__(client) # type: ignore
|
|
382
|
+
return endpoint
|
|
383
|
+
|
|
384
|
+
def _get_client(self) -> ClientBase:
|
|
385
|
+
if self._client is None:
|
|
386
|
+
raise SWNoClientProvided("Model has no api client set. Use `using` to set a client.")
|
|
387
|
+
return self._client
|
|
388
|
+
|
|
389
|
+
def _get_endpoint(self) -> EndpointClass:
|
|
390
|
+
# we want a fresh endpoint
|
|
391
|
+
client = self._get_client()
|
|
392
|
+
endpoint: EndpointClass = getattr(client, self._identifier).__class__(client) # type: ignore
|
|
393
|
+
return endpoint
|
|
394
|
+
|
|
395
|
+
|
|
396
|
+
class ApiModelBase(BaseModel):
|
|
368
397
|
model_config = ConfigDict(
|
|
369
398
|
alias_generator=AliasGenerator(
|
|
370
399
|
validation_alias=lambda field_name: AliasChoices(field_name, to_camel(field_name)),
|
|
@@ -373,13 +402,27 @@ class ApiModelBase(BaseModel, Generic[EndpointClass]):
|
|
|
373
402
|
validate_assignment=True,
|
|
374
403
|
)
|
|
375
404
|
|
|
376
|
-
id: IdField | None = None
|
|
377
|
-
|
|
405
|
+
id: "IdField | None" = None
|
|
406
|
+
version_id: IdField | None = None
|
|
407
|
+
translated: dict[str, Any] | list[Any] | None = None
|
|
408
|
+
created_at: AwareDatetime | None = Field(default_factory=lambda: datetime.now(UTC), exclude=True)
|
|
378
409
|
updated_at: AwareDatetime | None = Field(default=None, exclude=True)
|
|
379
410
|
|
|
380
411
|
def __init__(self, client: ClientBase | None = None, **kwargs: dict[str, Any]) -> None:
|
|
381
|
-
|
|
382
|
-
|
|
412
|
+
self._insert_translations(
|
|
413
|
+
data=kwargs,
|
|
414
|
+
translations=kwargs.get("translated")
|
|
415
|
+
)
|
|
416
|
+
|
|
417
|
+
try:
|
|
418
|
+
super().__init__(**kwargs)
|
|
419
|
+
except PydanticUserError:
|
|
420
|
+
self.model_rebuild()
|
|
421
|
+
super().__init__(**kwargs)
|
|
422
|
+
|
|
423
|
+
# Pydantic doesn't do a good job at calling the parents, so we have to help
|
|
424
|
+
if isinstance(self, EndpointMixin):
|
|
425
|
+
EndpointMixin.__init__(self, client=client)
|
|
383
426
|
|
|
384
427
|
def __setattr__(self, name: str, value: Any) -> Any:
|
|
385
428
|
from .endpoints.relations import ForeignRelation, ManyRelation
|
|
@@ -410,6 +453,17 @@ class ApiModelBase(BaseModel, Generic[EndpointClass]):
|
|
|
410
453
|
|
|
411
454
|
return super().__getattribute__(name)
|
|
412
455
|
|
|
456
|
+
@staticmethod
|
|
457
|
+
def _insert_translations(data: dict[str, Any], translations: dict[str, Any] | list[Any] | None) -> dict[str, Any]:
|
|
458
|
+
if not isinstance(translations, dict):
|
|
459
|
+
return data
|
|
460
|
+
|
|
461
|
+
for key, value in translations.items():
|
|
462
|
+
if value and data.get(key) is None:
|
|
463
|
+
data[key] = value
|
|
464
|
+
|
|
465
|
+
return data
|
|
466
|
+
|
|
413
467
|
@model_serializer(mode="wrap")
|
|
414
468
|
def ser_model(self, serializer: Callable[..., dict[str, Any]]) -> dict[str, Any]:
|
|
415
469
|
from .endpoints.relations import ForeignRelation, ManyRelation
|
|
@@ -429,24 +483,14 @@ class ApiModelBase(BaseModel, Generic[EndpointClass]):
|
|
|
429
483
|
|
|
430
484
|
return ser_dict
|
|
431
485
|
|
|
432
|
-
@classmethod
|
|
433
|
-
def using(cls: type[Self], client: ClientBase) -> EndpointClass:
|
|
434
|
-
# we want a fresh endpoint
|
|
435
|
-
endpoint: EndpointClass = getattr(client, cls._identifier.get_default()).__class__(client) # type: ignore
|
|
436
|
-
return endpoint
|
|
437
486
|
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
441
|
-
return self._client
|
|
442
|
-
|
|
443
|
-
def _get_endpoint(self) -> EndpointClass:
|
|
444
|
-
# we want a fresh endpoint
|
|
445
|
-
client = self._get_client()
|
|
446
|
-
endpoint: EndpointClass = getattr(client, self._identifier).__class__(client) # type: ignore
|
|
447
|
-
return endpoint
|
|
487
|
+
class AdminModel(ApiModelBase, EndpointMixin[AdminEndpointClass], Generic[AdminEndpointClass]):
|
|
488
|
+
def __init__(self, client: ClientBase | None = None, **kwargs: dict[str, Any]) -> None:
|
|
489
|
+
super().__init__(client, **kwargs)
|
|
448
490
|
|
|
449
|
-
async def save(
|
|
491
|
+
async def save(
|
|
492
|
+
self, force_insert: bool = False, update_fields: IncEx | None = None
|
|
493
|
+
) -> "AdminModel[Any] | dict | None":
|
|
450
494
|
endpoint = self._get_endpoint()
|
|
451
495
|
|
|
452
496
|
if force_insert or self.id is None:
|
|
@@ -466,15 +510,46 @@ class ApiModelBase(BaseModel, Generic[EndpointClass]):
|
|
|
466
510
|
return await endpoint.delete(pk=self.id)
|
|
467
511
|
|
|
468
512
|
|
|
469
|
-
class
|
|
513
|
+
class CustomFieldsMixin(BaseModel):
|
|
514
|
+
custom_fields: dict[str, Any] | None = Field(default=None)
|
|
515
|
+
|
|
516
|
+
|
|
517
|
+
class EndpointBase:
|
|
470
518
|
name: str
|
|
471
519
|
path: str
|
|
472
|
-
model_class: Type[ModelClass]
|
|
473
520
|
raw: bool
|
|
521
|
+
search_prefix: str = "/search"
|
|
474
522
|
|
|
475
|
-
def __init__(self, client: ClientBase):
|
|
523
|
+
def __init__(self, client: ClientBase, *args: Any, **kwargs: Any) -> None:
|
|
524
|
+
super().__init__(*args, **kwargs)
|
|
476
525
|
self.client = client
|
|
477
526
|
self.raw = client.raw
|
|
527
|
+
|
|
528
|
+
def _parse_data(self, response_dict: dict[str, Any]) -> list[dict[str, Any]]:
|
|
529
|
+
if "data" in response_dict:
|
|
530
|
+
key = "data"
|
|
531
|
+
elif "elements" in response_dict:
|
|
532
|
+
key = "elements"
|
|
533
|
+
else:
|
|
534
|
+
key = None
|
|
535
|
+
|
|
536
|
+
data: list[dict[str, Any]] | dict[str, Any] = response_dict[key] if key else response_dict
|
|
537
|
+
|
|
538
|
+
if isinstance(data, dict):
|
|
539
|
+
return [data]
|
|
540
|
+
|
|
541
|
+
return data
|
|
542
|
+
|
|
543
|
+
def _parse_data_single(self, reponse_dict: dict[str, Any]) -> dict[str, Any]:
|
|
544
|
+
return self._parse_data(reponse_dict)[0]
|
|
545
|
+
|
|
546
|
+
|
|
547
|
+
class EndpointSearchMixin(Generic[ModelClass]):
|
|
548
|
+
model_class: Type[ModelClass]
|
|
549
|
+
|
|
550
|
+
def __init__(self, *args: Any, **kwargs: Any):
|
|
551
|
+
super().__init__(*args, **kwargs)
|
|
552
|
+
|
|
478
553
|
self._filter: list[dict[str, Any]] = []
|
|
479
554
|
self._limit: int | None = None
|
|
480
555
|
self._page: int | None = None
|
|
@@ -533,24 +608,129 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
533
608
|
else:
|
|
534
609
|
return self.model_class.model_fields[name].serialization_alias or name
|
|
535
610
|
|
|
611
|
+
def select_related(self, **kwargs: Any) -> Self:
|
|
612
|
+
self._associations.update({self._serialize_field_name(field): data for field, data in kwargs.items()})
|
|
613
|
+
return self
|
|
614
|
+
|
|
615
|
+
def only(self, **kwargs: list[str]) -> Self:
|
|
616
|
+
for field, data in kwargs.items():
|
|
617
|
+
self._includes[self._serialize_field_name(field)] = [self._serialize_field_name(d) for d in data]
|
|
618
|
+
|
|
619
|
+
return self
|
|
620
|
+
|
|
621
|
+
def filter(self, **kwargs: Any) -> Self:
|
|
622
|
+
for key, value in kwargs.items():
|
|
623
|
+
filter_term = ""
|
|
624
|
+
filter_type = "equals"
|
|
625
|
+
|
|
626
|
+
field_parts = key.split("__")
|
|
627
|
+
|
|
628
|
+
if len(field_parts) > 1:
|
|
629
|
+
filter_term = field_parts[-1]
|
|
630
|
+
|
|
631
|
+
match filter_term:
|
|
632
|
+
case "in":
|
|
633
|
+
filter_type = "equalsAny"
|
|
634
|
+
case "contains":
|
|
635
|
+
filter_type = "contains"
|
|
636
|
+
case "gt":
|
|
637
|
+
filter_type = "range"
|
|
638
|
+
case "gte":
|
|
639
|
+
filter_type = "range"
|
|
640
|
+
case "lt":
|
|
641
|
+
filter_type = "range"
|
|
642
|
+
case "lte":
|
|
643
|
+
filter_type = "range"
|
|
644
|
+
case "range":
|
|
645
|
+
filter_type = "range"
|
|
646
|
+
case "startswith":
|
|
647
|
+
filter_type = "prefix"
|
|
648
|
+
case "endswith":
|
|
649
|
+
filter_type = "suffix"
|
|
650
|
+
case _:
|
|
651
|
+
filter_term = ""
|
|
652
|
+
|
|
653
|
+
if field_parts[0] not in self.model_class.model_fields:
|
|
654
|
+
raise SWFilterException(
|
|
655
|
+
f"Unknown Field: {field_parts[0]}. Available fields: {self.model_class.model_fields.keys()}"
|
|
656
|
+
)
|
|
657
|
+
|
|
658
|
+
if filter_term != "":
|
|
659
|
+
field_parts = field_parts[:-1]
|
|
660
|
+
|
|
661
|
+
if len(field_parts) >= 2:
|
|
662
|
+
field = "%s.%s" % (
|
|
663
|
+
self._serialize_field_name(field_parts[0]),
|
|
664
|
+
".".join(field_parts[1:]),
|
|
665
|
+
)
|
|
666
|
+
else:
|
|
667
|
+
field = self._serialize_field_name(field_parts[0])
|
|
668
|
+
|
|
669
|
+
parameters = {}
|
|
670
|
+
|
|
671
|
+
# range has additional parameters
|
|
672
|
+
if filter_type == "range":
|
|
673
|
+
if filter_term == "range":
|
|
674
|
+
parameters = {"gte": value[0], "lte": value[1]}
|
|
675
|
+
else:
|
|
676
|
+
parameters = {filter_term: value}
|
|
677
|
+
|
|
678
|
+
self._filter.append({"type": filter_type, "field": field, "value": value, "parameters": parameters})
|
|
679
|
+
|
|
680
|
+
return self
|
|
681
|
+
|
|
682
|
+
def limit(self, count: int | None) -> "Self":
|
|
683
|
+
self._limit = count
|
|
684
|
+
return self
|
|
685
|
+
|
|
686
|
+
def page(self, num: int | None) -> "Self":
|
|
687
|
+
self._page = num
|
|
688
|
+
return self
|
|
689
|
+
|
|
690
|
+
def order_by(self, fields: str | tuple[str]) -> "Self":
|
|
691
|
+
if isinstance(fields, str):
|
|
692
|
+
fields = (fields,)
|
|
693
|
+
|
|
694
|
+
for field in fields:
|
|
695
|
+
if field.startswith("-"):
|
|
696
|
+
field = field[1:]
|
|
697
|
+
order = "DESC"
|
|
698
|
+
else:
|
|
699
|
+
order = "ASC"
|
|
700
|
+
|
|
701
|
+
if field not in self.model_class.model_fields:
|
|
702
|
+
raise SWFilterException(
|
|
703
|
+
f"Unknown Field: {field}. Available fields: {self.model_class.model_fields.keys()}"
|
|
704
|
+
)
|
|
705
|
+
else:
|
|
706
|
+
field = self._serialize_field_name(field)
|
|
707
|
+
|
|
708
|
+
self._sort.append({"field": field, "order": order})
|
|
709
|
+
|
|
710
|
+
return self
|
|
711
|
+
|
|
712
|
+
|
|
713
|
+
class AdminEndpoint(EndpointBase, EndpointSearchMixin, Generic[AdminModelClass]):
|
|
714
|
+
model_class: Type[AdminModelClass]
|
|
715
|
+
|
|
536
716
|
@overload
|
|
537
|
-
def _parse_response(self, data: list[dict[str, Any]]) -> list[
|
|
717
|
+
def _parse_response(self, data: list[dict[str, Any]]) -> list[AdminModelClass]:
|
|
538
718
|
# typing overload
|
|
539
719
|
...
|
|
540
720
|
|
|
541
721
|
@overload
|
|
542
|
-
def _parse_response(self, data: dict[str, Any]) ->
|
|
722
|
+
def _parse_response(self, data: dict[str, Any]) -> AdminModelClass:
|
|
543
723
|
# typing overload
|
|
544
724
|
...
|
|
545
725
|
|
|
546
|
-
def _parse_response(self, data: list[dict[str, Any]] | dict[str, Any]) -> list[
|
|
726
|
+
def _parse_response(self, data: list[dict[str, Any]] | dict[str, Any]) -> list[AdminModelClass] | AdminModelClass:
|
|
547
727
|
single = False
|
|
548
728
|
|
|
549
729
|
if isinstance(data, dict):
|
|
550
730
|
single = True
|
|
551
731
|
data = [data]
|
|
552
732
|
|
|
553
|
-
result_list: list[
|
|
733
|
+
result_list: list[AdminModelClass] = []
|
|
554
734
|
errors = []
|
|
555
735
|
|
|
556
736
|
for entry in data:
|
|
@@ -582,31 +762,13 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
582
762
|
|
|
583
763
|
return result_list
|
|
584
764
|
|
|
585
|
-
def
|
|
586
|
-
if "data" in response_dict:
|
|
587
|
-
key = "data"
|
|
588
|
-
elif "elements" in response_dict:
|
|
589
|
-
key = "elements"
|
|
590
|
-
else:
|
|
591
|
-
key = None
|
|
592
|
-
|
|
593
|
-
data: list[dict[str, Any]] | dict[str, Any] = response_dict[key] if key else response_dict
|
|
594
|
-
|
|
595
|
-
if isinstance(data, dict):
|
|
596
|
-
return [data]
|
|
597
|
-
|
|
598
|
-
return data
|
|
599
|
-
|
|
600
|
-
def _parse_data_single(self, reponse_dict: dict[str, Any]) -> dict[str, Any]:
|
|
601
|
-
return self._parse_data(reponse_dict)[0]
|
|
602
|
-
|
|
603
|
-
async def all(self) -> list[ModelClass] | list[dict[str, Any]]:
|
|
765
|
+
async def all(self) -> list[AdminModelClass] | list[dict[str, Any]]:
|
|
604
766
|
data = self._get_data_dict()
|
|
605
767
|
|
|
606
768
|
if self._is_search_query():
|
|
607
|
-
result = await self.client.post(f"
|
|
769
|
+
result = await self.client.post(f"{self.search_prefix}{self.path}", json=data)
|
|
608
770
|
else:
|
|
609
|
-
result = await self.client.get(
|
|
771
|
+
result = await self.client.get(self.path, params=data)
|
|
610
772
|
|
|
611
773
|
result_data: list[dict[str, Any]] = self._parse_data(result.json())
|
|
612
774
|
|
|
@@ -617,7 +779,7 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
617
779
|
|
|
618
780
|
return self._parse_response(result_data)
|
|
619
781
|
|
|
620
|
-
async def get(self, pk: str) ->
|
|
782
|
+
async def get(self, pk: str) -> AdminModelClass | dict[str, Any]:
|
|
621
783
|
result = await self.client.get(f"{self.path}/{pk}")
|
|
622
784
|
result_data: dict[str, Any] = self._parse_data_single(result.json())
|
|
623
785
|
|
|
@@ -627,8 +789,8 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
627
789
|
return self._parse_response(result_data)
|
|
628
790
|
|
|
629
791
|
async def update(
|
|
630
|
-
|
|
631
|
-
) ->
|
|
792
|
+
self, pk: str, obj: AdminModelClass | dict[str, Any], update_fields: IncEx | None = None
|
|
793
|
+
) -> AdminModelClass | dict[str, Any] | None:
|
|
632
794
|
if isinstance(obj, ApiModelBase):
|
|
633
795
|
data = obj.model_dump_json(by_alias=True, include=update_fields)
|
|
634
796
|
else:
|
|
@@ -646,7 +808,7 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
646
808
|
|
|
647
809
|
return self._parse_response(result_data)
|
|
648
810
|
|
|
649
|
-
async def first(self) ->
|
|
811
|
+
async def first(self) -> AdminModelClass | dict[str, Any] | None:
|
|
650
812
|
self._limit = 1
|
|
651
813
|
result = await self.all()
|
|
652
814
|
|
|
@@ -658,7 +820,7 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
658
820
|
|
|
659
821
|
return result[0]
|
|
660
822
|
|
|
661
|
-
async def create(self, obj:
|
|
823
|
+
async def create(self, obj: AdminModelClass | dict[str, Any]) -> AdminModelClass | dict[str, Any] | None:
|
|
662
824
|
if isinstance(obj, ApiModelBase):
|
|
663
825
|
data = obj.model_dump_json(by_alias=True)
|
|
664
826
|
else:
|
|
@@ -684,7 +846,7 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
684
846
|
|
|
685
847
|
return False
|
|
686
848
|
|
|
687
|
-
async def get_related(self, parent:
|
|
849
|
+
async def get_related(self, parent: AdminModelClass, relation: str) -> list[AdminModelClass] | list[dict[str, Any]]:
|
|
688
850
|
parent_endpoint = parent._get_endpoint()
|
|
689
851
|
result = await self.client.get(f"{parent_endpoint.path}/{parent.id}/{relation}")
|
|
690
852
|
result_data: list[dict[str, Any]] = self._parse_data(result.json())
|
|
@@ -694,133 +856,121 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
694
856
|
|
|
695
857
|
return self._parse_response(result_data)
|
|
696
858
|
|
|
697
|
-
def
|
|
698
|
-
|
|
699
|
-
|
|
859
|
+
async def bulk_upsert(
|
|
860
|
+
self, objs: list[AdminModelClass] | list[dict[str, Any]], fail_silently: bool = False, **request_kwargs: Any
|
|
861
|
+
) -> dict[str, Any]:
|
|
862
|
+
return await self.client.bulk_upsert(name=self.name, objs=objs, fail_silently=fail_silently, **request_kwargs)
|
|
700
863
|
|
|
701
|
-
def
|
|
702
|
-
|
|
703
|
-
|
|
864
|
+
async def bulk_delete(
|
|
865
|
+
self, objs: list[AdminModelClass] | list[dict[str, Any]], fail_silently: bool = False, **request_kwargs: Any
|
|
866
|
+
) -> dict[str, Any]:
|
|
867
|
+
return await self.client.bulk_delete(name=self.name, objs=objs, fail_silently=fail_silently, **request_kwargs)
|
|
704
868
|
|
|
705
|
-
|
|
869
|
+
async def iter(self, batch_size: int = 100) -> AsyncGenerator[AdminModelClass | dict[str, Any], None]:
|
|
870
|
+
self._limit = batch_size
|
|
871
|
+
data = self._get_data_dict()
|
|
872
|
+
page = 1
|
|
706
873
|
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
874
|
+
if self._is_search_query():
|
|
875
|
+
url = f"/search{self.path}"
|
|
876
|
+
else:
|
|
877
|
+
url = self.path
|
|
711
878
|
|
|
712
|
-
|
|
879
|
+
while True:
|
|
880
|
+
data["page"] = page
|
|
881
|
+
if self._is_search_query():
|
|
882
|
+
result = await self.client.post(url, json=data)
|
|
883
|
+
else:
|
|
884
|
+
result = await self.client.get(url, params=data)
|
|
713
885
|
|
|
714
|
-
|
|
715
|
-
|
|
886
|
+
result_dict: dict[str, Any] = result.json()
|
|
887
|
+
result_data: list[dict[str, Any]] = self._parse_data(result_dict)
|
|
716
888
|
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
case "gt":
|
|
723
|
-
filter_type = "range"
|
|
724
|
-
case "gte":
|
|
725
|
-
filter_type = "range"
|
|
726
|
-
case "lt":
|
|
727
|
-
filter_type = "range"
|
|
728
|
-
case "lte":
|
|
729
|
-
filter_type = "range"
|
|
730
|
-
case "range":
|
|
731
|
-
filter_type = "range"
|
|
732
|
-
case "startswith":
|
|
733
|
-
filter_type = "prefix"
|
|
734
|
-
case "endswith":
|
|
735
|
-
filter_type = "suffix"
|
|
736
|
-
case _:
|
|
737
|
-
filter_term = ""
|
|
889
|
+
for entry in result_data:
|
|
890
|
+
if self.raw:
|
|
891
|
+
yield entry
|
|
892
|
+
else:
|
|
893
|
+
yield self._parse_response(entry)
|
|
738
894
|
|
|
739
|
-
if
|
|
740
|
-
|
|
741
|
-
|
|
742
|
-
|
|
895
|
+
if len(result_data) >= self._limit:
|
|
896
|
+
page += 1
|
|
897
|
+
else:
|
|
898
|
+
break
|
|
743
899
|
|
|
744
|
-
if filter_term != "":
|
|
745
|
-
field_parts = field_parts[:-1]
|
|
746
900
|
|
|
747
|
-
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
752
|
-
|
|
753
|
-
field = self._serialize_field_name(field_parts[0])
|
|
901
|
+
class StoreEndpoint(EndpointBase):
|
|
902
|
+
@overload
|
|
903
|
+
@staticmethod
|
|
904
|
+
def _parse_response(data: list[dict[str, Any]], cls: Type[ModelClass | FieldSet]) -> list[ModelClass | FieldSet]:
|
|
905
|
+
# typing overload
|
|
906
|
+
...
|
|
754
907
|
|
|
755
|
-
|
|
908
|
+
@overload
|
|
909
|
+
@staticmethod
|
|
910
|
+
def _parse_response(data: dict[str, Any], cls: Type[ModelClass | FieldSet]) -> ModelClass | FieldSet:
|
|
911
|
+
# typing overload
|
|
912
|
+
...
|
|
756
913
|
|
|
757
|
-
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
761
|
-
|
|
762
|
-
parameters = {filter_term: value}
|
|
914
|
+
@staticmethod
|
|
915
|
+
def _parse_response(
|
|
916
|
+
data: list[dict[str, Any]] | dict[str, Any], cls: Type[ModelClass | FieldSet]
|
|
917
|
+
) -> list[ModelClass | FieldSet] | ModelClass | FieldSet:
|
|
918
|
+
single = False
|
|
763
919
|
|
|
764
|
-
|
|
920
|
+
if isinstance(data, dict):
|
|
921
|
+
single = True
|
|
922
|
+
data = [data]
|
|
765
923
|
|
|
766
|
-
|
|
924
|
+
result_list: list[ModelClass | FieldSet] = []
|
|
925
|
+
errors = []
|
|
767
926
|
|
|
768
|
-
|
|
769
|
-
|
|
770
|
-
|
|
771
|
-
|
|
927
|
+
for entry in data:
|
|
928
|
+
try:
|
|
929
|
+
obj = cls(**entry)
|
|
930
|
+
except ValidationError as exc:
|
|
931
|
+
# catch pydantic validation errors, log faulty result with tracking data and attach to errors
|
|
932
|
+
# (errors will be raised after checking all result objects)
|
|
933
|
+
logger.error(
|
|
934
|
+
"Invalid Shopware data",
|
|
935
|
+
extra={"ModelClass": cls, "id": entry.get("id"), "data": entry, "detail": str(exc)},
|
|
936
|
+
)
|
|
937
|
+
errors.append(exc)
|
|
938
|
+
continue
|
|
772
939
|
|
|
773
|
-
|
|
774
|
-
self, objs: list[ModelClass] | list[dict[str, Any]], fail_silently: bool = False, **request_kwargs: Any
|
|
775
|
-
) -> dict[str, Any]:
|
|
776
|
-
return await self.client.bulk_delete(name=self.name, objs=objs, fail_silently=fail_silently, **request_kwargs)
|
|
940
|
+
result_list.append(obj)
|
|
777
941
|
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
return self
|
|
942
|
+
if errors:
|
|
943
|
+
raise SWAPIDataValidationError(errors=errors)
|
|
781
944
|
|
|
782
|
-
|
|
783
|
-
|
|
784
|
-
return self
|
|
945
|
+
if single:
|
|
946
|
+
return result_list[0]
|
|
785
947
|
|
|
786
|
-
|
|
787
|
-
if isinstance(fields, str):
|
|
788
|
-
fields = (fields,)
|
|
948
|
+
return result_list
|
|
789
949
|
|
|
790
|
-
for field in fields:
|
|
791
|
-
if field.startswith("-"):
|
|
792
|
-
field = field[1:]
|
|
793
|
-
order = "DESC"
|
|
794
|
-
else:
|
|
795
|
-
order = "ASC"
|
|
796
950
|
|
|
797
|
-
|
|
798
|
-
|
|
799
|
-
f"Unknown Field: {field}. Available fields: {self.model_class.model_fields.keys()}"
|
|
800
|
-
)
|
|
801
|
-
else:
|
|
802
|
-
field = self._serialize_field_name(field)
|
|
951
|
+
class StoreSearchEndpoint(StoreEndpoint, EndpointSearchMixin, Generic[ModelClass]):
|
|
952
|
+
path: str
|
|
803
953
|
|
|
804
|
-
|
|
954
|
+
async def all(self) -> list[ModelClass] | list[dict[str, Any]]:
|
|
955
|
+
data = self._get_data_dict()
|
|
805
956
|
|
|
806
|
-
|
|
957
|
+
result = await self.client.post(self.path, json=data)
|
|
958
|
+
|
|
959
|
+
result_data: list[dict[str, Any]] = result.json().get("elements", [])
|
|
960
|
+
|
|
961
|
+
if self.raw:
|
|
962
|
+
return result_data
|
|
963
|
+
|
|
964
|
+
return self._parse_response(result_data, cls=self.model_class)
|
|
807
965
|
|
|
808
966
|
async def iter(self, batch_size: int = 100) -> AsyncGenerator[ModelClass | dict[str, Any], None]:
|
|
809
967
|
self._limit = batch_size
|
|
810
968
|
data = self._get_data_dict()
|
|
811
969
|
page = 1
|
|
812
970
|
|
|
813
|
-
if self._is_search_query():
|
|
814
|
-
url = f"/search{self.path}"
|
|
815
|
-
else:
|
|
816
|
-
url = self.path
|
|
817
|
-
|
|
818
971
|
while True:
|
|
819
972
|
data["page"] = page
|
|
820
|
-
|
|
821
|
-
result = await self.client.post(url, json=data)
|
|
822
|
-
else:
|
|
823
|
-
result = await self.client.get(url, params=data)
|
|
973
|
+
result = await self.client.post(self.path, json=data)
|
|
824
974
|
|
|
825
975
|
result_dict: dict[str, Any] = result.json()
|
|
826
976
|
result_data: list[dict[str, Any]] = self._parse_data(result_dict)
|
|
@@ -829,9 +979,21 @@ class EndpointBase(Generic[ModelClass]):
|
|
|
829
979
|
if self.raw:
|
|
830
980
|
yield entry
|
|
831
981
|
else:
|
|
832
|
-
yield self._parse_response(entry)
|
|
982
|
+
yield self._parse_response(entry, cls=self.model_class)
|
|
833
983
|
|
|
834
|
-
if len(result_data)
|
|
984
|
+
if "next" in result_dict.get("links", {}) and len(result_data) > 0:
|
|
835
985
|
page += 1
|
|
836
986
|
else:
|
|
837
987
|
break
|
|
988
|
+
|
|
989
|
+
async def first(self) -> ModelClass | dict[str, Any] | None:
|
|
990
|
+
self._limit = 1
|
|
991
|
+
result = await self.all()
|
|
992
|
+
|
|
993
|
+
self._reset_endpoint()
|
|
994
|
+
|
|
995
|
+
# return None instead of an KeyError, if result is empty
|
|
996
|
+
if len(result) == 0:
|
|
997
|
+
return None
|
|
998
|
+
|
|
999
|
+
return result[0]
|