producteca 1.0.14__py3-none-any.whl → 2.0.57__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,136 @@
1
+ from typing import List, Optional, Union
2
+ from pydantic import BaseModel, Field
3
+
4
+
5
+ class FacetValue(BaseModel):
6
+ count: int
7
+ value: Optional[Union[str, bool]] = None
8
+ label: Union[str, bool]
9
+
10
+
11
+ class Facet(BaseModel):
12
+ key: str
13
+ value: List[FacetValue]
14
+ is_collection: Optional[bool] = False
15
+ translate: bool
16
+
17
+
18
+ class SearchStocks(BaseModel):
19
+ warehouse: str
20
+ quantity: int
21
+ reserved: int
22
+
23
+
24
+ class SearchPrices(BaseModel):
25
+ price_list_id: int = Field(..., alias='priceListId')
26
+ price_list: str = Field(..., alias='priceList')
27
+ amount: float
28
+ currency: str
29
+
30
+
31
+ class SearchIntegration(BaseModel):
32
+ app: Optional[int] = None
33
+ integration_id: Optional[str] = Field(None, alias='integrationId')
34
+ permalink: Optional[str] = None
35
+ status: Optional[str] = None
36
+ listing_type: Optional[str] = Field(None, alias='listingType')
37
+ safety_stock: Optional[int] = Field(None, alias='safetyStock')
38
+ synchronize_stock: Optional[bool] = Field(None, alias='synchronizeStock')
39
+ is_active: Optional[bool] = Field(None, alias='isActive')
40
+ is_active_or_paused: Optional[bool] = Field(None, alias='isActiveOrPaused')
41
+ id: Optional[int] = None
42
+
43
+
44
+ class SearchDeals(BaseModel):
45
+ campaign: str
46
+ product: int
47
+ variation: str
48
+ deal_price: float
49
+ discount: float
50
+ regular_price: float
51
+ enabled: bool
52
+ currency: str
53
+ id: str
54
+
55
+
56
+ class SearchResultItem(BaseModel):
57
+ search_score: float = Field(..., alias='@search.score')
58
+ id: int
59
+ product_id: Optional[int] = Field(None, alias='productId')
60
+ company_id: Optional[int] = Field(None, alias='companyId')
61
+ name:Optional[str] = None
62
+ code: Optional[str] = None
63
+ skus: List[str]
64
+ brand: Optional[str] = None
65
+ category: Optional[str] = None
66
+ thumbnail: Optional[str] = None
67
+ stocks: Optional[Union[List[SearchStocks], List]] = None
68
+ warehouses_with_stock: Optional[List[str]] = Field(None, alias='warehousesWithStock')
69
+ total_stock: Optional[int] = Field(None, alias='totalStock')
70
+ has_pictures: Optional[bool] = Field(None, alias='hasPictures')
71
+ buying_price: Optional[float] = Field(None, alias='buyingPrice')
72
+ prices: Optional[Union[List[SearchPrices], List]] = None
73
+ integration_ids: Optional[List[str]] = Field(None, alias='integrationIds')
74
+ integration_apps: Optional[List[str]] = Field(None, alias='integrationApps')
75
+ integrations: Optional[Union[List[SearchIntegration], List]] = None
76
+ campaigns: Optional[List[str]] = None
77
+ app: Optional[int] = None
78
+ status: Optional[str] = None
79
+ synchronize_stock: Optional[bool] = Field(None, alias='synchronizeStock')
80
+ listing_type: Optional[str] = Field(None, alias='listingType')
81
+ price_amount: Optional[float] = Field(None, alias='priceAmount')
82
+ price_currency: Optional[str] = Field(None, alias='priceCurrency')
83
+ category_id: Optional[str] = Field(None, alias='categoryId')
84
+ category_base_id: Optional[str] = Field(None, alias='categoryBaseId')
85
+ category_l1: Optional[str] = Field(None, alias='categoryL1')
86
+ category_l2: Optional[str] = Field(None, alias='categoryL2')
87
+ category_l3: Optional[str] = Field(None, alias='categoryL3')
88
+ category_l4: Optional[str] = Field(None, alias='categoryL4')
89
+ category_l5: Optional[str] = Field(None, alias='categoryL5')
90
+ category_l6: Optional[str] = Field(None, alias='categoryL6')
91
+ has_category: Optional[bool] = Field(None, alias='hasCategory')
92
+ category_fixed: Optional[bool] = Field(None, alias='categoryFixed')
93
+ accepts_mercadoenvios: Optional[bool] = Field(None, alias='acceptsMercadoenvios')
94
+ shipping_mode: Optional[str] = Field(None, alias='shippingMode')
95
+ local_pickup: Optional[bool] = Field(None, alias='localPickup')
96
+ mandatory_free_shipping: Optional[bool] = Field(None, alias='mandatoryFreeShipping')
97
+ free_shipping: Optional[bool] = Field(None, alias='freeShipping')
98
+ free_shipping_cost: Optional[float] = Field(None, alias='freeShippingCost')
99
+ template: Optional[int] = None
100
+ youtube_id: Optional[str] = Field(None, alias='youtubeId')
101
+ warranty: Optional[str] = None
102
+ permalink: Optional[str] = None
103
+ domain: Optional[str] = None
104
+ attribute_completion_status: Optional[str] = Field(None, alias='attributeCompletionStatus')
105
+ attribute_completion_count: Optional[int] = Field(None, alias='attributeCompletionCount')
106
+ attribute_completion_total: Optional[int] = Field(None, alias='attributeCompletionTotal')
107
+ deals: Optional[Union[SearchDeals, List]] = None
108
+ campaign_status: Optional[List[str]] = Field(None, alias='campaignStatus')
109
+ size_chart: Optional[str] = Field(None, alias='sizeChart')
110
+ channel_status: Optional[List[str]] = Field(None, alias='channelStatus')
111
+ channel_category_l1: Optional[List[str]] = Field(None, alias='channelCategoryL1')
112
+ channel_category_l2: Optional[List[str]] = Field(None, alias='channelCategoryL2')
113
+ channel_category_l3: Optional[List[str]] = Field(None, alias='channelCategoryL3')
114
+ channel_category_id: Optional[List[str]] = Field(None, alias='channelCategoryId')
115
+ channel_synchronizes_stock: Optional[List[str]] = Field(None, alias='channelSynchronizesStock')
116
+ channel_has_category: Optional[List[str]] = Field(None, alias='channelHasCategory')
117
+ catalog_products_status: Optional[List[str]] = Field(None, alias='catalogProductsStatus')
118
+ metadata: Optional[List[str]] = None
119
+ integration_tags: Optional[List[str]] = Field(None, alias='integrationTags')
120
+ variations_integration_ids: Optional[List[str]] = Field(None, alias='variationsIntegrationIds')
121
+ channel_pictures_templates: Optional[List[str]] = Field(None, alias='channelPicturesTemplates')
122
+ channel_pictures_templates_apps: Optional[List[str]] = Field(None, alias='channelPicturesTemplatesApps')
123
+
124
+
125
+ class SearchProduct(BaseModel):
126
+ count: int
127
+ facets: List[Facet]
128
+ results: List[SearchResultItem]
129
+
130
+
131
+ class SearchProductParams(BaseModel):
132
+ top: Optional[int]
133
+ skip: Optional[int]
134
+ filter: Optional[str] = Field(default=None, alias='$filter')
135
+ search: Optional[str] = Field(default=None)
136
+ sales_channel: Optional[str] = Field(default='2', alias='salesChannel')
@@ -1,95 +1,168 @@
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
+ self.product_to_create_payload = {
17
+ "sku": "9817234",
18
+ "code": "871234",
19
+ "name": "Hola test",
20
+ "buyingPrice": 0,
21
+ "deals": [
22
+ {
23
+ "campaign": "string",
24
+ "regularPrice": 0,
25
+ "dealPrice": 0
26
+ }
27
+ ],
28
+ "prices": [
29
+ {
30
+ "amount": 10,
31
+ "currency": "Local",
32
+ "priceList": "Default"
33
+ }
34
+ ],
35
+ "stocks": [
36
+ {
37
+ "quantity": 2,
38
+ "availableQuantity": 2,
39
+ "warehouse": "Default"
40
+ }
41
+ ],
42
+ }
16
43
 
17
44
  @patch('requests.post')
18
45
  def test_create_product_success(self, mock_post):
19
46
  # Mock successful response
20
47
  mock_response = Mock()
21
48
  mock_response.status_code = 200
22
- mock_response.json.return_value = {"id": 1, "sku": "TEST001"}
49
+ mock_response.json.return_value = self.test_product.model_dump()
23
50
  mock_post.return_value = mock_response
24
51
 
25
- response, status_code = self.test_product.create()
52
+ response = self.client.Product(**self.test_product.model_dump()).synchronize(self.product_to_create_payload)
26
53
 
27
- self.assertEqual(status_code, 200)
28
- self.assertEqual(response["sku"], "TEST001")
29
-
30
- @patch('requests.post')
31
- def test_create_product_not_exist(self, mock_post):
32
- # Mock product not found response
33
- mock_response = Mock()
34
- mock_response.status_code = 204
35
- mock_post.return_value = mock_response
36
-
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")
54
+ self.assertEqual(response.sku, "TEST001")
41
55
 
42
56
  @patch('requests.post')
43
57
  def test_update_product_success(self, mock_post):
44
- # Mock successful update
58
+ payload = self.product_to_create_payload
45
59
  mock_response = Mock()
46
60
  mock_response.status_code = 200
47
- mock_response.json.return_value = {"id": 1, "sku": "TEST001", "name": "Updated Product"}
61
+ mock_response.json.return_value = self.test_product.model_dump()
48
62
  mock_post.return_value = mock_response
49
63
 
50
- response, status_code = self.test_product.update()
64
+ response = self.client.Product(**self.test_product.model_dump()).synchronize(payload)
51
65
 
52
- self.assertEqual(status_code, 200)
53
- self.assertEqual(response["name"], "Updated Product")
66
+ self.assertEqual(response.name, "Test Product")
54
67
 
55
68
  @patch('requests.get')
56
69
  def test_get_product(self, mock_get):
57
70
  # Mock get product response
58
71
  mock_response = Mock()
59
72
  mock_response.status_code = 200
60
- mock_response.json.return_value = {"id": 1, "sku": "TEST001"}
73
+ mock_response.json.return_value = self.test_product.model_dump()
61
74
  mock_get.return_value = mock_response
62
75
 
63
- response, status_code = Product.get(self.config, 1)
76
+ response = self.client.Product.get(1)
64
77
 
65
- self.assertEqual(status_code, 200)
66
- self.assertEqual(response["sku"], "TEST001")
78
+ self.assertEqual(response.sku, "TEST001")
67
79
 
68
80
  @patch('requests.get')
69
81
  def test_get_bundle(self, mock_get):
70
82
  # Mock get bundle response
71
83
  mock_response = Mock()
72
84
  mock_response.status_code = 200
73
- mock_response.json.return_value = {"sku": "TEST001", "bundles": []}
85
+ test_prod = {
86
+ "results": [
87
+ {
88
+ "companyId": 0,
89
+ "productId": 0,
90
+ "variations": [
91
+ {
92
+ "variationId": 0,
93
+ "components": [
94
+ {
95
+ "quantity": 0,
96
+ "variationId": 0,
97
+ "productId": 0
98
+ }
99
+ ]
100
+ }
101
+ ],
102
+ "id": "string"
103
+ }
104
+ ],
105
+ "count": 0
106
+ }
107
+ mock_response.json.return_value = test_prod
74
108
  mock_get.return_value = mock_response
75
109
 
76
- product, status_code = Product.get_bundle(self.config, 1)
110
+ product = self.client.Product.get_bundle(1)
77
111
 
78
- self.assertEqual(status_code, 200)
79
- self.assertEqual(product.sku, "TEST001")
112
+ self.assertEqual(product.count, 0)
80
113
 
81
114
  @patch('requests.get')
82
115
  def test_get_ml_integration(self, mock_get):
83
116
  # Mock ML integration response
84
117
  mock_response = Mock()
85
118
  mock_response.status_code = 200
86
- mock_response.json.return_value = {"sku": "TEST001", "integrations": []}
119
+ meli_product = {
120
+ "hasCustomShippingCosts": True,
121
+ "productId": 0,
122
+ "shipping": {
123
+ "localPickup": True,
124
+ "mode": "string",
125
+ "freeShipping": True,
126
+ "freeShippingCost": 0,
127
+ "mandatoryFreeShipping": True,
128
+ "freeShippingMethod": "string"
129
+ },
130
+ "mShopsShipping": {
131
+ "enabled": True
132
+ },
133
+ "addFreeShippingCostToPrice": True,
134
+ "category": {
135
+ "meliId": "string",
136
+ "acceptsMercadoenvios": True,
137
+ "suggest": True,
138
+ "fixed": True
139
+ },
140
+ "attributeCompletion": {
141
+ "productIdentifierStatus": "Complete",
142
+ "dataSheetStatus": "Complete",
143
+ "status": "Complete",
144
+ "count": 0,
145
+ "total": 0
146
+ },
147
+ "catalogProducts": [
148
+ "string"
149
+ ],
150
+ "warranty": "string",
151
+ "domain": "string",
152
+ "listingTypeId": "GoldSpecial",
153
+ "catalogProductsStatus": "Unlinked",
154
+ "tags": [
155
+ "string"
156
+ ]
157
+ }
158
+
159
+ mock_response.json.return_value = meli_product
87
160
  mock_get.return_value = mock_response
88
161
 
89
- product, status_code = Product.get_ml_integration(self.config, 1)
162
+ product = self.client.Product.get_ml_integration(1)
90
163
 
91
- self.assertEqual(status_code, 200)
92
- self.assertEqual(product.sku, "TEST001")
164
+ self.assertEqual(product.listing_type_id, "GoldSpecial")
165
+
93
166
 
94
167
  if __name__ == '__main__':
95
168
  unittest.main()
@@ -1,81 +1,12 @@
1
1
  import unittest
2
2
  from unittest.mock import patch, Mock
3
- from producteca.config.config import ConfigProducteca
4
- from producteca.search.search_sale_orders import SearchSalesOrder, SearchSalesOrderParams, SearchSalesOrderResponse
5
- from producteca.search.search import SearchProduct, SearchProductParams
6
-
7
- class TestSearchSalesOrder(unittest.TestCase):
8
- def setUp(self):
9
- self.config = ConfigProducteca(
10
- token="test_client_id",
11
- api_key="test_client_secret",
12
- )
13
- self.params = SearchSalesOrderParams(
14
- top=10,
15
- skip=0,
16
- filter="status eq 'confirmed'"
17
- )
18
-
19
- @patch('requests.get')
20
- def test_search_saleorder_success(self, mock_get):
21
- # Mock successful response
22
- mock_response = Mock()
23
- mock_response.json.return_value = {
24
- "count": 1,
25
- "results": [{
26
- "id": "123",
27
- "status": "confirmed",
28
- "lines": [],
29
- "payments": [],
30
- "shipments": [],
31
- "integrations": [],
32
- "codes": [],
33
- "integration_ids": [],
34
- "product_names": [],
35
- "skus": [],
36
- "tags": [],
37
- "brands": []
38
- }]
39
- }
40
- mock_response.status_code = 200
41
- mock_get.return_value = mock_response
42
-
43
- response, status_code = SearchSalesOrder.search_saleorder(self.config, self.params)
44
-
45
- # Validate response
46
- self.assertEqual(status_code, 200)
47
- self.assertEqual(response["count"], 1)
48
- self.assertEqual(len(response["results"]), 1)
49
- self.assertEqual(response["results"][0]["id"], "123")
50
-
51
- # Verify the request was made with correct parameters
52
- expected_url = f"{self.config.get_endpoint(SearchSalesOrder.endpoint)}?$filter={self.params.filter}&top={self.params.top}&skip={self.params.skip}"
53
- mock_get.assert_called_once_with(
54
- expected_url,
55
- headers=self.config.headers
56
- )
57
-
58
- @patch('requests.get')
59
- def test_search_saleorder_error(self, mock_get):
60
- # Mock error response
61
- mock_response = Mock()
62
- mock_response.json.return_value = {"error": "Invalid request"}
63
- mock_response.status_code = 400
64
- mock_get.return_value = mock_response
65
-
66
- response, status_code = SearchSalesOrder.search_saleorder(self.config, self.params)
67
-
68
- # Validate error response
69
- self.assertEqual(status_code, 400)
70
- self.assertEqual(response["error"], "Invalid request")
3
+ from producteca.products.search_products import SearchProductParams
4
+ from producteca.client import ProductecaClient
71
5
 
72
6
 
73
7
  class TestSearchProduct(unittest.TestCase):
74
8
  def setUp(self):
75
- self.config = ConfigProducteca(
76
- token="test_client_id",
77
- api_key="test_client_secret",
78
- )
9
+ self.client = ProductecaClient(token="test_client_id", api_key="test_client_secret")
79
10
  self.params = SearchProductParams(
80
11
  top=10,
81
12
  skip=0,
@@ -103,8 +34,8 @@ class TestSearchProduct(unittest.TestCase):
103
34
  "results": [{
104
35
  "@search.score": 1.0,
105
36
  "id": 123,
106
- "product_id": 456,
107
- "company_id": 789,
37
+ "productId": 456,
38
+ "companyId": 789,
108
39
  "name": "Test Product",
109
40
  "code": "TEST-001",
110
41
  "skus": ["SKU001"],
@@ -116,18 +47,18 @@ class TestSearchProduct(unittest.TestCase):
116
47
  "quantity": 10,
117
48
  "reserved": 0
118
49
  }],
119
- "warehouses_with_stock": ["Main"],
120
- "total_stock": 10,
121
- "has_pictures": True,
122
- "buying_price": 100.0,
50
+ "warehousesWithStock": ["Main"],
51
+ "totalStock": 10,
52
+ "hasPictures": True,
53
+ "buyingPrice": 100.0,
123
54
  "prices": [{
124
- "price_list_id": 1,
125
- "price_list": "Default",
55
+ "priceListId": 1,
56
+ "priceList": "Default",
126
57
  "amount": 200.0,
127
58
  "currency": "USD"
128
59
  }],
129
- "integration_ids": ["INT001"],
130
- "integration_apps": ["APP1"],
60
+ "integrationIds": ["INT001"],
61
+ "integrationApps": ["APP1"],
131
62
  "integrations": [],
132
63
  "campaigns": [],
133
64
  "app": None,
@@ -181,15 +112,13 @@ class TestSearchProduct(unittest.TestCase):
181
112
  mock_response.status_code = 200
182
113
  mock_get.return_value = mock_response
183
114
 
184
- response = SearchProduct.search_product(self.config, self.params)
115
+ response = self.client.Product.search(self.params)
185
116
 
186
117
  # Validate response
187
118
  self.assertEqual(response.count, 1)
188
119
  self.assertEqual(len(response.results), 1)
189
120
  self.assertEqual(response.results[0].id, 123)
190
121
  self.assertEqual(response.results[0].name, "Test Product")
191
-
192
-
193
122
 
194
123
  @patch('requests.get')
195
124
  def test_search_product_error(self, mock_get):
@@ -200,7 +129,7 @@ class TestSearchProduct(unittest.TestCase):
200
129
  mock_get.return_value = mock_response
201
130
  # TODO: Fix this
202
131
  # with self.assertRaises(Exception):
203
- # SearchProduct.search_product(self.config, self.params)
132
+ # self.client.Product.search(self.params)
204
133
 
205
134
 
206
135
  if __name__ == '__main__':
@@ -1 +0,0 @@
1
- from . import sales_orders