Skip to content

Commit 438743d

Browse files
Feature/products prices actions (#93)
* Add product testing actions for prices * Add testing addPrice / changePrice actions * pin isort to same value everywhere (tox, setup.py) * try a different isort version Co-authored-by: David Weterings <d.weterings@labdigital.nl>
1 parent 557f98c commit 438743d

File tree

7 files changed

+191
-38
lines changed

7 files changed

+191
-38
lines changed

CHANGES

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
8.1.5 (xxxx-xx-xx)
1+
8.1.5 (2020-07-15)
22
------------------
33
- Fixed API extensions endpoints
4+
- Testing: add product change/add price actions
45

56

67
8.1.4 (2020-06-11)

setup.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
"astunparse==1.6.3",
1919
"attrs>=18.2.0",
2020
"black==18.9b0",
21-
"isort[pyproject]",
21+
"isort[pyproject]==4.3.21",
2222
"PyYAML==3.13",
2323
]
2424

@@ -36,7 +36,7 @@
3636
"pytest-cov==2.5.1",
3737
"pytest==3.1.3",
3838
# Linting
39-
"isort==4.2.15",
39+
"isort==4.3.21",
4040
"flake8==3.3.0",
4141
"flake8-blind-except==0.1.1",
4242
"flake8-debugger==1.4.0",

src/commercetools/services/extensions.py

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,11 @@ def query(
4343

4444
def create(self, draft: types.ExtensionDraft) -> types.Extension:
4545
return self._client._post(
46-
"extensions", {}, draft, schemas.ExtensionDraftSchema, schemas.ExtensionSchema
46+
"extensions",
47+
{},
48+
draft,
49+
schemas.ExtensionDraftSchema,
50+
schemas.ExtensionSchema,
4751
)
4852

4953
def update_by_id(

src/commercetools/testing/products.py

Lines changed: 93 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,13 @@
11
import copy
22
import datetime
3-
import typing
43
import uuid
4+
from typing import List, Optional, Union
55

66
from marshmallow import Schema
77
from marshmallow import fields as schema_fields
88

99
from commercetools import schemas, types
10+
from commercetools.testing import utils
1011
from commercetools.testing.abstract import BaseModel, ServiceBackend
1112
from commercetools.testing.utils import (
1213
create_commercetools_response,
@@ -22,7 +23,7 @@ class ProductsModel(BaseModel):
2223
_unique_values = ["key"]
2324

2425
def _create_from_draft(
25-
self, draft: types.ProductDraft, id: typing.Optional[str] = None
26+
self, draft: types.ProductDraft, id: Optional[str] = None
2627
) -> types.Product:
2728
object_id = str(uuid.UUID(id) if id is not None else uuid.uuid4())
2829

@@ -63,11 +64,11 @@ def _create_variant_from_draft(
6364
self, draft: types.ProductVariantDraft
6465
) -> types.ProductVariant:
6566

66-
assets: typing.Optional[typing.List[types.Asset]] = None
67+
assets: Optional[List[types.Asset]] = None
6768
if draft.assets:
6869
assets = self._create_assets_from_draft(draft.assets)
6970

70-
prices: typing.Optional[typing.List[types.Price]] = None
71+
prices: Optional[List[types.Price]] = None
7172
if draft.prices:
7273
prices = self._create_prices_from_draft(draft.prices)
7374

@@ -87,11 +88,11 @@ def _create_variant_from_draft(
8788
)
8889

8990
def _create_assets_from_draft(
90-
self, drafts: typing.List[types.AssetDraft]
91-
) -> typing.List[types.Asset]:
92-
assets: typing.List[types.Asset] = []
91+
self, drafts: List[types.AssetDraft]
92+
) -> List[types.Asset]:
93+
assets: List[types.Asset] = []
9394
for draft in drafts:
94-
custom: typing.Optional[types.CustomFields] = None
95+
custom: Optional[types.CustomFields] = None
9596
if draft.custom:
9697
custom = custom_fields_from_draft(self._storage, draft.custom)
9798

@@ -107,9 +108,9 @@ def _create_assets_from_draft(
107108
return assets
108109

109110
def _create_prices_from_draft(
110-
self, drafts: typing.List[types.PriceDraft]
111-
) -> typing.List[types.Price]:
112-
prices: typing.List[types.Price] = []
111+
self, drafts: List[types.PriceDraft]
112+
) -> List[types.Price]:
113+
prices: List[types.Price] = []
113114
for draft in drafts:
114115
custom = None
115116
if draft.custom:
@@ -130,8 +131,8 @@ def _create_prices_from_draft(
130131
return prices
131132

132133
def _create_price_from_draft(
133-
self, draft: typing.Optional[types.TypedMoneyDraft]
134-
) -> typing.Optional[types.TypedMoney]:
134+
self, draft: Union[types.Money, Optional[types.TypedMoneyDraft]]
135+
) -> Optional[types.TypedMoney]:
135136
if draft is None:
136137
return None
137138

@@ -163,9 +164,7 @@ def _get_target_obj(obj: dict, staged: bool):
163164
return obj["masterData"]["staged"]
164165

165166

166-
def _get_variant(
167-
data_object: dict, *, id: str = "", sku: str = ""
168-
) -> typing.Optional[dict]:
167+
def _get_variant(data_object: dict, *, id: int = "", sku: str = "") -> Optional[dict]:
169168
if not data_object:
170169
return None
171170

@@ -251,35 +250,40 @@ def updater(self, obj: dict, action: types.ProductPublishAction):
251250
# not implemented scopes right now.
252251
if new["masterData"].get("staged"):
253252
new["masterData"]["current"] = new["masterData"]["staged"]
253+
del new["masterData"]["staged"]
254254
new["masterData"]["hasStagedChanges"] = False
255255
new["masterData"]["published"] = True
256256
return new
257257

258258
return updater
259259

260260

261+
def convert_draft_price(
262+
price_draft: types.PriceDraft, price_id: str = None
263+
) -> types.Price:
264+
tiers: Optional[List[types.PriceTier]] = None
265+
if price_draft.tiers:
266+
tiers = [utils.create_from_draft(tier) for tier in price_draft.tiers]
267+
return types.Price(
268+
id=price_id or str(uuid.uuid4()),
269+
country=price_draft.country,
270+
channel=price_draft.channel,
271+
value=utils._money_to_typed(price_draft.value),
272+
valid_from=price_draft.valid_from,
273+
valid_until=price_draft.valid_until,
274+
discounted=price_draft.discounted,
275+
custom=utils.create_from_draft(price_draft.custom),
276+
tiers=tiers,
277+
)
278+
279+
261280
def _set_product_prices():
262281
def updater(self, obj: dict, action: types.ProductSetPricesAction):
263282
new = copy.deepcopy(obj)
264283
target_obj = _get_target_obj(new, getattr(action, "staged", True))
265284
prices = []
266285
for price_draft in action.prices:
267-
price = types.Price(
268-
id=str(uuid.uuid4()),
269-
country=price_draft.country,
270-
channel=price_draft.channel,
271-
value=types.TypedMoney(
272-
fraction_digits=2,
273-
cent_amount=price_draft.value.cent_amount,
274-
currency_code=price_draft.value.currency_code,
275-
type=types.MoneyType.CENT_PRECISION,
276-
),
277-
valid_from=price_draft.valid_from,
278-
valid_until=price_draft.valid_until,
279-
discounted=price_draft.discounted,
280-
custom=price_draft.custom,
281-
tiers=price_draft.tiers,
282-
)
286+
price = convert_draft_price(price_draft)
283287
prices.append(price)
284288

285289
schema = schemas.PriceSchema()
@@ -293,6 +297,61 @@ def updater(self, obj: dict, action: types.ProductSetPricesAction):
293297
return updater
294298

295299

300+
def _change_price():
301+
def updater(self, obj: dict, action: types.ProductChangePriceAction):
302+
new = copy.deepcopy(obj)
303+
staged = action.staged
304+
if staged is None:
305+
staged = True
306+
target_obj = _get_target_obj(new, staged)
307+
changed_price = convert_draft_price(action.price, action.price_id)
308+
schema = schemas.PriceSchema()
309+
310+
found_price = True
311+
for variant in get_product_variants(target_obj):
312+
for index, price in enumerate(variant["prices"]):
313+
if price["id"] == action.price_id:
314+
variant["prices"][index] = schema.dump(changed_price)
315+
found_price = True
316+
break
317+
if not found_price:
318+
raise ValueError("Could not find price with id %s" % action.price_id)
319+
if staged:
320+
new["masterData"]["hasStagedChanges"] = True
321+
return new
322+
323+
return updater
324+
325+
326+
def _add_price():
327+
def updater(self, obj: dict, action: types.ProductAddPriceAction):
328+
new = copy.deepcopy(obj)
329+
staged = action.staged
330+
if staged is None:
331+
staged = True
332+
target_obj = _get_target_obj(new, staged)
333+
new_price = convert_draft_price(action.price)
334+
schema = schemas.PriceSchema()
335+
336+
found_sku = False
337+
for variant in get_product_variants(target_obj):
338+
if variant["sku"] == action.sku:
339+
if "prices" not in variant:
340+
variant["prices"] = []
341+
elif not variant["prices"]:
342+
variant["prices"] = []
343+
variant["prices"].append(schema.dump(new_price))
344+
found_sku = True
345+
break
346+
if not found_sku:
347+
raise ValueError("Could not find sku %s" % action.sku)
348+
if staged:
349+
new["masterData"]["hasStagedChanges"] = True
350+
return new
351+
352+
return updater
353+
354+
296355
class UploadImageQuerySchema(Schema):
297356
staged = schema_fields.Bool()
298357
filename = schema_fields.Field()
@@ -325,6 +384,8 @@ def urls(self):
325384
"setAttribute": _set_attribute_action(),
326385
"addVariant": _add_variant_action(),
327386
"setPrices": _set_product_prices(),
387+
"changePrice": _change_price(),
388+
"addPrice": _add_price(),
328389
"publish": _publish_product_action(),
329390
}
330391

src/commercetools/testing/utils.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -51,7 +51,20 @@ def create_from_draft(draft):
5151
return None
5252

5353
if isinstance(draft, types.CustomFieldsDraft):
54-
return types.CustomFields(type=draft.type, fields=draft.fields)
54+
return types.CustomFields(
55+
type=types.TypeReference(type_id=draft.type.type_id, id=draft.type.id),
56+
fields=draft.fields,
57+
)
58+
if isinstance(draft, types.PriceTierDraft):
59+
return types.PriceTier(
60+
minimum_quantity=draft.minimum_quantity,
61+
value=_money_to_typed(
62+
types.Money(
63+
cent_amount=draft.value.cent_amount,
64+
currency_code=draft.value.currency_code,
65+
)
66+
),
67+
)
5568

5669
raise ValueError(f"Unsupported type {draft.__class__}")
5770

tests/test_service_products.py

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -197,3 +197,77 @@ def test_product_update(client):
197197
],
198198
)
199199
assert product.key == "test-product"
200+
201+
202+
def test_product_update_add_change_price_staged(client):
203+
product = client.products.create(
204+
types.ProductDraft(
205+
key="test-product",
206+
master_variant=types.ProductVariantDraft(sku="1", key="1"),
207+
)
208+
)
209+
210+
product = client.products.update_by_id(
211+
id=product.id,
212+
version=product.version,
213+
actions=[
214+
types.ProductAddPriceAction(
215+
sku="1",
216+
price=types.PriceDraft(
217+
value=types.Money(cent_amount=1000, currency_code="GBP")
218+
),
219+
)
220+
],
221+
)
222+
223+
assert product.master_data.current is None
224+
assert len(product.master_data.staged.master_variant.prices) == 1
225+
price = product.master_data.staged.master_variant.prices[0]
226+
assert price.value.cent_amount == 1000
227+
assert price.value.currency_code == "GBP"
228+
229+
product = client.products.update_by_id(
230+
id=product.id,
231+
version=product.version,
232+
actions=[
233+
types.ProductChangePriceAction(
234+
price_id=price.id,
235+
price=types.PriceDraft(
236+
value=types.Money(cent_amount=3000, currency_code="EUR")
237+
),
238+
)
239+
],
240+
)
241+
242+
assert product.master_data.current is None
243+
assert len(product.master_data.staged.master_variant.prices) == 1
244+
price = product.master_data.staged.master_variant.prices[0]
245+
assert price.value.cent_amount == 3000
246+
assert price.value.currency_code == "EUR"
247+
248+
249+
def test_product_update_add_price_current(client):
250+
product = client.products.create(
251+
types.ProductDraft(
252+
key="test-product",
253+
master_variant=types.ProductVariantDraft(sku="1", key="1"),
254+
publish=True,
255+
)
256+
)
257+
258+
product = client.products.update_by_id(
259+
id=product.id,
260+
version=product.version,
261+
actions=[
262+
types.ProductAddPriceAction(
263+
sku="1",
264+
staged=False,
265+
price=types.PriceDraft(
266+
value=types.Money(cent_amount=1000, currency_code="GBP")
267+
),
268+
)
269+
],
270+
)
271+
272+
assert product.master_data.staged is None
273+
assert len(product.master_data.current.master_variant.prices) == 1

tox.ini

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ commands =
2525
basepython = python3.7
2626
deps =
2727
black==18.9b0
28-
isort[toml]
28+
isort[toml]==4.3.21
2929
skip_install = true
3030
commands =
3131
isort --recursive --check-only src tests

0 commit comments

Comments
 (0)