producteca 2.0.46__tar.gz → 2.0.47__tar.gz

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.

Files changed (32) hide show
  1. {producteca-2.0.46 → producteca-2.0.47}/PKG-INFO +1 -1
  2. {producteca-2.0.46 → producteca-2.0.47}/producteca/abstract/abstract_dataclass.py +4 -2
  3. {producteca-2.0.46 → producteca-2.0.47}/producteca/products/products.py +6 -4
  4. {producteca-2.0.46 → producteca-2.0.47}/producteca/sales_orders/sales_orders.py +8 -7
  5. producteca-2.0.47/producteca/utils.py +43 -0
  6. {producteca-2.0.46 → producteca-2.0.47}/pyproject.toml +1 -1
  7. {producteca-2.0.46 → producteca-2.0.47}/LICENSE +0 -0
  8. {producteca-2.0.46 → producteca-2.0.47}/README.md +0 -0
  9. {producteca-2.0.46 → producteca-2.0.47}/producteca/__init__.py +0 -0
  10. {producteca-2.0.46 → producteca-2.0.47}/producteca/abstract/__init__.py +0 -0
  11. {producteca-2.0.46 → producteca-2.0.47}/producteca/client.py +0 -0
  12. {producteca-2.0.46 → producteca-2.0.47}/producteca/config/__init__.py +0 -0
  13. {producteca-2.0.46 → producteca-2.0.47}/producteca/config/config.py +0 -0
  14. {producteca-2.0.46 → producteca-2.0.47}/producteca/payments/__init__.py +0 -0
  15. {producteca-2.0.46 → producteca-2.0.47}/producteca/payments/payments.py +0 -0
  16. {producteca-2.0.46 → producteca-2.0.47}/producteca/payments/tests/__init__.py +0 -0
  17. {producteca-2.0.46 → producteca-2.0.47}/producteca/payments/tests/test_payments.py +0 -0
  18. {producteca-2.0.46 → producteca-2.0.47}/producteca/products/__init__.py +0 -0
  19. {producteca-2.0.46 → producteca-2.0.47}/producteca/products/search_products.py +0 -0
  20. {producteca-2.0.46 → producteca-2.0.47}/producteca/products/tests/__init__.py +0 -0
  21. {producteca-2.0.46 → producteca-2.0.47}/producteca/products/tests/test_products.py +0 -0
  22. {producteca-2.0.46 → producteca-2.0.47}/producteca/products/tests/test_search_products.py +0 -0
  23. {producteca-2.0.46 → producteca-2.0.47}/producteca/sales_orders/__init__.py +0 -0
  24. {producteca-2.0.46 → producteca-2.0.47}/producteca/sales_orders/search_sale_orders.py +0 -0
  25. {producteca-2.0.46 → producteca-2.0.47}/producteca/sales_orders/tests/__init__.py +0 -0
  26. {producteca-2.0.46 → producteca-2.0.47}/producteca/sales_orders/tests/search.json +0 -0
  27. {producteca-2.0.46 → producteca-2.0.47}/producteca/sales_orders/tests/test_sales_orders.py +0 -0
  28. {producteca-2.0.46 → producteca-2.0.47}/producteca/sales_orders/tests/test_search_so.py +0 -0
  29. {producteca-2.0.46 → producteca-2.0.47}/producteca/shipments/__init__.py +0 -0
  30. {producteca-2.0.46 → producteca-2.0.47}/producteca/shipments/shipment.py +0 -0
  31. {producteca-2.0.46 → producteca-2.0.47}/producteca/shipments/tests/__init__.py +0 -0
  32. {producteca-2.0.46 → producteca-2.0.47}/producteca/shipments/tests/test_shipment.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: producteca
3
- Version: 2.0.46
3
+ Version: 2.0.47
4
4
  Summary:
5
5
  Author: Chroma Agency, Matias Rivera
6
6
  Author-email: mrivera@chroma.agency
@@ -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.model_dump(by_alias=True)
18
+ return clean_model_dump(self._record)
18
19
 
19
20
  def to_json(self):
20
- return self._record.model_dump_json(by_alias=True)
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)
@@ -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(0, alias='buyingPrice')
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,14 +254,15 @@ 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
- data = product_variation.model_dump(by_alias=True, exclude_none=True)
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}")
@@ -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.model_dump(by_alias=True, exclude_none=True))
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.model_dump(by_alias=True))
331
- response = requests.post(url, json=sync_body.model_dump(by_alias=True), headers=self.config.headers)
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.model_dump(by_alias=True)).model_dump()})
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.model_dump(by_alias=True, exclude_none=True), headers=self.config.headers)
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.model_dump(by_alias=True, exclude_none=True), headers=self.config.headers)
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.model_dump(by_alias=True, exclude_none=True), headers=self.config.headers)
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.model_dump(by_alias=True, exclude_none=True), headers=self.config.headers)
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())
@@ -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
  [tool.poetry]
2
2
  name = "producteca"
3
- version = "2.0.46"
3
+ version = "2.0.47"
4
4
  description = ""
5
5
  authors = ["Chroma Agency, Matias Rivera <mrivera@chroma.agency>"]
6
6
  readme = "README.md"
File without changes
File without changes