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.
Files changed (290) hide show
  1. shopware_api_client/base.py +338 -176
  2. shopware_api_client/cache.py +5 -10
  3. shopware_api_client/endpoints/admin/__init__.py +11 -4
  4. shopware_api_client/endpoints/admin/commercial/b2b_components_role.py +5 -20
  5. shopware_api_client/endpoints/admin/commercial/b2b_components_shopping_list.py +5 -25
  6. shopware_api_client/endpoints/admin/commercial/b2b_components_shopping_list_line_item.py +7 -24
  7. shopware_api_client/endpoints/admin/commercial/b2b_employee.py +5 -27
  8. shopware_api_client/endpoints/admin/commercial/dynamic_access.py +9 -19
  9. shopware_api_client/endpoints/admin/core/acl_role.py +5 -19
  10. shopware_api_client/endpoints/admin/core/api_info.py +11 -12
  11. shopware_api_client/endpoints/admin/core/app.py +5 -42
  12. shopware_api_client/endpoints/admin/core/app_script_condition.py +5 -24
  13. shopware_api_client/endpoints/admin/core/category.py +5 -56
  14. shopware_api_client/endpoints/admin/core/cms_block.py +5 -34
  15. shopware_api_client/endpoints/admin/core/cms_page.py +5 -26
  16. shopware_api_client/endpoints/admin/core/cms_section.py +5 -35
  17. shopware_api_client/endpoints/admin/core/cms_slot.py +5 -31
  18. shopware_api_client/endpoints/admin/core/country.py +5 -37
  19. shopware_api_client/endpoints/admin/core/country_state.py +5 -23
  20. shopware_api_client/endpoints/admin/core/currency.py +5 -33
  21. shopware_api_client/endpoints/admin/core/currency_country_rounding.py +5 -20
  22. shopware_api_client/endpoints/admin/core/custom_entity.py +4 -26
  23. shopware_api_client/endpoints/admin/core/custom_field.py +4 -14
  24. shopware_api_client/endpoints/admin/core/customer.py +5 -57
  25. shopware_api_client/endpoints/admin/core/customer_address.py +6 -33
  26. shopware_api_client/endpoints/admin/core/customer_group.py +5 -24
  27. shopware_api_client/endpoints/admin/core/customer_recovery.py +5 -16
  28. shopware_api_client/endpoints/admin/core/customer_wishlist.py +5 -19
  29. shopware_api_client/endpoints/admin/core/customer_wishlist_product.py +5 -19
  30. shopware_api_client/endpoints/admin/core/delivery_time.py +5 -21
  31. shopware_api_client/endpoints/admin/core/document.py +5 -28
  32. shopware_api_client/endpoints/admin/core/document_base_config.py +5 -27
  33. shopware_api_client/endpoints/admin/core/document_base_config_sales_channel.py +7 -20
  34. shopware_api_client/endpoints/admin/core/document_type.py +5 -19
  35. shopware_api_client/endpoints/admin/core/integration.py +5 -25
  36. shopware_api_client/endpoints/admin/core/landing_page.py +5 -28
  37. shopware_api_client/endpoints/admin/core/language.py +5 -21
  38. shopware_api_client/endpoints/admin/core/locale.py +5 -20
  39. shopware_api_client/endpoints/admin/core/main_category.py +5 -19
  40. shopware_api_client/endpoints/admin/core/media.py +25 -37
  41. shopware_api_client/endpoints/admin/core/media_default_folder.py +4 -17
  42. shopware_api_client/endpoints/admin/core/media_folder.py +5 -26
  43. shopware_api_client/endpoints/admin/core/media_folder_configuration.py +5 -21
  44. shopware_api_client/endpoints/admin/core/media_thumbnail.py +5 -26
  45. shopware_api_client/endpoints/admin/core/media_thumbnail_size.py +7 -18
  46. shopware_api_client/endpoints/admin/core/order.py +12 -48
  47. shopware_api_client/endpoints/admin/core/order_address.py +6 -34
  48. shopware_api_client/endpoints/admin/core/order_customer.py +6 -29
  49. shopware_api_client/endpoints/admin/core/order_delivery.py +5 -30
  50. shopware_api_client/endpoints/admin/core/order_delivery_position.py +5 -26
  51. shopware_api_client/endpoints/admin/core/order_line_item.py +7 -44
  52. shopware_api_client/endpoints/admin/core/order_line_item_download.py +5 -23
  53. shopware_api_client/endpoints/admin/core/order_transaction.py +5 -25
  54. shopware_api_client/endpoints/admin/core/order_transaction_capture.py +5 -27
  55. shopware_api_client/endpoints/admin/core/order_transaction_capture_refund.py +7 -28
  56. shopware_api_client/endpoints/admin/core/order_transaction_capture_refund_position.py +8 -29
  57. shopware_api_client/endpoints/admin/core/payment_method.py +5 -52
  58. shopware_api_client/endpoints/admin/core/product.py +24 -90
  59. shopware_api_client/endpoints/admin/core/product_configurator_setting.py +5 -26
  60. shopware_api_client/endpoints/admin/core/product_cross_selling.py +5 -27
  61. shopware_api_client/endpoints/admin/core/product_cross_selling_assigned_products.py +7 -21
  62. shopware_api_client/endpoints/admin/core/product_download.py +5 -22
  63. shopware_api_client/endpoints/admin/core/product_export.py +5 -34
  64. shopware_api_client/endpoints/admin/core/product_feature_set.py +5 -19
  65. shopware_api_client/endpoints/admin/core/product_manufacturer.py +5 -23
  66. shopware_api_client/endpoints/admin/core/product_media.py +5 -22
  67. shopware_api_client/endpoints/admin/core/product_price.py +5 -24
  68. shopware_api_client/endpoints/admin/core/product_review.py +5 -29
  69. shopware_api_client/endpoints/admin/core/product_search_keyword.py +5 -20
  70. shopware_api_client/endpoints/admin/core/product_stream.py +5 -23
  71. shopware_api_client/endpoints/admin/core/product_visibility.py +5 -18
  72. shopware_api_client/endpoints/admin/core/product_warehouse.py +5 -17
  73. shopware_api_client/endpoints/admin/core/promotion.py +5 -38
  74. shopware_api_client/endpoints/admin/core/promotion_discount.py +5 -24
  75. shopware_api_client/endpoints/admin/core/promotion_discount_prices.py +5 -19
  76. shopware_api_client/endpoints/admin/core/property_group.py +5 -24
  77. shopware_api_client/endpoints/admin/core/property_group_option.py +5 -23
  78. shopware_api_client/endpoints/admin/core/rule.py +5 -24
  79. shopware_api_client/endpoints/admin/core/rule_condition.py +5 -23
  80. shopware_api_client/endpoints/admin/core/sales_channel.py +6 -53
  81. shopware_api_client/endpoints/admin/core/sales_channel_domain.py +5 -23
  82. shopware_api_client/endpoints/admin/core/salutation.py +5 -20
  83. shopware_api_client/endpoints/admin/core/seo_url.py +5 -30
  84. shopware_api_client/endpoints/admin/core/shipping_method.py +8 -30
  85. shopware_api_client/endpoints/admin/core/shipping_method_price.py +20 -0
  86. shopware_api_client/endpoints/admin/core/state_machine.py +5 -21
  87. shopware_api_client/endpoints/admin/core/state_machine_history.py +5 -25
  88. shopware_api_client/endpoints/admin/core/state_machine_state.py +6 -20
  89. shopware_api_client/endpoints/admin/core/state_machine_transition.py +5 -23
  90. shopware_api_client/endpoints/admin/core/system_config.py +5 -19
  91. shopware_api_client/endpoints/admin/core/tag.py +5 -14
  92. shopware_api_client/endpoints/admin/core/tax.py +5 -21
  93. shopware_api_client/endpoints/admin/core/tax_rule.py +5 -22
  94. shopware_api_client/endpoints/admin/core/tax_rule_type.py +5 -21
  95. shopware_api_client/endpoints/admin/core/unit.py +5 -19
  96. shopware_api_client/endpoints/admin/core/user.py +5 -31
  97. shopware_api_client/endpoints/admin/core/warehouse.py +5 -15
  98. shopware_api_client/endpoints/admin/core/warehouse_group.py +6 -18
  99. shopware_api_client/endpoints/admin/core/warehouse_group_warehouse.py +6 -18
  100. shopware_api_client/endpoints/base_fields.py +13 -22
  101. shopware_api_client/endpoints/relations.py +36 -24
  102. shopware_api_client/endpoints/store/__init__.py +37 -4
  103. shopware_api_client/endpoints/store/core/address.py +51 -68
  104. shopware_api_client/endpoints/store/core/cart.py +39 -85
  105. shopware_api_client/endpoints/store/core/category.py +16 -0
  106. shopware_api_client/endpoints/store/core/cms_block.py +10 -0
  107. shopware_api_client/endpoints/store/core/cms_page.py +12 -0
  108. shopware_api_client/endpoints/store/core/cms_section.py +12 -0
  109. shopware_api_client/endpoints/store/core/cms_slot.py +8 -0
  110. shopware_api_client/endpoints/store/core/context.py +58 -0
  111. shopware_api_client/endpoints/store/core/country.py +15 -0
  112. shopware_api_client/endpoints/store/core/country_state.py +19 -0
  113. shopware_api_client/endpoints/store/core/currency.py +12 -0
  114. shopware_api_client/endpoints/store/core/customer.py +34 -0
  115. shopware_api_client/endpoints/store/core/customer_group.py +5 -0
  116. shopware_api_client/endpoints/store/core/delivery_time.py +5 -0
  117. shopware_api_client/endpoints/store/core/document.py +15 -0
  118. shopware_api_client/endpoints/store/core/document_type.py +5 -0
  119. shopware_api_client/endpoints/store/core/landing_page.py +10 -0
  120. shopware_api_client/endpoints/store/core/language.py +18 -0
  121. shopware_api_client/endpoints/store/core/locale.py +5 -0
  122. shopware_api_client/endpoints/store/core/main_category.py +5 -0
  123. shopware_api_client/endpoints/store/core/media.py +8 -0
  124. shopware_api_client/endpoints/store/core/media_thumbnail.py +5 -0
  125. shopware_api_client/endpoints/store/core/order.py +67 -0
  126. shopware_api_client/endpoints/store/core/order_address.py +12 -0
  127. shopware_api_client/endpoints/store/core/order_customer.py +8 -0
  128. shopware_api_client/endpoints/store/core/order_delivery.py +14 -0
  129. shopware_api_client/endpoints/store/core/order_delivery_position.py +5 -0
  130. shopware_api_client/endpoints/store/core/order_line_item.py +14 -0
  131. shopware_api_client/endpoints/store/core/order_transaction.py +12 -0
  132. shopware_api_client/endpoints/store/core/order_transaction_capture.py +12 -0
  133. shopware_api_client/endpoints/store/core/order_transaction_capture_refund.py +12 -0
  134. shopware_api_client/endpoints/store/core/order_transaction_capture_refund_position.py +11 -0
  135. shopware_api_client/endpoints/store/core/payment_method.py +8 -0
  136. shopware_api_client/endpoints/store/core/product.py +60 -0
  137. shopware_api_client/endpoints/store/core/product_configurator_setting.py +10 -0
  138. shopware_api_client/endpoints/store/core/product_cross_selling.py +5 -0
  139. shopware_api_client/endpoints/store/core/product_download.py +10 -0
  140. shopware_api_client/endpoints/store/core/product_manufacturer.py +8 -0
  141. shopware_api_client/endpoints/store/core/product_media.py +10 -0
  142. shopware_api_client/endpoints/store/core/product_review.py +5 -0
  143. shopware_api_client/endpoints/store/core/product_stream.py +5 -0
  144. shopware_api_client/endpoints/store/core/property_group.py +8 -0
  145. shopware_api_client/endpoints/store/core/property_group_option.py +10 -0
  146. shopware_api_client/endpoints/store/core/rule.py +5 -0
  147. shopware_api_client/endpoints/store/core/sales_channel.py +23 -0
  148. shopware_api_client/endpoints/store/core/sales_channel_domain.py +12 -0
  149. shopware_api_client/endpoints/store/core/salutation.py +12 -0
  150. shopware_api_client/endpoints/store/core/seo_url.py +5 -0
  151. shopware_api_client/endpoints/store/core/shipping_method.py +18 -0
  152. shopware_api_client/endpoints/store/core/shipping_method_price.py +5 -0
  153. shopware_api_client/endpoints/store/core/state_machine_state.py +5 -0
  154. shopware_api_client/endpoints/store/core/tag.py +5 -0
  155. shopware_api_client/endpoints/store/core/tax.py +5 -0
  156. shopware_api_client/endpoints/store/core/unit.py +5 -0
  157. shopware_api_client/exceptions.py +4 -0
  158. shopware_api_client/fieldsets.py +12 -0
  159. shopware_api_client/models/__init__.py +0 -0
  160. shopware_api_client/models/acl_role.py +11 -0
  161. shopware_api_client/models/app.py +33 -0
  162. shopware_api_client/models/app_script_condition.py +16 -0
  163. shopware_api_client/models/b2b_components_role.py +12 -0
  164. shopware_api_client/models/b2b_components_shopping_list.py +13 -0
  165. shopware_api_client/models/b2b_components_shopping_list_line_item.py +14 -0
  166. shopware_api_client/models/b2b_employee.py +17 -0
  167. shopware_api_client/models/category.py +44 -0
  168. shopware_api_client/models/cms_block.py +23 -0
  169. shopware_api_client/models/cms_page.py +15 -0
  170. shopware_api_client/models/cms_section.py +20 -0
  171. shopware_api_client/models/cms_slot.py +17 -0
  172. shopware_api_client/models/country.py +27 -0
  173. shopware_api_client/models/country_state.py +12 -0
  174. shopware_api_client/models/currency.py +20 -0
  175. shopware_api_client/models/currency_country_rounding.py +11 -0
  176. shopware_api_client/models/custom_entity.py +19 -0
  177. shopware_api_client/models/custom_field.py +8 -0
  178. shopware_api_client/models/customer.py +47 -0
  179. shopware_api_client/models/customer_address.py +22 -0
  180. shopware_api_client/models/customer_group.py +13 -0
  181. shopware_api_client/models/customer_recovery.py +9 -0
  182. shopware_api_client/models/customer_wishlist.py +9 -0
  183. shopware_api_client/models/customer_wishlist_product.py +10 -0
  184. shopware_api_client/models/delivery_time.py +12 -0
  185. shopware_api_client/models/document.py +20 -0
  186. shopware_api_client/models/document_base_config.py +19 -0
  187. shopware_api_client/models/document_base_config_sales_channel.py +10 -0
  188. shopware_api_client/models/document_type.py +8 -0
  189. shopware_api_client/models/dynamic_access.py +9 -0
  190. shopware_api_client/models/integration.py +15 -0
  191. shopware_api_client/models/landing_page.py +15 -0
  192. shopware_api_client/models/language.py +11 -0
  193. shopware_api_client/models/locale.py +9 -0
  194. shopware_api_client/models/main_category.py +12 -0
  195. shopware_api_client/models/media.py +26 -0
  196. shopware_api_client/models/media_default_folder.py +7 -0
  197. shopware_api_client/models/media_folder.py +16 -0
  198. shopware_api_client/models/media_folder_configuration.py +11 -0
  199. shopware_api_client/models/media_thumbnail.py +15 -0
  200. shopware_api_client/models/media_thumbnail_size.py +8 -0
  201. shopware_api_client/models/order.py +36 -0
  202. shopware_api_client/models/order_address.py +23 -0
  203. shopware_api_client/models/order_customer.py +18 -0
  204. shopware_api_client/models/order_delivery.py +19 -0
  205. shopware_api_client/models/order_delivery_position.py +15 -0
  206. shopware_api_client/models/order_line_item.py +34 -0
  207. shopware_api_client/models/order_line_item_download.py +12 -0
  208. shopware_api_client/models/order_transaction.py +15 -0
  209. shopware_api_client/models/order_transaction_capture.py +15 -0
  210. shopware_api_client/models/order_transaction_capture_refund.py +16 -0
  211. shopware_api_client/models/order_transaction_capture_refund_position.py +15 -0
  212. shopware_api_client/models/payment_method.py +29 -0
  213. shopware_api_client/models/product.py +72 -0
  214. shopware_api_client/models/product_configurator_setting.py +14 -0
  215. shopware_api_client/models/product_cross_selling.py +17 -0
  216. shopware_api_client/models/product_cross_selling_assigned_products.py +11 -0
  217. shopware_api_client/models/product_download.py +11 -0
  218. shopware_api_client/models/product_export.py +27 -0
  219. shopware_api_client/models/product_feature_set.py +11 -0
  220. shopware_api_client/models/product_manufacturer.py +11 -0
  221. shopware_api_client/models/product_media.py +11 -0
  222. shopware_api_client/models/product_price.py +15 -0
  223. shopware_api_client/models/product_review.py +19 -0
  224. shopware_api_client/models/product_search_keyword.py +13 -0
  225. shopware_api_client/models/product_stream.py +14 -0
  226. shopware_api_client/models/product_visibility.py +11 -0
  227. shopware_api_client/models/product_warehouse.py +10 -0
  228. shopware_api_client/models/promotion.py +29 -0
  229. shopware_api_client/models/promotion_discount.py +17 -0
  230. shopware_api_client/models/promotion_discount_prices.py +10 -0
  231. shopware_api_client/models/property_group.py +13 -0
  232. shopware_api_client/models/property_group_option.py +12 -0
  233. shopware_api_client/models/rule.py +16 -0
  234. shopware_api_client/models/rule_condition.py +15 -0
  235. shopware_api_client/models/sales_channel.py +44 -0
  236. shopware_api_client/models/sales_channel_domain.py +13 -0
  237. shopware_api_client/models/salutation.py +9 -0
  238. shopware_api_client/models/seo_url.py +19 -0
  239. shopware_api_client/models/shipping_method.py +18 -0
  240. shopware_api_client/models/shipping_method_price.py +15 -0
  241. shopware_api_client/models/state_machine.py +10 -0
  242. shopware_api_client/models/state_machine_history.py +18 -0
  243. shopware_api_client/models/state_machine_state.py +8 -0
  244. shopware_api_client/models/state_machine_transition.py +11 -0
  245. shopware_api_client/models/system_config.py +12 -0
  246. shopware_api_client/models/tag.py +7 -0
  247. shopware_api_client/models/tax.py +9 -0
  248. shopware_api_client/models/tax_rule.py +15 -0
  249. shopware_api_client/models/tax_rule_type.py +11 -0
  250. shopware_api_client/models/unit.py +8 -0
  251. shopware_api_client/models/user.py +21 -0
  252. shopware_api_client/models/warehouse.py +8 -0
  253. shopware_api_client/models/warehouse_group.py +11 -0
  254. shopware_api_client/models/warehouse_group_warehouse.py +11 -0
  255. shopware_api_client/structs/__init__.py +0 -0
  256. shopware_api_client/structs/absolute_price_definition.py +6 -0
  257. shopware_api_client/structs/calculated_cheapest_price.py +6 -0
  258. shopware_api_client/structs/calculated_price.py +17 -0
  259. shopware_api_client/structs/calculated_tax.py +8 -0
  260. shopware_api_client/structs/cart.py +21 -0
  261. shopware_api_client/structs/cart_price.py +15 -0
  262. shopware_api_client/structs/cash_rounding_config.py +7 -0
  263. shopware_api_client/structs/context.py +15 -0
  264. shopware_api_client/structs/delivery.py +16 -0
  265. shopware_api_client/structs/delivery_date.py +8 -0
  266. shopware_api_client/structs/delivery_information.py +13 -0
  267. shopware_api_client/structs/delivery_position.py +12 -0
  268. shopware_api_client/structs/delivery_time.py +8 -0
  269. shopware_api_client/structs/language_info.py +6 -0
  270. shopware_api_client/structs/line_item.py +39 -0
  271. shopware_api_client/structs/list_price.py +7 -0
  272. shopware_api_client/structs/measurement_units.py +8 -0
  273. shopware_api_client/structs/percentage_price_definition.py +6 -0
  274. shopware_api_client/structs/price.py +14 -0
  275. shopware_api_client/structs/quantity_information.py +7 -0
  276. shopware_api_client/structs/quantity_price_definition.py +14 -0
  277. shopware_api_client/structs/reference_price.py +5 -0
  278. shopware_api_client/structs/reference_price_definition.py +7 -0
  279. shopware_api_client/structs/regulation_price.py +5 -0
  280. shopware_api_client/structs/sales_channel_context.py +33 -0
  281. shopware_api_client/structs/shipping_location.py +12 -0
  282. shopware_api_client/structs/tax_free_config.py +8 -0
  283. shopware_api_client/structs/tax_rule.py +6 -0
  284. shopware_api_client/structs/transaction.py +8 -0
  285. shopware_api_client/structs/variant_listing_config.py +8 -0
  286. {shopware_api_client-1.0.118.dist-info → shopware_api_client-1.1.1.dist-info}/METADATA +45 -273
  287. shopware_api_client-1.1.1.dist-info/RECORD +298 -0
  288. shopware_api_client-1.0.118.dist-info/RECORD +0 -117
  289. {shopware_api_client-1.0.118.dist-info → shopware_api_client-1.1.1.dist-info}/WHEEL +0 -0
  290. {shopware_api_client-1.0.118.dist-info → shopware_api_client-1.1.1.dist-info}/licenses/LICENSE +0 -0
@@ -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[Any]")
58
- ModelClass = TypeVar("ModelClass", bound="ApiModelBase[Any]")
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
- self,
68
- url: str,
69
- retry_after_threshold: int = 60,
70
- redis_client: "Redis | None" = None,
71
- local_cache_cleanup_cycle_seconds: int = 10,
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([isinstance(err, no_retry_errors) for err in error.errors]):
294
+ if any(isinstance(err, no_retry_errors) for err in error.errors):
289
295
  raise error
290
296
 
291
- if not any([isinstance(err, retry_errors) for err in error.errors]):
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
- f"x-trace-id: {str(response.headers.get('x-trace-id', 'not-set'))}" + exception.detail
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
- self,
347
- name: str,
348
- objs: list[ModelClass] | list[dict[str, Any]],
349
- fail_silently: bool = False,
350
- **request_kwargs: Any,
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
- self,
356
- name: str,
357
- objs: list[ModelClass] | list[dict[str, Any]],
358
- fail_silently: bool = False,
359
- **request_kwargs: Any,
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 ApiModelBase(BaseModel, Generic[EndpointClass]):
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
- created_at: AwareDatetime = Field(default_factory=lambda: datetime.now(UTC), exclude=True)
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
- super().__init__(**kwargs)
382
- self._client = client
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
- def _get_client(self) -> ClientBase:
439
- if self._client is None:
440
- raise SWNoClientProvided("Model has no api client set. Use `using` to set a client.")
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(self, force_insert: bool = False, update_fields: IncEx | None = None) -> Self | dict | None:
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 EndpointBase(Generic[ModelClass]):
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[ModelClass]:
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]) -> ModelClass:
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[ModelClass] | ModelClass:
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[ModelClass] = []
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 _parse_data(self, response_dict: dict[str, Any]) -> list[dict[str, Any]]:
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"/search{self.path}", json=data)
769
+ result = await self.client.post(f"{self.search_prefix}{self.path}", json=data)
608
770
  else:
609
- result = await self.client.get(f"{self.path}", params=data)
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) -> ModelClass | dict[str, Any]:
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
- self, pk: str, obj: ModelClass | dict[str, Any], update_fields: IncEx | None = None
631
- ) -> ModelClass | dict[str, Any] | None:
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) -> ModelClass | dict[str, Any] | None:
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: ModelClass | dict[str, Any]) -> ModelClass | dict[str, Any] | None:
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: ModelClass, relation: str) -> list[ModelClass] | list[dict[str, Any]]:
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 select_related(self, **kwargs: Any) -> Self:
698
- self._associations.update({self._serialize_field_name(field): data for field, data in kwargs.items()})
699
- return self
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 only(self, **kwargs: list[str]) -> Self:
702
- for field, data in kwargs.items():
703
- self._includes[self._serialize_field_name(field)] = [self._serialize_field_name(d) for d in data]
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
- return self
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
- def filter(self, **kwargs: Any) -> Self:
708
- for key, value in kwargs.items():
709
- filter_term = ""
710
- filter_type = "equals"
874
+ if self._is_search_query():
875
+ url = f"/search{self.path}"
876
+ else:
877
+ url = self.path
711
878
 
712
- field_parts = key.split("__")
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
- if len(field_parts) > 1:
715
- filter_term = field_parts[-1]
886
+ result_dict: dict[str, Any] = result.json()
887
+ result_data: list[dict[str, Any]] = self._parse_data(result_dict)
716
888
 
717
- match filter_term:
718
- case "in":
719
- filter_type = "equalsAny"
720
- case "contains":
721
- filter_type = "contains"
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 field_parts[0] not in self.model_class.model_fields:
740
- raise SWFilterException(
741
- f"Unknown Field: {field_parts[0]}. Available fields: {self.model_class.model_fields.keys()}"
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
- if len(field_parts) >= 2:
748
- field = "%s.%s" % (
749
- self._serialize_field_name(field_parts[0]),
750
- ".".join(field_parts[1:]),
751
- )
752
- else:
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
- parameters = {}
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
- # range has additional parameters
758
- if filter_type == "range":
759
- if filter_term == "range":
760
- parameters = {"gte": value[0], "lte": value[1]}
761
- else:
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
- self._filter.append({"type": filter_type, "field": field, "value": value, "parameters": parameters})
920
+ if isinstance(data, dict):
921
+ single = True
922
+ data = [data]
765
923
 
766
- return self
924
+ result_list: list[ModelClass | FieldSet] = []
925
+ errors = []
767
926
 
768
- async def bulk_upsert(
769
- self, objs: list[ModelClass] | list[dict[str, Any]], fail_silently: bool = False, **request_kwargs: Any
770
- ) -> dict[str, Any]:
771
- return await self.client.bulk_upsert(name=self.name, objs=objs, fail_silently=fail_silently, **request_kwargs)
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
- async def bulk_delete(
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
- def limit(self, count: int | None) -> "Self":
779
- self._limit = count
780
- return self
942
+ if errors:
943
+ raise SWAPIDataValidationError(errors=errors)
781
944
 
782
- def page(self, num: int | None) -> "Self":
783
- self._page = num
784
- return self
945
+ if single:
946
+ return result_list[0]
785
947
 
786
- def order_by(self, fields: str | tuple[str]) -> "Self":
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
- if field not in self.model_class.model_fields:
798
- raise SWFilterException(
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
- self._sort.append({"field": field, "order": order})
954
+ async def all(self) -> list[ModelClass] | list[dict[str, Any]]:
955
+ data = self._get_data_dict()
805
956
 
806
- return self
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
- if self._is_search_query():
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) >= self._limit:
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]