producteca 2.0.43__py3-none-any.whl → 2.0.47__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of producteca might be problematic. Click here for more details.
- producteca/abstract/abstract_dataclass.py +4 -2
- producteca/products/products.py +8 -6
- producteca/sales_orders/sales_orders.py +8 -7
- producteca/utils.py +43 -0
- {producteca-2.0.43.dist-info → producteca-2.0.47.dist-info}/METADATA +1 -1
- {producteca-2.0.43.dist-info → producteca-2.0.47.dist-info}/RECORD +9 -8
- {producteca-2.0.43.dist-info → producteca-2.0.47.dist-info}/LICENSE +0 -0
- {producteca-2.0.43.dist-info → producteca-2.0.47.dist-info}/WHEEL +0 -0
- {producteca-2.0.43.dist-info → producteca-2.0.47.dist-info}/entry_points.txt +0 -0
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
from abc import ABC
|
|
2
2
|
from ..config.config import ConfigProducteca
|
|
3
|
+
from ..utils import clean_model_dump
|
|
3
4
|
from dataclasses import dataclass
|
|
4
5
|
from typing import Optional, Any
|
|
5
6
|
|
|
@@ -14,10 +15,11 @@ class BaseService(ABC):
|
|
|
14
15
|
return repr(self._record)
|
|
15
16
|
|
|
16
17
|
def to_dict(self):
|
|
17
|
-
return self._record
|
|
18
|
+
return clean_model_dump(self._record)
|
|
18
19
|
|
|
19
20
|
def to_json(self):
|
|
20
|
-
|
|
21
|
+
import json
|
|
22
|
+
return json.dumps(clean_model_dump(self._record))
|
|
21
23
|
|
|
22
24
|
def __getattr__(self, key):
|
|
23
25
|
return getattr(self._record, key)
|
producteca/products/products.py
CHANGED
|
@@ -3,6 +3,7 @@ from pydantic import BaseModel, Field, ValidationError
|
|
|
3
3
|
from dataclasses import dataclass
|
|
4
4
|
from producteca.abstract.abstract_dataclass import BaseService
|
|
5
5
|
from producteca.products.search_products import SearchProduct, SearchProductParams
|
|
6
|
+
from producteca.utils import clean_model_dump
|
|
6
7
|
import logging
|
|
7
8
|
import requests
|
|
8
9
|
|
|
@@ -140,7 +141,7 @@ class ProductVariationBase(BaseModel):
|
|
|
140
141
|
barcode: Optional[str] = None
|
|
141
142
|
attributes: Union[List[Attribute], List] = []
|
|
142
143
|
tags: Optional[List[str]] = []
|
|
143
|
-
buying_price: Optional[float] = Field(
|
|
144
|
+
buying_price: Optional[float] = Field(default=None, alias='buyingPrice')
|
|
144
145
|
dimensions: Optional[Union[Dimensions, dict]] = Field(default_factory=Dimensions)
|
|
145
146
|
brand: Optional[str] = ''
|
|
146
147
|
notes: Optional[str] = ''
|
|
@@ -253,19 +254,20 @@ class ProductService(BaseService):
|
|
|
253
254
|
return self
|
|
254
255
|
|
|
255
256
|
def synchronize(self, payload) -> Union[Product, SynchronizeResponse]:
|
|
256
|
-
|
|
257
257
|
endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
|
|
258
258
|
headers = self.config.headers.copy()
|
|
259
259
|
headers.update({"createifitdoesntexist": str(self.create_if_it_doesnt_exist).lower()})
|
|
260
260
|
product_variation = ProductVariation(**payload)
|
|
261
261
|
if not product_variation.code and not product_variation.sku:
|
|
262
262
|
raise Exception("Sku or code should be provided to update the product")
|
|
263
|
-
|
|
263
|
+
# Hacer model_dump con limpieza automática de valores vacíos
|
|
264
|
+
data = clean_model_dump(product_variation)
|
|
265
|
+
_logger.info(f"Synchronizing product: {data}")
|
|
264
266
|
response = requests.post(endpoint_url, json=data, headers=headers)
|
|
265
267
|
if not response.ok:
|
|
266
268
|
raise Exception(f"Error getting product {product_variation.sku} - {product_variation.code}\n {response.text}")
|
|
267
|
-
|
|
268
|
-
|
|
269
|
+
if response.status_code == 204:
|
|
270
|
+
raise Exception("Status code is 204, meaning nothing was updated or created")
|
|
269
271
|
|
|
270
272
|
_logger.info(f"response text: {response.text}")
|
|
271
273
|
response_data = response.json()
|
|
@@ -321,7 +323,7 @@ class ProductService(BaseService):
|
|
|
321
323
|
endpoint: str = f'search/{self.endpoint}'
|
|
322
324
|
headers = self.config.headers
|
|
323
325
|
url = self.config.get_endpoint(endpoint)
|
|
324
|
-
response = requests.get(url, headers=headers, params=params
|
|
326
|
+
response = requests.get(url, headers=headers, params=clean_model_dump(params))
|
|
325
327
|
if not response.ok:
|
|
326
328
|
raise Exception(f"error in searching products {response.text}")
|
|
327
329
|
return SearchProduct(**response.json())
|
|
@@ -5,6 +5,7 @@ from producteca.abstract.abstract_dataclass import BaseService
|
|
|
5
5
|
from producteca.sales_orders.search_sale_orders import SearchSalesOrderParams, SearchSalesOrder
|
|
6
6
|
from producteca.payments.payments import Payment
|
|
7
7
|
from producteca.shipments.shipment import Shipment
|
|
8
|
+
from producteca.utils import clean_model_dump
|
|
8
9
|
from dataclasses import dataclass
|
|
9
10
|
import logging
|
|
10
11
|
_logger = logging.getLogger(__name__)
|
|
@@ -327,8 +328,8 @@ class SaleOrderService(BaseService):
|
|
|
327
328
|
endpoint = f'{self.endpoint}/synchronize'
|
|
328
329
|
url = self.config.get_endpoint(endpoint)
|
|
329
330
|
# TODO: Check what can we sync, and what can we not sync
|
|
330
|
-
sync_body = SaleOrderSynchronize(**self._record
|
|
331
|
-
response = requests.post(url, json=sync_body
|
|
331
|
+
sync_body = SaleOrderSynchronize(**clean_model_dump(self._record))
|
|
332
|
+
response = requests.post(url, json=clean_model_dump(sync_body), headers=self.config.headers)
|
|
332
333
|
if not response.ok:
|
|
333
334
|
raise Exception(f"Synchronize error {response.status_code} {response.text}")
|
|
334
335
|
sync_res = SaleOrderSyncResponse(**response.json()) # noqa
|
|
@@ -341,7 +342,7 @@ class SaleOrderService(BaseService):
|
|
|
341
342
|
url = self.config.get_endpoint(endpoint)
|
|
342
343
|
response = requests.put(url, headers=self.config.headers,
|
|
343
344
|
json={"id": self._record.id,
|
|
344
|
-
"invoiceIntegration": SaleOrderInvoiceIntegrationPut(**self._record.invoice_integration
|
|
345
|
+
"invoiceIntegration": clean_model_dump(SaleOrderInvoiceIntegrationPut(**clean_model_dump(self._record.invoice_integration)))})
|
|
345
346
|
if not response.ok:
|
|
346
347
|
raise Exception(f"Error on resposne {response.text}")
|
|
347
348
|
return response.ok
|
|
@@ -365,7 +366,7 @@ class SaleOrderService(BaseService):
|
|
|
365
366
|
raise Exception("You need to add a record id")
|
|
366
367
|
payment = Payment(**payload)
|
|
367
368
|
url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/payments")
|
|
368
|
-
res = requests.post(url, json=payment
|
|
369
|
+
res = requests.post(url, json=clean_model_dump(payment), headers=self.config.headers)
|
|
369
370
|
if not res.ok:
|
|
370
371
|
raise Exception(f"Error on resposne {res.text}")
|
|
371
372
|
return Payment(**res.json())
|
|
@@ -375,7 +376,7 @@ class SaleOrderService(BaseService):
|
|
|
375
376
|
raise Exception("You need to add a record id")
|
|
376
377
|
payment = Payment(**payload)
|
|
377
378
|
url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/payments/{payment_id}")
|
|
378
|
-
res = requests.put(url, json=payment
|
|
379
|
+
res = requests.put(url, json=clean_model_dump(payment), headers=self.config.headers)
|
|
379
380
|
if not res.ok:
|
|
380
381
|
raise Exception(f"Error on payment update {res.text}")
|
|
381
382
|
return Payment(**res.json())
|
|
@@ -385,7 +386,7 @@ class SaleOrderService(BaseService):
|
|
|
385
386
|
raise Exception("You need to add a record id")
|
|
386
387
|
shipment = Shipment(**payload)
|
|
387
388
|
url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/shipments")
|
|
388
|
-
res = requests.post(url, json=shipment
|
|
389
|
+
res = requests.post(url, json=clean_model_dump(shipment), headers=self.config.headers)
|
|
389
390
|
if not res.ok:
|
|
390
391
|
raise Exception(f"Error on shipment add {res.text}")
|
|
391
392
|
return Shipment(**res.json())
|
|
@@ -395,7 +396,7 @@ class SaleOrderService(BaseService):
|
|
|
395
396
|
raise Exception("You need to add a record id")
|
|
396
397
|
shipment = Shipment(**payload)
|
|
397
398
|
url = self.config.get_endpoint(f"{self.endpoint}/{self._record.id}/shipments/{shipment_id}")
|
|
398
|
-
res = requests.put(url, json=shipment
|
|
399
|
+
res = requests.put(url, json=clean_model_dump(shipment), headers=self.config.headers)
|
|
399
400
|
if not res.ok:
|
|
400
401
|
raise Exception(f"Error on shipment update {res.text}")
|
|
401
402
|
return Shipment(**res.json())
|
producteca/utils.py
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Utility functions for the producteca package
|
|
3
|
+
"""
|
|
4
|
+
from typing import Any, Dict, List, Union
|
|
5
|
+
from pydantic import BaseModel
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
def exclude_empty_values(obj: Any) -> Any:
|
|
9
|
+
"""Recursively remove None, empty lists, empty strings, and empty dicts
|
|
10
|
+
|
|
11
|
+
Note: Preserves 0 values for numeric fields as they are valid prices/quantities
|
|
12
|
+
"""
|
|
13
|
+
if isinstance(obj, dict):
|
|
14
|
+
filtered_dict = {}
|
|
15
|
+
for k, v in obj.items():
|
|
16
|
+
# Skip None, empty lists, empty strings, empty dicts
|
|
17
|
+
if v is None or v == [] or v == "" or v == {}:
|
|
18
|
+
continue
|
|
19
|
+
# Keep numeric 0 values - they are valid for prices, quantities, etc.
|
|
20
|
+
filtered_dict[k] = exclude_empty_values(v)
|
|
21
|
+
return filtered_dict
|
|
22
|
+
elif isinstance(obj, list):
|
|
23
|
+
filtered_list = [exclude_empty_values(item) for item in obj if item is not None]
|
|
24
|
+
return [item for item in filtered_list if item != [] and item != "" and item != {}]
|
|
25
|
+
else:
|
|
26
|
+
return obj
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
def clean_model_dump(model: BaseModel, by_alias: bool = True, exclude_none: bool = True, **kwargs) -> Dict[str, Any]:
|
|
30
|
+
"""
|
|
31
|
+
Enhanced model_dump that automatically cleans empty values
|
|
32
|
+
|
|
33
|
+
Args:
|
|
34
|
+
model: Pydantic model instance
|
|
35
|
+
by_alias: Use field aliases in output
|
|
36
|
+
exclude_none: Exclude None values
|
|
37
|
+
**kwargs: Additional arguments to pass to model_dump
|
|
38
|
+
|
|
39
|
+
Returns:
|
|
40
|
+
Clean dictionary with empty values removed
|
|
41
|
+
"""
|
|
42
|
+
raw_data = model.model_dump(by_alias=by_alias, exclude_none=exclude_none, **kwargs)
|
|
43
|
+
return exclude_empty_values(raw_data)
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
producteca/__init__.py,sha256=ML-Y_hCd8HOgNOVGiqGpbLqWAtvPXqQQaBQmj-A5Lvc,52
|
|
2
2
|
producteca/abstract/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
3
|
-
producteca/abstract/abstract_dataclass.py,sha256=
|
|
3
|
+
producteca/abstract/abstract_dataclass.py,sha256=Axsf4ewa7Qua5aosvEXksR-fXoGF01_EUlOl1oKdkIk,597
|
|
4
4
|
producteca/client.py,sha256=4T4vLh_Zz56mG2S0P9Il1NEJ4Rj0g2mgXYA7KJjY72U,789
|
|
5
5
|
producteca/config/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
6
6
|
producteca/config/config.py,sha256=uTRaHLI9L7P_LPuFmuYgV50Aedz9MfDbXOZVZPFkOuI,658
|
|
@@ -9,13 +9,13 @@ producteca/payments/payments.py,sha256=ac0X-QC4CGvxs40B_qWv0IepBVpj_nULNSNp8s3v5
|
|
|
9
9
|
producteca/payments/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
10
10
|
producteca/payments/tests/test_payments.py,sha256=wutk5zK2xRChTvKyTmi54Y_92PzNtUKZF6xWF298sFY,3032
|
|
11
11
|
producteca/products/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
12
|
-
producteca/products/products.py,sha256=
|
|
12
|
+
producteca/products/products.py,sha256=LBXN07XuwHRWJfLRMDkCLFjt66QnmH1yIxwuodpnn3o,13195
|
|
13
13
|
producteca/products/search_products.py,sha256=8qKPhwHWq-QzRc3-oZKlMaQQN4Et2iUWm4uxOGWXzFc,6172
|
|
14
14
|
producteca/products/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
15
15
|
producteca/products/tests/test_products.py,sha256=0Tph7T9D2XTLdTDoIpR2PcLTrvPt6h2LPuKowTXrDgU,5742
|
|
16
16
|
producteca/products/tests/test_search_products.py,sha256=ISkDClYaYnwo23a7_O4OlgPfc4dAZCMinLAi07yecEI,4941
|
|
17
17
|
producteca/sales_orders/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
18
|
-
producteca/sales_orders/sales_orders.py,sha256=
|
|
18
|
+
producteca/sales_orders/sales_orders.py,sha256=0tYdDrckefFOab6ZALLwiaCYeR7a36I2oCU4I-rfhyQ,16437
|
|
19
19
|
producteca/sales_orders/search_sale_orders.py,sha256=xCnc1QmfmTB1YmHq387poBDasj4pPNJRShkpEFzMdSs,5073
|
|
20
20
|
producteca/sales_orders/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
21
21
|
producteca/sales_orders/tests/search.json,sha256=v6LkTvQyeBdqxRc3iWwXH5Ugs7EYJAEOiuPuuj_rHYk,3508
|
|
@@ -25,8 +25,9 @@ producteca/shipments/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hS
|
|
|
25
25
|
producteca/shipments/shipment.py,sha256=YokJRBX1ZbUtZVc4AOAB188rbHFO0_MH5hFv5U9Qpjw,844
|
|
26
26
|
producteca/shipments/tests/__init__.py,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
27
27
|
producteca/shipments/tests/test_shipment.py,sha256=0edX3WM-t4nbp53r_Lvhtk_Suc2QCnIUQ27I90qSUfE,2512
|
|
28
|
-
producteca
|
|
29
|
-
producteca-2.0.
|
|
30
|
-
producteca-2.0.
|
|
31
|
-
producteca-2.0.
|
|
32
|
-
producteca-2.0.
|
|
28
|
+
producteca/utils.py,sha256=ihhhSs4RQ3WI25FDp1VPyeJGCLZTjB-QFyzrJYRtJTQ,1591
|
|
29
|
+
producteca-2.0.47.dist-info/LICENSE,sha256=DZak_2itbUtvHzD3E7GNUYSRK6jdOJ-GqncQ2weavLA,34523
|
|
30
|
+
producteca-2.0.47.dist-info/METADATA,sha256=Bfh9DfnwaqYTycEOWSPKilWcIbV41H35AuwDUFK0MZM,3809
|
|
31
|
+
producteca-2.0.47.dist-info/WHEEL,sha256=FMvqSimYX_P7y0a7UY-_Mc83r5zkBZsCYPm7Lr0Bsq4,88
|
|
32
|
+
producteca-2.0.47.dist-info/entry_points.txt,sha256=BFSDFLbB70p8YVZPiU7HDJdj2VyyLdMVa4ekLQAUAVc,125
|
|
33
|
+
producteca-2.0.47.dist-info/RECORD,,
|
|
File without changes
|
|
File without changes
|
|
File without changes
|