producteca 1.0.13__tar.gz → 2.0.14__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.
Files changed (36) hide show
  1. {producteca-1.0.13 → producteca-2.0.14}/PKG-INFO +31 -1
  2. {producteca-1.0.13 → producteca-2.0.14}/README.md +30 -0
  3. producteca-2.0.14/producteca/abstract/abstract_dataclass.py +11 -0
  4. producteca-2.0.14/producteca/client.py +22 -0
  5. {producteca-1.0.13 → producteca-2.0.14}/producteca/payments/payments.py +1 -13
  6. {producteca-1.0.13 → producteca-2.0.14}/producteca/payments/tests/test_payments.py +5 -5
  7. {producteca-1.0.13 → producteca-2.0.14}/producteca/products/products.py +88 -63
  8. producteca-1.0.13/producteca/search/search.py → producteca-2.0.14/producteca/products/search_products.py +1 -11
  9. {producteca-1.0.13 → producteca-2.0.14}/producteca/products/tests/test_products.py +25 -27
  10. producteca-1.0.13/producteca/search/tests/test_search.py → producteca-2.0.14/producteca/products/tests/test_search_products.py +5 -76
  11. {producteca-1.0.13 → producteca-2.0.14}/producteca/sales_orders/sales_orders.py +135 -42
  12. producteca-2.0.14/producteca/sales_orders/search_sale_orders.py +152 -0
  13. producteca-2.0.14/producteca/sales_orders/tests/search.json +137 -0
  14. {producteca-1.0.13 → producteca-2.0.14}/producteca/sales_orders/tests/test_sales_orders.py +17 -18
  15. producteca-2.0.14/producteca/sales_orders/tests/test_search_so.py +48 -0
  16. {producteca-1.0.13 → producteca-2.0.14}/producteca/shipments/shipment.py +0 -14
  17. producteca-2.0.14/producteca/shipments/tests/__init__.py +0 -0
  18. {producteca-1.0.13 → producteca-2.0.14}/producteca/shipments/tests/test_shipment.py +10 -17
  19. {producteca-1.0.13 → producteca-2.0.14}/pyproject.toml +1 -1
  20. producteca-1.0.13/producteca/abstract/abstract_dataclass.py +0 -23
  21. producteca-1.0.13/producteca/client.py +0 -37
  22. producteca-1.0.13/producteca/config/__init__.py +0 -1
  23. producteca-1.0.13/producteca/products/__init__.py +0 -1
  24. producteca-1.0.13/producteca/sales_orders/__init__.py +0 -1
  25. producteca-1.0.13/producteca/search/search_sale_orders.py +0 -170
  26. {producteca-1.0.13 → producteca-2.0.14}/producteca/__init__.py +0 -0
  27. {producteca-1.0.13 → producteca-2.0.14}/producteca/abstract/__init__.py +0 -0
  28. {producteca-1.0.13/producteca/payments → producteca-2.0.14/producteca/config}/__init__.py +0 -0
  29. {producteca-1.0.13 → producteca-2.0.14}/producteca/config/config.py +0 -0
  30. {producteca-1.0.13/producteca/payments/tests → producteca-2.0.14/producteca/payments}/__init__.py +0 -0
  31. {producteca-1.0.13/producteca/products → producteca-2.0.14/producteca/payments}/tests/__init__.py +0 -0
  32. {producteca-1.0.13/producteca/sales_orders/tests → producteca-2.0.14/producteca/products}/__init__.py +0 -0
  33. {producteca-1.0.13/producteca/search → producteca-2.0.14/producteca/products/tests}/__init__.py +0 -0
  34. {producteca-1.0.13/producteca/search/tests → producteca-2.0.14/producteca/sales_orders}/__init__.py +0 -0
  35. {producteca-1.0.13/producteca/shipments → producteca-2.0.14/producteca/sales_orders/tests}/__init__.py +0 -0
  36. {producteca-1.0.13/producteca/shipments/tests → producteca-2.0.14/producteca/shipments}/__init__.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: producteca
3
- Version: 1.0.13
3
+ Version: 2.0.14
4
4
  Summary:
5
5
  Author: Chroma Agency, Matias Rivera
6
6
  Author-email: mrivera@chroma.agency
@@ -38,10 +38,40 @@ To use the Producteca client, first instantiate `ProductecaClient` with your cre
38
38
  from producteca.client import ProductecaClient
39
39
 
40
40
  client = ProductecaClient(token='your_token', api_key='your_api_key')
41
+
42
+ # Usage examples for SalesOrderService
43
+ sale_order_service = client.SalesOrder
44
+ # Get from api
45
+ sale_order = sale_order_service.get(123)
46
+ # or construct from db
47
+ sale_order = client.SalesOrder(...argswithid)
48
+ labels = sale_order.get_shipping_labels()
49
+ status_code, close_response = sale_order.close()
50
+ status_code, cancel_response = sale_order.cancel()
51
+ status_code, synchronized_order = sale_order.synchronize()
52
+ status_code, invoice_response = sale_order.invoice_integration(sale_order)
53
+ search_results = sale_order_service.search(params)
54
+ payment = sale_order.add_payment(payment_payload)
55
+ updated_payment = sale_order.update_payment(payment_id, payment_payload)
56
+ shipment = sale_order.add_shipment(shipment_payload)
57
+ updated_shipment = sale_order.update_shipment(shipment_id, shipment_payload)
58
+
59
+ # Usage examples for ProductService
60
+ product_service = client.Products
61
+ # Get from api
62
+ product = product_service.get(456)
63
+ bundle = product_service.get_bundle(456)
64
+ ml_integration = product_service.get_ml_integration(456)
65
+ search_results = product_service.search(search_params)
66
+ # Or construct from db
67
+ product = client.Products(...args)
68
+ created_product = product.create()
69
+ updated_product = product.update()
41
70
  ```
42
71
 
43
72
  then with the client you should use every other module, like SalesOrder, Products, Payments, Shipments and Search
44
73
 
74
+
45
75
  ## Contributing
46
76
 
47
77
  We welcome contributions to improve this project! Here's how you can help:
@@ -20,10 +20,40 @@ To use the Producteca client, first instantiate `ProductecaClient` with your cre
20
20
  from producteca.client import ProductecaClient
21
21
 
22
22
  client = ProductecaClient(token='your_token', api_key='your_api_key')
23
+
24
+ # Usage examples for SalesOrderService
25
+ sale_order_service = client.SalesOrder
26
+ # Get from api
27
+ sale_order = sale_order_service.get(123)
28
+ # or construct from db
29
+ sale_order = client.SalesOrder(...argswithid)
30
+ labels = sale_order.get_shipping_labels()
31
+ status_code, close_response = sale_order.close()
32
+ status_code, cancel_response = sale_order.cancel()
33
+ status_code, synchronized_order = sale_order.synchronize()
34
+ status_code, invoice_response = sale_order.invoice_integration(sale_order)
35
+ search_results = sale_order_service.search(params)
36
+ payment = sale_order.add_payment(payment_payload)
37
+ updated_payment = sale_order.update_payment(payment_id, payment_payload)
38
+ shipment = sale_order.add_shipment(shipment_payload)
39
+ updated_shipment = sale_order.update_shipment(shipment_id, shipment_payload)
40
+
41
+ # Usage examples for ProductService
42
+ product_service = client.Products
43
+ # Get from api
44
+ product = product_service.get(456)
45
+ bundle = product_service.get_bundle(456)
46
+ ml_integration = product_service.get_ml_integration(456)
47
+ search_results = product_service.search(search_params)
48
+ # Or construct from db
49
+ product = client.Products(...args)
50
+ created_product = product.create()
51
+ updated_product = product.update()
23
52
  ```
24
53
 
25
54
  then with the client you should use every other module, like SalesOrder, Products, Payments, Shipments and Search
26
55
 
56
+
27
57
  ## Contributing
28
58
 
29
59
  We welcome contributions to improve this project! Here's how you can help:
@@ -0,0 +1,11 @@
1
+ from abc import ABC
2
+ from ..config.config import ConfigProducteca
3
+ from dataclasses import dataclass
4
+ from typing import Optional
5
+
6
+
7
+ @dataclass
8
+ class BaseService[T](ABC):
9
+ config: ConfigProducteca
10
+ endpoint: str
11
+ _record: Optional[T] = None
@@ -0,0 +1,22 @@
1
+ from producteca.config.config import ConfigProducteca
2
+ from producteca.products.products import ProductService
3
+ from producteca.sales_orders.sales_orders import SaleOrderService
4
+ import os
5
+
6
+
7
+ class ProductecaClient:
8
+
9
+ def __init__(self, token: str = os.environ.get('PRODUCTECA_TOKEN', ''), api_key: str = os.environ.get('PRODUCTECA_API_KEY', '')):
10
+ if not token:
11
+ raise ValueError('PRODUCTECA_TOKEN environment variable not set')
12
+ if not api_key:
13
+ raise ValueError('PRODUCTECA_API_KEY environment variable not set')
14
+ self.config = ConfigProducteca(token=token, api_key=api_key)
15
+
16
+ @property
17
+ def Product(self):
18
+ return ProductService(self.config)
19
+
20
+ @property
21
+ def SalesOrder(self):
22
+ return SaleOrderService(self.config)
@@ -1,7 +1,5 @@
1
1
  from pydantic import BaseModel
2
2
  from typing import Optional
3
- import requests
4
- from ..config.config import ConfigProducteca
5
3
 
6
4
 
7
5
  class PaymentCard(BaseModel):
@@ -32,14 +30,4 @@ class Payment(BaseModel):
32
30
  hasCancelableStatus: bool
33
31
  id: Optional[int] = None
34
32
 
35
- @classmethod
36
- def create(cls, config: ConfigProducteca, sale_order_id: int, payload: "Payment") -> "Payment":
37
- url = config.get_endpoint(f"salesorders/{sale_order_id}/payments")
38
- res = requests.post(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
39
- return cls(**res.json())
40
-
41
- @classmethod
42
- def update(cls, config: ConfigProducteca, sale_order_id: int, payment_id: int, payload: "Payment") -> "Payment":
43
- url = config.get_endpoint(f"salesorders/{sale_order_id}/payments/{payment_id}")
44
- res = requests.put(url, data=payload.model_dump_json(exclude_none=True), headers=config.headers)
45
- return cls(**res.json())
33
+
@@ -1,13 +1,13 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, Mock
3
3
  from datetime import datetime
4
- from producteca.config.config import ConfigProducteca
5
- from producteca.payments.payments import Payment, PaymentCard, PaymentIntegration
4
+ from producteca.client import ProductecaClient
5
+ from producteca.payments.payments import Payment
6
6
 
7
7
 
8
8
  class TestPayments(unittest.TestCase):
9
9
  def setUp(self):
10
- self.config = ConfigProducteca(token="asd", api_key="test_key")
10
+ self.client = ProductecaClient(token="asd", api_key="test_key")
11
11
  self.sale_order_id = 123
12
12
  self.payment_id = 456
13
13
 
@@ -35,7 +35,7 @@ class TestPayments(unittest.TestCase):
35
35
  payment = Payment(**self.payment_data)
36
36
 
37
37
  # Test create method
38
- result = Payment.create(self.config, self.sale_order_id, payment)
38
+ result = self.client.SalesOrder(id=self.sale_order_id).add_payment(payment)
39
39
 
40
40
  # Assertions
41
41
  mock_post.assert_called_once()
@@ -55,7 +55,7 @@ class TestPayments(unittest.TestCase):
55
55
  payment = Payment(**self.payment_data)
56
56
 
57
57
  # Test update method
58
- result = Payment.update(self.config, self.sale_order_id, self.payment_id, payment)
58
+ result = self.client.SalesOrder(id=self.sale_order_id).update_payment(self.payment_id, payment)
59
59
 
60
60
  # Assertions
61
61
  mock_put.assert_called_once()
@@ -1,20 +1,23 @@
1
1
  from typing import List, Optional, Union
2
2
  from pydantic import BaseModel, Field
3
- import requests
4
- from ..config.config import ConfigProducteca
3
+ from dataclasses import dataclass
4
+ from producteca.abstract.abstract_dataclass import BaseService
5
+ from producteca.products.search_products import SearchProduct, SearchProductParams
5
6
  import logging
7
+ import requests
6
8
 
7
9
  _logger = logging.getLogger(__name__)
8
10
 
9
- # Models for nested structures
10
11
 
11
12
  class Attribute(BaseModel):
12
13
  key: str
13
14
  value: str
14
15
 
16
+
15
17
  class Tag(BaseModel):
16
18
  tag: str
17
19
 
20
+
18
21
  class Dimensions(BaseModel):
19
22
  weight: Optional[float] = None
20
23
  width: Optional[float] = None
@@ -22,11 +25,13 @@ class Dimensions(BaseModel):
22
25
  length: Optional[float] = None
23
26
  pieces: Optional[int] = None
24
27
 
28
+
25
29
  class Deal(BaseModel):
26
30
  campaign: str
27
31
  regular_price: Optional[float] = Field(default=None, alias='regularPrice')
28
32
  deal_price: Optional[float] = Field(default=None, alias='dealPrice')
29
33
 
34
+
30
35
  class Stock(BaseModel):
31
36
  quantity: Optional[int] = None
32
37
  available_quantity: Optional[int] = Field(default=None, alias='availableQuantity')
@@ -35,15 +40,18 @@ class Stock(BaseModel):
35
40
  reserved: Optional[int] = None
36
41
  available: Optional[int] = None
37
42
 
43
+
38
44
  class Price(BaseModel):
39
45
  amount: Optional[float] = None
40
46
  currency: str
41
47
  price_list: str = Field(alias='priceList')
42
48
  price_list_id: Optional[int] = Field(default=None, alias='priceListId')
43
49
 
50
+
44
51
  class Picture(BaseModel):
45
52
  url: str
46
53
 
54
+
47
55
  class Integration(BaseModel):
48
56
  app: Optional[int] = None
49
57
  integration_id: Optional[str] = Field(default=None, alias='integrationId')
@@ -57,6 +65,7 @@ class Integration(BaseModel):
57
65
  id: Optional[int] = None
58
66
  parent_integration: Optional[str] = Field(default=None, alias='parentIntegration')
59
67
 
68
+
60
69
  class Variation(BaseModel):
61
70
  variation_id: Optional[int] = Field(default=None, alias='variationId')
62
71
  components: Optional[List] = None
@@ -71,11 +80,15 @@ class Variation(BaseModel):
71
80
  sku: Optional[str] = None
72
81
  barcode: Optional[str] = None
73
82
 
74
- # Model base para los productos
83
+
84
+ class MeliCategory(BaseModel):
85
+ meli_id: Optional[str] = Field(default=None, alias='meliId')
86
+ accepts_mercadoenvios: Optional[bool] = Field(default=None, alias='acceptsMercadoenvios')
87
+ suggest: Optional[bool] = None
88
+ fixed: Optional[bool] = None
89
+
90
+
75
91
  class Product(BaseModel):
76
- config: Optional[ConfigProducteca] = Field(default=None, exclude=True)
77
- endpoint: str = Field(default='products', exclude=True)
78
- create_if_it_doesnt_exist: bool = Field(default=False, exclude=True)
79
92
  sku: Optional[str] = None
80
93
  variation_id: Optional[int] = None
81
94
  code: Optional[str] = None
@@ -85,7 +98,7 @@ class Product(BaseModel):
85
98
  tags: Optional[List[str]] = None
86
99
  buying_price: Optional[float] = None
87
100
  dimensions: Optional[Dimensions] = None
88
- category: Optional[Union[str, dict]] = None # Puede ser string en POST o dict en GET Meli
101
+ category: Optional[Union[str, MeliCategory]]
89
102
  brand: Optional[str] = None
90
103
  notes: Optional[str] = None
91
104
  deals: Optional[List[Deal]] = None
@@ -94,60 +107,6 @@ class Product(BaseModel):
94
107
  pictures: Optional[List[Picture]] = None
95
108
 
96
109
 
97
- def create(self):
98
- endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
99
- headers = self.config.headers.copy()
100
- headers.update({"createifitdoesntexist": str(self.create_if_it_doesnt_exist).lower()})
101
- data = self.model_dump_json(by_alias=True, exclude_none=True)
102
- response = requests.post(endpoint_url, data=data, headers=headers)
103
- if response.status_code == 204:
104
- final_response = {"Message":"Product does not exist and the request cant create if it does not exist"}
105
- else:
106
- final_response = response.json()
107
- return final_response, response.status_code
108
-
109
- def update(self):
110
- endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
111
- headers = self.config.headers.copy()
112
- data = self.model_dump_json(by_alias=True, exclude_none=True)
113
- if not self.code and not self.sku:
114
- return {"Message":"Sku or code should be provided to update the product"}, 204
115
- response = requests.post(endpoint_url, data=data, headers=headers)
116
- if response.status_code == 204:
117
- final_response = {"Message":"Product does not exist and the request cant create if it does not exist"}
118
- else:
119
- final_response = response.json()
120
- return final_response, response.status_code
121
-
122
- @classmethod
123
- def get(cls, config: ConfigProducteca, product_id: int):
124
- endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}')
125
- headers = config.headers
126
- response = requests.get(endpoint_url, headers=headers)
127
- response_data = response.json()
128
- return response_data, response.status_code
129
-
130
- @classmethod
131
- def get_bundle(cls, config: ConfigProducteca, product_id: int):
132
- endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}/bundles')
133
- headers = config.headers
134
- response = requests.get(endpoint_url, headers=headers)
135
- return cls(config=config, **response.json()), response.status_code
136
-
137
- @classmethod
138
- def get_ml_integration(cls, config: ConfigProducteca, product_id: int):
139
- endpoint_url = config.get_endpoint(f'{cls().endpoint}/{product_id}/listintegration')
140
- headers = config.headers
141
- response = requests.get(endpoint_url, headers=headers)
142
- return cls(config=config, **response.json()), response.status_code
143
-
144
- # Modelo con campos extra de la vista Meli
145
- class MeliCategory(BaseModel):
146
- meli_id: Optional[str] = Field(default=None, alias='meliId')
147
- accepts_mercadoenvios: Optional[bool] = Field(default=None, alias='acceptsMercadoenvios')
148
- suggest: Optional[bool] = None
149
- fixed: Optional[bool] = None
150
-
151
110
  class Shipping(BaseModel):
152
111
  local_pickup: Optional[bool] = Field(default=None, alias='localPickup')
153
112
  mode: Optional[str] = None
@@ -156,9 +115,11 @@ class Shipping(BaseModel):
156
115
  mandatory_free_shipping: Optional[bool] = Field(default=None, alias='mandatoryFreeShipping')
157
116
  free_shipping_method: Optional[str] = Field(default=None, alias='freeShippingMethod')
158
117
 
118
+
159
119
  class MShopsShipping(BaseModel):
160
120
  enabled: Optional[bool] = None
161
121
 
122
+
162
123
  class AttributeCompletion(BaseModel):
163
124
  product_identifier_status: Optional[str] = Field(default=None, alias='productIdentifierStatus')
164
125
  data_sheet_status: Optional[str] = Field(default=None, alias='dataSheetStatus')
@@ -166,13 +127,14 @@ class AttributeCompletion(BaseModel):
166
127
  count: Optional[int] = None
167
128
  total: Optional[int] = None
168
129
 
130
+
169
131
  class MeliProduct(Product):
170
132
  product_id: Optional[int] = Field(default=None, alias='productId')
171
133
  has_custom_shipping_costs: Optional[bool] = Field(default=None, alias='hasCustomShippingCosts')
172
134
  shipping: Optional[Shipping] = None
173
135
  mshops_shipping: Optional[MShopsShipping] = Field(default=None, alias='mShopsShipping')
174
136
  add_free_shipping_cost_to_price: Optional[bool] = Field(default=None, alias='addFreeShippingCostToPrice')
175
- category: Optional[MeliCategory] = None
137
+ category: Optional[Union[str, MeliCategory]] # will never be str, but needs compat with super class
176
138
  attribute_completion: Optional[AttributeCompletion] = Field(default=None, alias='attributeCompletion')
177
139
  catalog_products: Optional[List[str]] = Field(default=None, alias='catalogProducts')
178
140
  warranty: Optional[str] = None
@@ -180,3 +142,66 @@ class MeliProduct(Product):
180
142
  listing_type_id: Optional[str] = Field(default=None, alias='listingTypeId')
181
143
  catalog_products_status: Optional[str] = Field(default=None, alias='catalogProductsStatus')
182
144
 
145
+
146
+ @dataclass
147
+ class ProductService(BaseService[Product]):
148
+ endpoint: str = Field(default='products', exclude=True)
149
+ create_if_it_doesnt_exist: bool = Field(default=False, exclude=True)
150
+
151
+ def __call__(self, **payload):
152
+ self._record = Product(**payload)
153
+ return self
154
+
155
+ def create(self):
156
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
157
+ headers = self.config.headers.copy()
158
+ headers.update({"createifitdoesntexist": str(self.create_if_it_doesnt_exist).lower()})
159
+ data = self._record.model_dump_json(by_alias=True, exclude_none=True)
160
+ response = requests.post(endpoint_url, data=data, headers=headers)
161
+ if response.status_code == 204:
162
+ raise Exception("Product does not exist and the request cant create if it does not exist")
163
+ return Product(**response.json())
164
+
165
+ def update(self):
166
+ # TODO: Change name to synchronize
167
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/synchronize')
168
+ headers = self.config.headers.copy()
169
+ data = self._record.model_dump_json(by_alias=True, exclude_none=True)
170
+ if not self._record.code and not self._record.sku:
171
+ raise "Sku or code should be provided to update the product"
172
+ response = requests.post(endpoint_url, data=data, headers=headers)
173
+ if response.status_code == 204:
174
+ raise Exception("Product does not exist and the request cant create if it does not exist")
175
+ return Product(**response.json())
176
+
177
+ def get(self, product_id: int) -> "Product":
178
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/{product_id}')
179
+ headers = self.config.headers
180
+ response = requests.get(endpoint_url, headers=headers)
181
+ if not response.ok:
182
+ raise Exception(f"Error getting product {product_id}\n {response.text}")
183
+ response_data = response.json()
184
+ return Product(**response_data)
185
+
186
+ def get_bundle(self, product_id: int) -> "Product":
187
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/{product_id}/bundles')
188
+ headers = self.config.headers
189
+ response = requests.get(endpoint_url, headers=headers)
190
+ if not response.ok:
191
+ raise Exception(f"Error getting bundle {product_id}\n {response.text}")
192
+ return Product(**response.json())
193
+
194
+ def get_ml_integration(self, product_id: int) -> "MeliProduct":
195
+ endpoint_url = self.config.get_endpoint(f'{self.endpoint}/{product_id}/listingintegration')
196
+ headers = self.config.headers
197
+ response = requests.get(endpoint_url, headers=headers)
198
+ if not response.ok:
199
+ raise Exception(f"Error getting ml integration {product_id}\n {response.text}")
200
+ return MeliProduct(**response.json())
201
+
202
+ def search(self, params: SearchProductParams) -> SearchProduct:
203
+ endpoint: str = f'search/{self.endpoint}'
204
+ headers = self.config.headers
205
+ url = self.config.get_endpoint(endpoint)
206
+ response = requests.get(url, headers=headers, params=params.model_dump(by_alias=True, exclude_none=True))
207
+ return SearchProduct(**response.json())
@@ -1,7 +1,5 @@
1
1
  from typing import List, Optional
2
2
  from pydantic import BaseModel, Field
3
- import requests
4
- from ..config.config import ConfigProducteca
5
3
 
6
4
 
7
5
  class FacetValue(BaseModel):
@@ -124,7 +122,7 @@ class SearchResultItem(BaseModel):
124
122
  channel_pictures_templates_apps: Optional[List[str]]
125
123
 
126
124
 
127
- class SearchProductResponse(BaseModel):
125
+ class SearchProduct(BaseModel):
128
126
  count: int
129
127
  facets: List[Facet]
130
128
  results: List[SearchResultItem]
@@ -138,12 +136,4 @@ class SearchProductParams(BaseModel):
138
136
  sales_channel: Optional[str] = Field(default='2', alias='salesChannel')
139
137
 
140
138
 
141
- class SearchProduct:
142
- endpoint: str = 'search/products'
143
139
 
144
- @classmethod
145
- def search_product(cls, config: ConfigProducteca, params: SearchProductParams) -> SearchProductResponse:
146
- headers = config.headers
147
- url = config.get_endpoint(cls.endpoint)
148
- response = requests.get(url, headers=headers, params=params.model_dump(by_alias=True, exclude_none=True))
149
- return SearchProductResponse(**response.json())
@@ -1,17 +1,17 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, Mock
3
- from producteca.config.config import ConfigProducteca
4
- from producteca.products.products import Product, MeliProduct
3
+ from producteca.products.products import Product
4
+ from producteca.client import ProductecaClient
5
5
 
6
6
 
7
7
  class TestProduct(unittest.TestCase):
8
8
  def setUp(self):
9
- self.config = ConfigProducteca(token="test_id", api_key="test_secret")
9
+ self.client = ProductecaClient(token="test_client_id", api_key="test_client_secret")
10
10
  self.test_product = Product(
11
- config=self.config,
12
11
  sku="TEST001",
13
12
  name="Test Product",
14
- code="TEST001"
13
+ code="TEST001",
14
+ category="Test"
15
15
  )
16
16
 
17
17
  @patch('requests.post')
@@ -19,13 +19,12 @@ class TestProduct(unittest.TestCase):
19
19
  # Mock successful response
20
20
  mock_response = Mock()
21
21
  mock_response.status_code = 200
22
- mock_response.json.return_value = {"id": 1, "sku": "TEST001"}
22
+ mock_response.json.return_value = self.test_product.model_dump()
23
23
  mock_post.return_value = mock_response
24
24
 
25
- response, status_code = self.test_product.create()
25
+ response = self.client.Product(**self.test_product.model_dump()).create()
26
26
 
27
- self.assertEqual(status_code, 200)
28
- self.assertEqual(response["sku"], "TEST001")
27
+ self.assertEqual(response.sku, "TEST001")
29
28
 
30
29
  @patch('requests.post')
31
30
  def test_create_product_not_exist(self, mock_post):
@@ -34,48 +33,45 @@ class TestProduct(unittest.TestCase):
34
33
  mock_response.status_code = 204
35
34
  mock_post.return_value = mock_response
36
35
 
37
- response, status_code = self.test_product.create()
38
-
39
- self.assertEqual(status_code, 204)
40
- self.assertEqual(response["Message"], "Product does not exist and the request cant create if it does not exist")
36
+ with self.assertRaises(Exception):
37
+ self.client.Product.create()
41
38
 
42
39
  @patch('requests.post')
43
40
  def test_update_product_success(self, mock_post):
44
41
  # Mock successful update
45
42
  mock_response = Mock()
46
43
  mock_response.status_code = 200
47
- mock_response.json.return_value = {"id": 1, "sku": "TEST001", "name": "Updated Product"}
44
+ mock_response.json.return_value = self.test_product.model_dump()
48
45
  mock_post.return_value = mock_response
49
46
 
50
- response, status_code = self.test_product.update()
47
+ response = self.client.Product(**self.test_product.model_dump()).update()
51
48
 
52
- self.assertEqual(status_code, 200)
53
- self.assertEqual(response["name"], "Updated Product")
49
+ self.assertEqual(response.name, "Test Product")
54
50
 
55
51
  @patch('requests.get')
56
52
  def test_get_product(self, mock_get):
57
53
  # Mock get product response
58
54
  mock_response = Mock()
59
55
  mock_response.status_code = 200
60
- mock_response.json.return_value = {"id": 1, "sku": "TEST001"}
56
+ mock_response.json.return_value = self.test_product.model_dump()
61
57
  mock_get.return_value = mock_response
62
58
 
63
- response, status_code = Product.get(self.config, 1)
59
+ response = self.client.Product.get(1)
64
60
 
65
- self.assertEqual(status_code, 200)
66
- self.assertEqual(response["sku"], "TEST001")
61
+ self.assertEqual(response.sku, "TEST001")
67
62
 
68
63
  @patch('requests.get')
69
64
  def test_get_bundle(self, mock_get):
70
65
  # Mock get bundle response
71
66
  mock_response = Mock()
72
67
  mock_response.status_code = 200
73
- mock_response.json.return_value = {"sku": "TEST001", "bundles": []}
68
+ test_prod = self.test_product.model_dump()
69
+ test_prod.update({"sku": "TEST001", "bundles": []})
70
+ mock_response.json.return_value = test_prod
74
71
  mock_get.return_value = mock_response
75
72
 
76
- product, status_code = Product.get_bundle(self.config, 1)
73
+ product = self.client.Product.get_bundle(1)
77
74
 
78
- self.assertEqual(status_code, 200)
79
75
  self.assertEqual(product.sku, "TEST001")
80
76
 
81
77
  @patch('requests.get')
@@ -83,13 +79,15 @@ class TestProduct(unittest.TestCase):
83
79
  # Mock ML integration response
84
80
  mock_response = Mock()
85
81
  mock_response.status_code = 200
86
- mock_response.json.return_value = {"sku": "TEST001", "integrations": []}
82
+ test_prod = self.test_product.model_dump()
83
+ test_prod.update({"sku": "TEST001", "integrations": []})
84
+ mock_response.json.return_value = test_prod
87
85
  mock_get.return_value = mock_response
88
86
 
89
- product, status_code = Product.get_ml_integration(self.config, 1)
87
+ product = self.client.Product.get_ml_integration(1)
90
88
 
91
- self.assertEqual(status_code, 200)
92
89
  self.assertEqual(product.sku, "TEST001")
93
90
 
91
+
94
92
  if __name__ == '__main__':
95
93
  unittest.main()