prestashop-webservice 0.3.0__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.
@@ -0,0 +1,82 @@
1
+ """
2
+ PrestaShop Webservice - Python Client for PrestaShop API
3
+ """
4
+
5
+ __version__ = "0.3.0"
6
+ __author__ = "Patitas Co."
7
+
8
+ from prestashop_webservice.address import Address
9
+ from prestashop_webservice.base_model import BaseModel
10
+ from prestashop_webservice.carrier import Carrier
11
+ from prestashop_webservice.client import Client
12
+ from prestashop_webservice.combination import Combination
13
+ from prestashop_webservice.country import Country
14
+ from prestashop_webservice.customer import Customer
15
+ from prestashop_webservice.customer_message import CustomerMessage
16
+ from prestashop_webservice.customer_thread import CustomerThread
17
+ from prestashop_webservice.image_product import ImageProduct
18
+ from prestashop_webservice.models import (
19
+ AddressData,
20
+ CarrierData,
21
+ CombinationData,
22
+ CountryData,
23
+ CustomerData,
24
+ CustomerMessageData,
25
+ CustomerThreadData,
26
+ ImageProductData,
27
+ OrderCarrierData,
28
+ OrderData,
29
+ OrderDetailData,
30
+ OrderHistoryData,
31
+ OrderStateData,
32
+ ProductData,
33
+ StateData,
34
+ )
35
+ from prestashop_webservice.order import Order
36
+ from prestashop_webservice.order_carrier import OrderCarrier
37
+ from prestashop_webservice.order_detail import OrderDetail
38
+ from prestashop_webservice.order_history import OrderHistory
39
+ from prestashop_webservice.order_state import OrderState
40
+ from prestashop_webservice.params import Params, Sort, SortOrder
41
+ from prestashop_webservice.product import Product
42
+ from prestashop_webservice.state import State
43
+
44
+ __all__ = [
45
+ "Client",
46
+ "BaseModel",
47
+ "Params",
48
+ "Sort",
49
+ "SortOrder",
50
+ # Complex query models
51
+ "Order",
52
+ "OrderCarrier",
53
+ "OrderHistory",
54
+ "OrderState",
55
+ "Customer",
56
+ "Address",
57
+ "State",
58
+ "Product",
59
+ "ImageProduct",
60
+ "Combination",
61
+ "Country",
62
+ "Carrier",
63
+ "CustomerMessage",
64
+ "CustomerThread",
65
+ "OrderDetail",
66
+ # Data models
67
+ "OrderData",
68
+ "CustomerData",
69
+ "ProductData",
70
+ "CombinationData",
71
+ "AddressData",
72
+ "CountryData",
73
+ "CarrierData",
74
+ "OrderCarrierData",
75
+ "OrderHistoryData",
76
+ "OrderStateData",
77
+ "StateData",
78
+ "ImageProductData",
79
+ "CustomerMessageData",
80
+ "CustomerThreadData",
81
+ "OrderDetailData",
82
+ ]
@@ -0,0 +1,29 @@
1
+ from prestashop_webservice.base_model import BaseModel
2
+ from prestashop_webservice.models import AddressData
3
+ from prestashop_webservice.params import Params
4
+
5
+
6
+ class Address(BaseModel):
7
+ """Complex queries for addresses endpoint."""
8
+
9
+ _data_class = AddressData
10
+
11
+ def get_by_id(self, address_id: str) -> AddressData:
12
+ """Get address by ID."""
13
+ return self._query(f"addresses/{address_id}", None, "address")
14
+
15
+ def get_by_customer(self, customer_id: str) -> list[AddressData]:
16
+ """Get all addresses for a customer."""
17
+ params = Params(
18
+ filter={"id_customer": customer_id},
19
+ display=["id", "id_customer", "address1", "city", "postcode"],
20
+ )
21
+ return self._query("addresses", params, "addresses")
22
+
23
+ def get_by_postal_code(self, postal_code: str) -> list[AddressData]:
24
+ """Get addresses by postal code."""
25
+ params = Params(
26
+ filter={"postcode": postal_code},
27
+ display=["id", "address1", "city", "postcode"],
28
+ )
29
+ return self._query("addresses", params, "addresses")
@@ -0,0 +1,121 @@
1
+ from dataclasses import fields
2
+ from typing import Any, TypeVar
3
+
4
+ from prestashop_webservice.client import Client
5
+ from prestashop_webservice.params import Params
6
+
7
+ T = TypeVar("T")
8
+
9
+
10
+ class BaseModel:
11
+ """Base class for model queries with automatic dataclass conversion."""
12
+
13
+ _data_class: type[Any] | None = None
14
+ _MAX_FILTER_BYTES = 1500
15
+
16
+ def __init__(self, client: Client):
17
+ self.client = client
18
+
19
+ @staticmethod
20
+ def _to_list(result: dict | list) -> list[dict]:
21
+ """Ensure result is always a list.
22
+
23
+ Args:
24
+ result: Result from API call (dict or list)
25
+
26
+ Returns:
27
+ List of dictionaries
28
+ """
29
+ if isinstance(result, dict):
30
+ return [result] if result else []
31
+ return result or []
32
+
33
+ def _query(self, endpoint: str, params: Params | None = None, response_key: str = "") -> Any:
34
+ """Override Client._query to return dataclass instances instead of dicts.
35
+
36
+ Args:
37
+ endpoint: API endpoint to query
38
+ params: Query parameters
39
+ response_key: Key to extract from JSON response
40
+
41
+ Returns:
42
+ Single dataclass instance or list of dataclass instances
43
+ """
44
+
45
+ if params and params.filter:
46
+ filter_str = str(params.filter)
47
+ if len(filter_str.encode("utf-8")) > self._MAX_FILTER_BYTES:
48
+ filter_chunks = self._split_filter(params)
49
+ merged_results: list[dict] = []
50
+ for chunk in filter_chunks:
51
+ if not chunk:
52
+ continue
53
+ chunk_params = Params(
54
+ filter=chunk,
55
+ display=params.display,
56
+ limit=params.limit,
57
+ sort=params.sort,
58
+ date=params.date,
59
+ )
60
+ partial = self.client._query(endpoint, chunk_params, response_key)
61
+ merged_results.extend(self._to_list(partial))
62
+
63
+ result = merged_results
64
+ else:
65
+ result = self.client._query(endpoint, params, response_key)
66
+ else:
67
+ result = self.client._query(endpoint, params, response_key)
68
+
69
+ # If no data_class is set, return raw result
70
+ if self._data_class is None:
71
+ return result
72
+
73
+ # Convert to list and instantiate dataclasses
74
+ results = self._to_list(result)
75
+ instances = []
76
+ for r in results:
77
+ # Filter out unknown fields to avoid TypeError
78
+ if self._data_class:
79
+ field_names = {f.name for f in fields(self._data_class)}
80
+ filtered = {k: v for k, v in r.items() if k in field_names}
81
+ instances.append(self._data_class(**filtered))
82
+
83
+ # Return single instance if result was dict, list if it was list
84
+ if isinstance(result, dict):
85
+ return instances[0] if instances else None
86
+ return instances
87
+
88
+ def _split_filter(self, params: Params) -> list[dict]:
89
+ """Split a large filter payload into smaller chunks."""
90
+ if not params.filter:
91
+ return []
92
+
93
+ # Multiple keys: split dict in half
94
+ if len(params.filter) > 1:
95
+ items = list(params.filter.items())
96
+ mid_index = len(items) // 2
97
+ return [dict(items[:mid_index]), dict(items[mid_index:])]
98
+
99
+ # Single key with huge value: split the value into safe-size chunks
100
+ key, value = next(iter(params.filter.items()))
101
+ value_str = str(value)
102
+ inner = (
103
+ value_str[1:-1] if value_str.startswith("[") and value_str.endswith("]") else value_str
104
+ )
105
+ parts = inner.split("|") if "|" in inner else [inner]
106
+
107
+ filter_chunks: list[dict] = []
108
+ current: list[str] = []
109
+ for part in parts:
110
+ candidate = "|".join(current + [part]) if current else part
111
+ encoded_len = len(f"[{candidate}]".encode())
112
+ if encoded_len > self._MAX_FILTER_BYTES and current:
113
+ filter_chunks.append({key: "|".join(current)})
114
+ current = [part]
115
+ else:
116
+ current.append(part)
117
+
118
+ if current:
119
+ filter_chunks.append({key: "|".join(current)})
120
+
121
+ return filter_chunks or [params.filter]
@@ -0,0 +1,31 @@
1
+ from typing import cast
2
+
3
+ from prestashop_webservice.base_model import BaseModel
4
+ from prestashop_webservice.models import CarrierData
5
+ from prestashop_webservice.params import Params
6
+
7
+
8
+ class Carrier(BaseModel):
9
+ """Complex queries for carriers endpoint."""
10
+
11
+ _data_class = CarrierData
12
+
13
+ def get_by_id(self, carrier_id: str) -> CarrierData:
14
+ """Get carrier by ID."""
15
+ return cast(CarrierData, self._query(f"carriers/{carrier_id}", None, "carrier"))
16
+
17
+ def get_all(self, limit: int | None = None) -> list[CarrierData]:
18
+ """Get carriers with optional limit."""
19
+ params = Params(limit=limit)
20
+ return cast(list[CarrierData], self._query("carriers", params, "carriers"))
21
+
22
+ def get_active(self, limit: int | None = None) -> list[CarrierData]:
23
+ """Get active carriers only."""
24
+ params = Params(filter={"active": "1"}, limit=limit)
25
+ return cast(list[CarrierData], self._query("carriers", params, "carriers"))
26
+
27
+ def get_by_reference(self, reference_id: str) -> CarrierData | None:
28
+ """Get carrier by reference identifier."""
29
+ params = Params(filter={"id_reference": reference_id}, limit=1)
30
+ carriers = cast(list[CarrierData], self._query("carriers", params, "carriers"))
31
+ return carriers[0] if carriers else None
@@ -0,0 +1,160 @@
1
+ from typing import cast
2
+
3
+ from cachetools import TTLCache, cached
4
+ from httpx import BasicAuth, Limits, Timeout
5
+ from httpx import Client as HTTPXClient
6
+
7
+ from prestashop_webservice.config import Config
8
+ from prestashop_webservice.logger import logger
9
+ from prestashop_webservice.params import Params
10
+
11
+
12
+ class Client:
13
+ _instance = None
14
+
15
+ def __new__(cls, *args, **kwargs):
16
+ if cls._instance is None:
17
+ cls._instance = super().__new__(cls)
18
+ cls._instance._initialized = False
19
+ return cls._instance
20
+
21
+ def __init__(
22
+ self,
23
+ prestashop_base_url: str,
24
+ prestashop_ws_key: str,
25
+ max_connections: int = Config.MAX_CONNECTIONS,
26
+ max_keepalive_connections: int = Config.MAX_KEEPALIVE_CONNECTIONS,
27
+ keepalive_expiry: float = Config.KEEPALIVE_EXPIRY,
28
+ ):
29
+ if getattr(self, "_initialized", False):
30
+ return
31
+
32
+ self.base_url = prestashop_base_url.rstrip("/api")
33
+ self.client = HTTPXClient(
34
+ base_url=prestashop_base_url,
35
+ timeout=Timeout(connect=5.0, read=10.0, write=10.0, pool=5.0),
36
+ limits=Limits(
37
+ max_connections=max_connections,
38
+ max_keepalive_connections=max_keepalive_connections,
39
+ keepalive_expiry=keepalive_expiry,
40
+ ),
41
+ follow_redirects=False,
42
+ auth=BasicAuth(prestashop_ws_key, ""),
43
+ params={"output_format": "JSON"},
44
+ )
45
+ self._initialized = True
46
+
47
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
48
+ def query_order(self, params: Params | None = None, order_id: str = "") -> dict:
49
+ return cast(dict, self._query(f"orders/{order_id}", params, "order"))
50
+
51
+ def query_address(self, params: Params | None = None, address_id: str = "") -> dict:
52
+ return cast(dict, self._query(f"addresses/{address_id}", params, "address"))
53
+
54
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
55
+ def query_order_carriers(self, params: Params | None = None) -> list[dict]:
56
+ return cast(list[dict], self._query("order_carriers", params, "order_carriers"))
57
+
58
+ @cached(cache=TTLCache(maxsize=200, ttl=86400))
59
+ def query_customer(self, params: Params | None = None, customer_id: str = "") -> dict:
60
+ return cast(dict, self._query(f"customers/{customer_id}", params, "customer"))
61
+
62
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
63
+ def query_customers(self, params: Params | None = None) -> list[dict]:
64
+ return cast(list[dict], self._query("customers", params, "customers"))
65
+
66
+ @cached(cache=TTLCache(maxsize=200, ttl=86400))
67
+ def query_orders(self, params: Params | None = None) -> list[dict]:
68
+ return cast(list[dict], self._query("orders", params, "orders"))
69
+
70
+ @cached(cache=TTLCache(maxsize=500, ttl=86400))
71
+ def query_product(self, params: Params | None = None, product_id: str = "") -> dict:
72
+ return cast(dict, self._query(f"products/{product_id}", params, "product"))
73
+
74
+ @cached(cache=TTLCache(maxsize=50, ttl=86400))
75
+ def query_country(self, params: Params | None = None, country_id: str = "") -> dict:
76
+ return cast(dict, self._query(f"countries/{country_id}", params, "country"))
77
+
78
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
79
+ def query_countries(self, params: Params | None = None) -> list[dict]:
80
+ return cast(list[dict], self._query("countries", params, "countries"))
81
+
82
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
83
+ def query_addresses(self, params: Params | None = None) -> list[dict]:
84
+ return cast(list[dict], self._query("addresses", params, "addresses"))
85
+
86
+ @cached(cache=TTLCache(maxsize=500, ttl=86400))
87
+ def query_products(self, params: Params | None = None) -> list[dict]:
88
+ return cast(list[dict], self._query("products", params, "products"))
89
+
90
+ @cached(cache=TTLCache(maxsize=50, ttl=86400))
91
+ def query_state(self, params: Params | None = None, state_id: str = "") -> dict:
92
+ return cast(dict, self._query(f"states/{state_id}", params, "state"))
93
+
94
+ @cached(cache=TTLCache(maxsize=200, ttl=86400))
95
+ def query_product_images(
96
+ self, params: Params | None = None, product_id: str = ""
97
+ ) -> list[dict]:
98
+ return cast(list[dict], self._query(f"images/products/{product_id}", params, "image"))
99
+
100
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
101
+ def query_order_histories(self, params: Params | None = None) -> list[dict]:
102
+ return cast(list[dict], self._query("order_histories", params, "order_histories"))
103
+
104
+ @cached(cache=TTLCache(maxsize=50, ttl=86400))
105
+ def query_order_state(self, params: Params | None = None, state_id: str = "") -> dict:
106
+ return cast(dict, self._query(f"order_states/{state_id}", params, "order_state"))
107
+
108
+ @cached(cache=TTLCache(maxsize=500, ttl=86400))
109
+ def query_combination(self, params: Params | None = None, combination_id: str = "") -> dict:
110
+ return cast(dict, self._query(f"combinations/{combination_id}", params, "combination"))
111
+
112
+ @cached(cache=TTLCache(maxsize=500, ttl=86400))
113
+ def query_combinations(self, params: Params | None = None) -> list[dict]:
114
+ return cast(list[dict], self._query("combinations", params, "combinations"))
115
+
116
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
117
+ def query_customer_message(self, params: Params | None = None, message_id: str = "") -> dict:
118
+ return cast(
119
+ dict, self._query(f"customer_messages/{message_id}", params, "customer_message")
120
+ )
121
+
122
+ @cached(cache=TTLCache(maxsize=100, ttl=86400))
123
+ def query_customer_messages(self, params: Params | None = None) -> list[dict]:
124
+ return cast(list[dict], self._query("customer_messages", params, "customer_messages"))
125
+
126
+ @cached(cache=TTLCache(maxsize=500, ttl=86400))
127
+ def query_order_detail(self, params: Params | None = None, order_detail_id: str = "") -> dict:
128
+ return cast(dict, self._query(f"order_details/{order_detail_id}", params, "order_detail"))
129
+
130
+ @cached(cache=TTLCache(maxsize=500, ttl=86400))
131
+ def query_order_details(self, params: Params | None = None) -> list[dict]:
132
+ return cast(list[dict], self._query("order_details", params, "order_details"))
133
+
134
+ def post(self, endpoint: str, xml_payload: str) -> dict:
135
+ logger.debug(f"POSTing to {endpoint}")
136
+
137
+ response = self.client.post(
138
+ f"/{endpoint}",
139
+ content=xml_payload,
140
+ headers={"Content-Type": "application/xml"},
141
+ )
142
+ response.raise_for_status()
143
+
144
+ return response.json() or {}
145
+
146
+ def _query(
147
+ self, endpoint: str, params: Params | None = None, response_key: str = ""
148
+ ) -> dict | list:
149
+ logger.debug(f"Requesting {endpoint}")
150
+
151
+ response = self.client.get(f"/{endpoint}", params=params.to_dict() if params else None)
152
+ response.raise_for_status()
153
+
154
+ json_response = response.json() or {}
155
+
156
+ if response_key:
157
+ result = json_response.get(response_key, {})
158
+ return result if result else {}
159
+
160
+ return json_response
@@ -0,0 +1,29 @@
1
+ from typing import cast
2
+
3
+ from cachetools import TTLCache, cached
4
+
5
+ from prestashop_webservice.base_model import BaseModel
6
+ from prestashop_webservice.models import CombinationData
7
+ from prestashop_webservice.params import Params
8
+
9
+
10
+ class Combination(BaseModel):
11
+ """Complex queries for combinations endpoint."""
12
+
13
+ _data_class = CombinationData
14
+
15
+ @cached(cache=TTLCache(maxsize=1000, ttl=604800))
16
+ def get_by_id(self, combination_id: str) -> CombinationData:
17
+ """Get combination by ID."""
18
+ return cast(
19
+ CombinationData, self._query(f"combinations/{combination_id}", None, "combination")
20
+ )
21
+
22
+ @cached(cache=TTLCache(maxsize=1000, ttl=604800))
23
+ def get_by_product(self, product_id: str) -> list[CombinationData]:
24
+ """Get combinations for a product."""
25
+ params = Params(
26
+ filter={"id_product": product_id},
27
+ display=["id", "id_product", "reference", "ean13", "price", "weight", "default_on"],
28
+ )
29
+ return cast(list[CombinationData], self._query("combinations", params, "combinations"))
@@ -0,0 +1,9 @@
1
+ """
2
+ Default configuration file for PrestaShop Webservice
3
+ """
4
+
5
+
6
+ class Config:
7
+ MAX_CONNECTIONS = 2
8
+ MAX_KEEPALIVE_CONNECTIONS = 2
9
+ KEEPALIVE_EXPIRY = 10.0
@@ -0,0 +1,30 @@
1
+ from prestashop_webservice.base_model import BaseModel
2
+ from prestashop_webservice.models import CountryData
3
+ from prestashop_webservice.params import Params
4
+
5
+
6
+ class Country(BaseModel):
7
+ """Complex queries for countries endpoint."""
8
+
9
+ _data_class = CountryData
10
+
11
+ def get_by_id(self, country_id: str) -> CountryData:
12
+ """Get country by ID."""
13
+ return self._query(f"countries/{country_id}", None, "country")
14
+
15
+ def get_by_iso_code(self, iso_code: str) -> CountryData | None:
16
+ """Get country by ISO code."""
17
+ params = Params(
18
+ filter={"iso_code": iso_code},
19
+ display=["id", "iso_code", "name"],
20
+ )
21
+ countries = self._query("countries", params, "countries")
22
+ return countries[0] if countries else None
23
+
24
+ def get_active(self) -> list[CountryData]:
25
+ """Get all active countries."""
26
+ params = Params(
27
+ filter={"active": "1"},
28
+ display=["id", "iso_code", "name", "active"],
29
+ )
30
+ return self._query("countries", params, "countries")
@@ -0,0 +1,42 @@
1
+ from prestashop_webservice.base_model import BaseModel
2
+ from prestashop_webservice.models import CustomerData
3
+ from prestashop_webservice.params import Params
4
+
5
+
6
+ class Customer(BaseModel):
7
+ """Complex queries for customers endpoint."""
8
+
9
+ _data_class = CustomerData
10
+
11
+ def get_by_id(self, customer_id: str) -> CustomerData:
12
+ """Get customer by ID."""
13
+ return self._query(f"customers/{customer_id}", None, "customer")
14
+
15
+ def get_by_email(self, email: str) -> CustomerData | None:
16
+ """Get customer by email."""
17
+ email = email.strip().lower()
18
+ params = Params(
19
+ filter={"email": email},
20
+ display=["id", "email", "firstname", "lastname"],
21
+ )
22
+ customers = self._query("customers", params, "customers")
23
+ return customers[0] if customers else None
24
+
25
+ def get_active(self, limit: int = 100) -> list[CustomerData]:
26
+ """Get active customers."""
27
+ params = Params(
28
+ filter={"active": "1"},
29
+ display=["id", "firstname", "lastname", "email", "active"],
30
+ limit=limit,
31
+ )
32
+ return self._query("customers", params, "customers")
33
+
34
+ def search_by_name(self, name: str, limit: int = 50) -> list[CustomerData]:
35
+ """Search customers by firstname or lastname."""
36
+ params = Params(
37
+ filter={"firstname": f"%{name}%"},
38
+ display=["id", "firstname", "lastname", "email"],
39
+ limit=limit,
40
+ )
41
+ result = self._query("customers", params, "customers")
42
+ return result if result is not None else []
@@ -0,0 +1,29 @@
1
+ from prestashop_webservice.base_model import BaseModel
2
+ from prestashop_webservice.models import CustomerMessageData
3
+ from prestashop_webservice.params import Params
4
+
5
+
6
+ class CustomerMessage(BaseModel):
7
+ """Complex queries for customer_messages endpoint."""
8
+
9
+ _data_class = CustomerMessageData
10
+
11
+ def get_by_id(self, message_id: str) -> CustomerMessageData:
12
+ """Get customer message by ID."""
13
+ return self._query(f"customer_messages/{message_id}", None, "customer_message")
14
+
15
+ def get_by_order(self, order_id: str) -> list[CustomerMessageData]:
16
+ """Get messages associated with an order (via customer thread potentially, but often queried by order)."""
17
+ params = Params(
18
+ filter={"id_order": order_id},
19
+ display=["id", "message", "id_employee", "private", "date_add"],
20
+ )
21
+ return self._query("customer_messages", params, "customer_messages")
22
+
23
+ def get_by_thread(self, thread_id: str) -> list[CustomerMessageData]:
24
+ """Get messages associated with a specific customer thread."""
25
+ params = Params(
26
+ filter={"id_customer_thread": thread_id},
27
+ display=["full"],
28
+ )
29
+ return self._query("customer_messages", params, "customer_messages")
@@ -0,0 +1,23 @@
1
+ from prestashop_webservice.base_model import BaseModel
2
+ from prestashop_webservice.models import CustomerThreadData
3
+ from prestashop_webservice.params import Params
4
+
5
+
6
+ class CustomerThread(BaseModel):
7
+ """Complex queries for customer_threads endpoint."""
8
+
9
+ _data_class = CustomerThreadData
10
+
11
+ def get_by_id(self, thread_id: str) -> CustomerThreadData:
12
+ """Get customer thread by ID."""
13
+ return self._query(f"customer_threads/{thread_id}", None, "customer_thread")
14
+
15
+ def get_by_customer(self, customer_id: str) -> list[CustomerThreadData]:
16
+ """Get customer threads by customer ID."""
17
+ params = Params(filter={"id_customer": customer_id}, display=["full"])
18
+ return self._query("customer_threads", params, "customer_threads")
19
+
20
+ def get_by_order(self, order_id: str) -> list[CustomerThreadData]:
21
+ """Get customer threads by order ID."""
22
+ params = Params(filter={"id_order": order_id}, display=["full"])
23
+ return self._query("customer_threads", params, "customer_threads")
@@ -0,0 +1,12 @@
1
+ from prestashop_webservice.base_model import BaseModel
2
+ from prestashop_webservice.models import ImageProductData
3
+
4
+
5
+ class ImageProduct(BaseModel):
6
+ """Complex queries for images/products endpoint."""
7
+
8
+ _data_class = ImageProductData
9
+
10
+ def get_by_product(self, product_id: str) -> list[ImageProductData]:
11
+ """Get images for a product."""
12
+ return self._query(f"images/products/{product_id}", None, "image")
@@ -0,0 +1,31 @@
1
+ from loguru import logger
2
+
3
+
4
+ def configure_logger():
5
+ logger.remove()
6
+
7
+ # Format
8
+ console_format = "<green>{time:HH:mm:ss}</green> | " "<level>{level}</level> >> " "{message}"
9
+
10
+ # Terminal logging
11
+ logger.add(
12
+ lambda msg: print(msg, end=""),
13
+ level="INFO",
14
+ format=console_format,
15
+ colorize=True,
16
+ )
17
+
18
+ # File logging
19
+ logger.add(
20
+ "logs/app.log",
21
+ rotation="10 MB",
22
+ retention="10 days",
23
+ level="DEBUG",
24
+ format="{time:YYYY-MM-DD at HH:mm:ss} | {level} | {message}",
25
+ compression="zip",
26
+ )
27
+
28
+ return logger
29
+
30
+
31
+ configure_logger()