Skip to content

Commit a08c360

Browse files
Feature/store distribution channels (#94)
* Regenerate types to include store distribution channels * Implement testing actions store set distribution channels * Add missing delete channel testing method Co-authored-by: David Weterings <d.weterings@labdigital.nl>
1 parent 3f8788c commit a08c360

File tree

13 files changed

+395
-18
lines changed

13 files changed

+395
-18
lines changed

CHANGES

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,8 @@
1+
8.2.0 (2020-07-20)
2+
------------------
3+
- Regenerate types (mainly Store distribution channels functionality)
4+
- Store: distribution channel functionality including testing actions added
5+
16
8.1.5 (2020-07-15)
27
------------------
38
- Fixed API extensions endpoints

src/commercetools/schemas/_error.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"InvalidOperationErrorSchema",
3434
"InvalidSubjectErrorSchema",
3535
"InvalidTokenErrorSchema",
36+
"LanguageUsedInStoresErrorSchema",
3637
"MatchingPriceNotFoundErrorSchema",
3738
"MissingTaxRateForCountryErrorSchema",
3839
"NoMatchingProductDiscountFoundErrorSchema",
@@ -106,6 +107,7 @@ class ErrorResponseSchema(marshmallow.Schema):
106107
"InvalidOperation": "commercetools.schemas._error.InvalidOperationErrorSchema",
107108
"InvalidSubject": "commercetools.schemas._error.InvalidSubjectErrorSchema",
108109
"invalid_token": "commercetools.schemas._error.InvalidTokenErrorSchema",
110+
"LanguageUsedInStores": "commercetools.schemas._error.LanguageUsedInStoresErrorSchema",
109111
"MatchingPriceNotFound": "commercetools.schemas._error.MatchingPriceNotFoundErrorSchema",
110112
"MissingTaxRateForCountry": "commercetools.schemas._error.MissingTaxRateForCountryErrorSchema",
111113
"NoMatchingProductDiscountFound": "commercetools.schemas._error.NoMatchingProductDiscountFoundErrorSchema",
@@ -593,6 +595,18 @@ def post_load(self, data, **kwargs):
593595
return types.InvalidTokenError(**data)
594596

595597

598+
class LanguageUsedInStoresErrorSchema(ErrorObjectSchema):
599+
"Marshmallow schema for :class:`commercetools.types.LanguageUsedInStoresError`."
600+
601+
class Meta:
602+
unknown = marshmallow.EXCLUDE
603+
604+
@marshmallow.post_load
605+
def post_load(self, data, **kwargs):
606+
del data["code"]
607+
return types.LanguageUsedInStoresError(**data)
608+
609+
596610
class MatchingPriceNotFoundErrorSchema(ErrorObjectSchema):
597611
"Marshmallow schema for :class:`commercetools.types.MatchingPriceNotFoundError`."
598612
product_id = marshmallow.fields.String(allow_none=True, data_key="productId")

src/commercetools/schemas/_order_edit.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2129,6 +2129,7 @@ class OrderEditPreviewFailureSchema(OrderEditResultSchema):
21292129
"InvalidOperation": "commercetools.schemas._error.InvalidOperationErrorSchema",
21302130
"InvalidSubject": "commercetools.schemas._error.InvalidSubjectErrorSchema",
21312131
"invalid_token": "commercetools.schemas._error.InvalidTokenErrorSchema",
2132+
"LanguageUsedInStores": "commercetools.schemas._error.LanguageUsedInStoresErrorSchema",
21322133
"MatchingPriceNotFound": "commercetools.schemas._error.MatchingPriceNotFoundErrorSchema",
21332134
"MissingTaxRateForCountry": "commercetools.schemas._error.MissingTaxRateForCountryErrorSchema",
21342135
"NoMatchingProductDiscountFound": "commercetools.schemas._error.NoMatchingProductDiscountFoundErrorSchema",

src/commercetools/schemas/_project.py

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@
99
"CartClassificationTypeSchema",
1010
"CartScoreTypeSchema",
1111
"CartValueTypeSchema",
12+
"CartsConfigurationSchema",
1213
"ExternalOAuthSchema",
1314
"ProjectChangeCountriesActionSchema",
15+
"ProjectChangeCountryTaxRateFallbackEnabledActionSchema",
1416
"ProjectChangeCurrenciesActionSchema",
1517
"ProjectChangeLanguagesActionSchema",
1618
"ProjectChangeMessagesConfigurationActionSchema",
@@ -25,6 +27,20 @@
2527
]
2628

2729

30+
class CartsConfigurationSchema(marshmallow.Schema):
31+
"Marshmallow schema for :class:`commercetools.types.CartsConfiguration`."
32+
country_tax_rate_fallback_enabled = marshmallow.fields.Bool(
33+
allow_none=True, missing=None, data_key="countryTaxRateFallbackEnabled"
34+
)
35+
36+
class Meta:
37+
unknown = marshmallow.EXCLUDE
38+
39+
@marshmallow.post_load
40+
def post_load(self, data, **kwargs):
41+
return types.CartsConfiguration(**data)
42+
43+
2844
class ExternalOAuthSchema(marshmallow.Schema):
2945
"Marshmallow schema for :class:`commercetools.types.ExternalOAuth`."
3046
url = marshmallow.fields.String(allow_none=True)
@@ -76,6 +92,11 @@ class ProjectSchema(marshmallow.Schema):
7692
missing=None,
7793
data_key="externalOAuth",
7894
)
95+
carts = marshmallow.fields.Nested(
96+
nested="commercetools.schemas._project.CartsConfigurationSchema",
97+
unknown=marshmallow.EXCLUDE,
98+
allow_none=True,
99+
)
79100

80101
class Meta:
81102
unknown = marshmallow.EXCLUDE
@@ -106,6 +127,7 @@ class ProjectUpdateSchema(marshmallow.Schema):
106127
discriminator_field=("action", "action"),
107128
discriminator_schemas={
108129
"changeCountries": "commercetools.schemas._project.ProjectChangeCountriesActionSchema",
130+
"changeCountryTaxRateFallbackEnabled": "commercetools.schemas._project.ProjectChangeCountryTaxRateFallbackEnabledActionSchema",
109131
"changeCurrencies": "commercetools.schemas._project.ProjectChangeCurrenciesActionSchema",
110132
"changeLanguages": "commercetools.schemas._project.ProjectChangeLanguagesActionSchema",
111133
"changeMessagesConfiguration": "commercetools.schemas._project.ProjectChangeMessagesConfigurationActionSchema",
@@ -198,6 +220,21 @@ def post_load(self, data, **kwargs):
198220
return types.ProjectChangeCountriesAction(**data)
199221

200222

223+
class ProjectChangeCountryTaxRateFallbackEnabledActionSchema(ProjectUpdateActionSchema):
224+
"Marshmallow schema for :class:`commercetools.types.ProjectChangeCountryTaxRateFallbackEnabledAction`."
225+
country_tax_rate_fallback_enabled = marshmallow.fields.Bool(
226+
allow_none=True, data_key="countryTaxRateFallbackEnabled"
227+
)
228+
229+
class Meta:
230+
unknown = marshmallow.EXCLUDE
231+
232+
@marshmallow.post_load
233+
def post_load(self, data, **kwargs):
234+
del data["action"]
235+
return types.ProjectChangeCountryTaxRateFallbackEnabledAction(**data)
236+
237+
201238
class ProjectChangeCurrenciesActionSchema(ProjectUpdateActionSchema):
202239
"Marshmallow schema for :class:`commercetools.types.ProjectChangeCurrenciesAction`."
203240
currencies = marshmallow.fields.List(marshmallow.fields.String())

src/commercetools/schemas/_shipping_method.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -89,10 +89,10 @@ def post_load(self, data, **kwargs):
8989

9090
class ShippingMethodPagedQueryResponseSchema(marshmallow.Schema):
9191
"Marshmallow schema for :class:`commercetools.types.ShippingMethodPagedQueryResponse`."
92-
limit = marshmallow.fields.Integer(allow_none=True)
92+
limit = marshmallow.fields.Integer(allow_none=True, missing=None)
9393
count = marshmallow.fields.Integer(allow_none=True)
9494
total = marshmallow.fields.Integer(allow_none=True, missing=None)
95-
offset = marshmallow.fields.Integer(allow_none=True)
95+
offset = marshmallow.fields.Integer(allow_none=True, missing=None)
9696
results = marshmallow.fields.Nested(
9797
nested="commercetools.schemas._shipping_method.ShippingMethodSchema",
9898
unknown=marshmallow.EXCLUDE,

src/commercetools/schemas/_store.py

Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,9 @@
2222
"StoreSetNameActionSchema",
2323
"StoreUpdateActionSchema",
2424
"StoreUpdateSchema",
25+
"StoresAddDistributionChannelsActionSchema",
26+
"StoresRemoveDistributionChannelsActionSchema",
27+
"StoresSetDistributionChannelsActionSchema",
2528
]
2629

2730

@@ -32,6 +35,14 @@ class StoreDraftSchema(marshmallow.Schema):
3235
languages = marshmallow.fields.List(
3336
marshmallow.fields.String(allow_none=True), missing=None
3437
)
38+
distribution_channels = marshmallow.fields.Nested(
39+
nested="commercetools.schemas._channel.ChannelResourceIdentifierSchema",
40+
unknown=marshmallow.EXCLUDE,
41+
allow_none=True,
42+
many=True,
43+
missing=None,
44+
data_key="distributionChannels",
45+
)
3546

3647
class Meta:
3748
unknown = marshmallow.EXCLUDE
@@ -131,6 +142,13 @@ class StoreSchema(BaseResourceSchema):
131142
languages = marshmallow.fields.List(
132143
marshmallow.fields.String(allow_none=True), missing=None
133144
)
145+
distribution_channels = marshmallow.fields.Nested(
146+
nested="commercetools.schemas._channel.ChannelReferenceSchema",
147+
unknown=marshmallow.EXCLUDE,
148+
allow_none=True,
149+
many=True,
150+
data_key="distributionChannels",
151+
)
134152

135153
class Meta:
136154
unknown = marshmallow.EXCLUDE
@@ -162,6 +180,9 @@ class StoreUpdateSchema(marshmallow.Schema):
162180
discriminator_schemas={
163181
"setLanguages": "commercetools.schemas._store.StoreSetLanguagesActionSchema",
164182
"setName": "commercetools.schemas._store.StoreSetNameActionSchema",
183+
"addDistributionChannel": "commercetools.schemas._store.StoresAddDistributionChannelsActionSchema",
184+
"removeDistributionChannel": "commercetools.schemas._store.StoresRemoveDistributionChannelsActionSchema",
185+
"setDistributionChannels": "commercetools.schemas._store.StoresSetDistributionChannelsActionSchema",
165186
},
166187
unknown=marshmallow.EXCLUDE,
167188
allow_none=True,
@@ -203,3 +224,59 @@ class Meta:
203224
def post_load(self, data, **kwargs):
204225
del data["action"]
205226
return types.StoreSetNameAction(**data)
227+
228+
229+
class StoresAddDistributionChannelsActionSchema(StoreUpdateActionSchema):
230+
"Marshmallow schema for :class:`commercetools.types.StoresAddDistributionChannelsAction`."
231+
distribution_channel = marshmallow.fields.Nested(
232+
nested="commercetools.schemas._channel.ChannelResourceIdentifierSchema",
233+
unknown=marshmallow.EXCLUDE,
234+
allow_none=True,
235+
data_key="distributionChannel",
236+
)
237+
238+
class Meta:
239+
unknown = marshmallow.EXCLUDE
240+
241+
@marshmallow.post_load
242+
def post_load(self, data, **kwargs):
243+
del data["action"]
244+
return types.StoresAddDistributionChannelsAction(**data)
245+
246+
247+
class StoresRemoveDistributionChannelsActionSchema(StoreUpdateActionSchema):
248+
"Marshmallow schema for :class:`commercetools.types.StoresRemoveDistributionChannelsAction`."
249+
distribution_channel = marshmallow.fields.Nested(
250+
nested="commercetools.schemas._channel.ChannelResourceIdentifierSchema",
251+
unknown=marshmallow.EXCLUDE,
252+
allow_none=True,
253+
data_key="distributionChannel",
254+
)
255+
256+
class Meta:
257+
unknown = marshmallow.EXCLUDE
258+
259+
@marshmallow.post_load
260+
def post_load(self, data, **kwargs):
261+
del data["action"]
262+
return types.StoresRemoveDistributionChannelsAction(**data)
263+
264+
265+
class StoresSetDistributionChannelsActionSchema(StoreUpdateActionSchema):
266+
"Marshmallow schema for :class:`commercetools.types.StoresSetDistributionChannelsAction`."
267+
distribution_channels = marshmallow.fields.Nested(
268+
nested="commercetools.schemas._channel.ChannelResourceIdentifierSchema",
269+
unknown=marshmallow.EXCLUDE,
270+
allow_none=True,
271+
many=True,
272+
missing=None,
273+
data_key="distributionChannels",
274+
)
275+
276+
class Meta:
277+
unknown = marshmallow.EXCLUDE
278+
279+
@marshmallow.post_load
280+
def post_load(self, data, **kwargs):
281+
del data["action"]
282+
return types.StoresSetDistributionChannelsAction(**data)

src/commercetools/testing/channels.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -42,4 +42,5 @@ def urls(self):
4242
("^$", "POST", self.create),
4343
("^(?P<id>[^/]+)$", "GET", self.get_by_id),
4444
("^(?P<id>[^/]+)$", "POST", self.update_by_id),
45+
("^(?P<id>[^/]+)$", "DELETE", self.delete_by_id),
4546
]

src/commercetools/testing/stores.py

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

66
from commercetools import schemas, types
77
from commercetools.testing.abstract import BaseModel, ServiceBackend
8-
from commercetools.testing.utils import update_attribute
8+
from commercetools.testing.utils import InternalUpdateError, update_attribute
99

1010

1111
class StoresModel(BaseModel):
@@ -14,19 +14,50 @@ class StoresModel(BaseModel):
1414
_unique_values = ["key"]
1515

1616
def _create_from_draft(
17-
self, draft: types.StoreDraft, id: typing.Optional[str] = None
17+
self, draft: types.StoreDraft, id: Optional[str] = None
1818
) -> types.Store:
1919
object_id = str(uuid.UUID(id) if id is not None else uuid.uuid4())
20+
distribution_channels: List[types.ChannelReference] = []
21+
if draft.distribution_channels:
22+
distribution_channels = convert_identifiers_to_references(
23+
self._storage._stores["channel"], draft.distribution_channels
24+
)
25+
2026
return types.Store(
2127
id=str(object_id),
2228
version=1,
2329
key=draft.key,
2430
name=draft.name,
2531
created_at=datetime.datetime.now(datetime.timezone.utc),
2632
languages=draft.languages,
33+
distribution_channels=distribution_channels,
2734
)
2835

2936

37+
def convert_identifiers_to_references(
38+
channel_store: Dict, channel_identifiers: List[types.ChannelResourceIdentifier]
39+
) -> List[types.ChannelReference]:
40+
channel_references: List[types.ChannelReference] = []
41+
for ci in channel_identifiers:
42+
channel_data: Optional[Dict] = None
43+
for c in channel_store.values():
44+
if ci.key and c["key"] == ci.key:
45+
channel_data = c
46+
break
47+
if ci.id and c["id"] == ci.id:
48+
channel_data = c
49+
break
50+
if not channel_data:
51+
raise InternalUpdateError("Channel not found.")
52+
channel: types.Channel = schemas.ChannelSchema().load(data=channel_data)
53+
if types.ChannelRoleEnum.PRODUCT_DISTRIBUTION not in channel.roles:
54+
raise InternalUpdateError(
55+
"Channel does not have product distribution role."
56+
)
57+
channel_references.append(types.ChannelReference(id=channel.id))
58+
return channel_references
59+
60+
3061
def set_languages():
3162
def updater(self, obj, action):
3263
value = getattr(action, "languages")
@@ -37,6 +68,23 @@ def updater(self, obj, action):
3768
return updater
3869

3970

71+
def set_distribution_channels(
72+
backend: "StoresBackend",
73+
obj: Dict,
74+
action: types.StoresSetDistributionChannelsAction,
75+
):
76+
channel_references = []
77+
if action.distribution_channels:
78+
channel_references = convert_identifiers_to_references(
79+
backend.model._storage._stores["channel"], action.distribution_channels
80+
)
81+
new = copy.deepcopy(obj)
82+
new["distributionChannels"] = schemas.ChannelResourceIdentifierSchema().dump(
83+
channel_references, many=True
84+
)
85+
return new
86+
87+
4088
class StoresBackend(ServiceBackend):
4189
service_path = "stores"
4290
model_class = StoresModel
@@ -60,4 +108,5 @@ def urls(self):
60108
_actions = {
61109
"setName": update_attribute("name", "name"),
62110
"setLanguages": set_languages(),
111+
"setDistributionChannels": set_distribution_channels,
63112
}

src/commercetools/types/_error.py

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@
3737
"InvalidOperationError",
3838
"InvalidSubjectError",
3939
"InvalidTokenError",
40+
"LanguageUsedInStoresError",
4041
"MatchingPriceNotFoundError",
4142
"MissingTaxRateForCountryError",
4243
"NoMatchingProductDiscountFoundError",
@@ -661,6 +662,19 @@ def __repr__(self) -> str:
661662
return "InvalidTokenError(code=%r, message=%r)" % (self.code, self.message)
662663

663664

665+
class LanguageUsedInStoresError(ErrorObject):
666+
"Corresponding marshmallow schema is :class:`commercetools.schemas.LanguageUsedInStoresErrorSchema`."
667+
668+
def __init__(self, *, code: str = None, message: str = None) -> None:
669+
super().__init__(code="LanguageUsedInStores", message=message)
670+
671+
def __repr__(self) -> str:
672+
return "LanguageUsedInStoresError(code=%r, message=%r)" % (
673+
self.code,
674+
self.message,
675+
)
676+
677+
664678
class MatchingPriceNotFoundError(ErrorObject):
665679
"Corresponding marshmallow schema is :class:`commercetools.schemas.MatchingPriceNotFoundErrorSchema`."
666680
#: :class:`str` `(Named` ``productId`` `in Commercetools)`

0 commit comments

Comments
 (0)