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.
- prestashop_webservice/__init__.py +82 -0
- prestashop_webservice/address.py +29 -0
- prestashop_webservice/base_model.py +121 -0
- prestashop_webservice/carrier.py +31 -0
- prestashop_webservice/client.py +160 -0
- prestashop_webservice/combination.py +29 -0
- prestashop_webservice/config.py +9 -0
- prestashop_webservice/country.py +30 -0
- prestashop_webservice/customer.py +42 -0
- prestashop_webservice/customer_message.py +29 -0
- prestashop_webservice/customer_thread.py +23 -0
- prestashop_webservice/image_product.py +12 -0
- prestashop_webservice/logger.py +31 -0
- prestashop_webservice/models.py +448 -0
- prestashop_webservice/order.py +114 -0
- prestashop_webservice/order_carrier.py +33 -0
- prestashop_webservice/order_detail.py +28 -0
- prestashop_webservice/order_history.py +57 -0
- prestashop_webservice/order_state.py +12 -0
- prestashop_webservice/params.py +77 -0
- prestashop_webservice/product.py +54 -0
- prestashop_webservice/py.typed +2 -0
- prestashop_webservice/state.py +12 -0
- prestashop_webservice-0.3.0.dist-info/METADATA +97 -0
- prestashop_webservice-0.3.0.dist-info/RECORD +28 -0
- prestashop_webservice-0.3.0.dist-info/WHEEL +5 -0
- prestashop_webservice-0.3.0.dist-info/licenses/LICENSE +21 -0
- prestashop_webservice-0.3.0.dist-info/top_level.txt +1 -0
|
@@ -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,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()
|