Skip to content

Commit b256d60

Browse files
committed
Parameter model Proof of concept
1 parent 68c9e07 commit b256d60

File tree

3 files changed

+196
-6
lines changed

3 files changed

+196
-6
lines changed

mpt_api_client/models/model.py

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,7 @@ class MptBox(Box):
1515
"""python-box that preserves camelCase keys when converted to json."""
1616

1717
def __init__(self, *args, attribute_mapping: dict[str, str] | None = None, **_): # type: ignore[no-untyped-def]
18-
attribute_mapping = attribute_mapping or {}
19-
self._attribute_mapping = attribute_mapping
18+
self._attribute_mapping = (attribute_mapping or {}).copy()
2019
super().__init__(
2120
*args,
2221
camel_killer_box=False,
@@ -34,7 +33,8 @@ def __setattr__(self, item: str, value: Any) -> None:
3433
if item in _box_safe_attributes:
3534
return object.__setattr__(self, item, value)
3635

37-
super().__setattr__(item, value) # type: ignore[no-untyped-call]
36+
mapped_key = self._prep_key(item)
37+
super().__setattr__(mapped_key, value) # type: ignore[no-untyped-call]
3838
return None
3939

4040
@override
@@ -50,16 +50,20 @@ def to_dict(self) -> dict[str, Any]: # noqa: WPS210
5050
}
5151
out_dict = {}
5252
for parsed_key, item_value in super().to_dict().items():
53-
original_key = reverse_mapping[parsed_key]
53+
original_key = reverse_mapping.get(parsed_key, parsed_key)
5454
out_dict[original_key] = item_value
5555
return out_dict
5656

5757
def _prep_key(self, key: str) -> str:
5858
try:
5959
return self._attribute_mapping[key]
6060
except KeyError:
61-
self._attribute_mapping[key] = _camel_killer(key)
62-
return self._attribute_mapping[key]
61+
# Check if key is already a value in the mapping (it's already the API key)
62+
if key in self._attribute_mapping.values():
63+
return key
64+
mapped_key = _camel_killer(key)
65+
self._attribute_mapping[key] = mapped_key
66+
return mapped_key # type: ignore[no-any-return]
6367

6468

6569
class Model: # noqa: WPS214
@@ -96,6 +100,12 @@ def __setattr__(self, attribute: str, attribute_value: Any) -> None:
96100
object.__setattr__(self, attribute, attribute_value)
97101
return
98102

103+
# Respect descriptors (e.g., @property setters) defined on the class
104+
cls_attr = getattr(self.__class__, attribute, None)
105+
if isinstance(cls_attr, property) and cls_attr.fset is not None:
106+
cls_attr.fset(self, attribute_value)
107+
return
108+
99109
self._box.__setattr__(attribute, attribute_value)
100110

101111
@classmethod

mpt_api_client/resources/catalog/products_parameters.py

Lines changed: 104 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
from typing import Any, ClassVar
2+
13
from mpt_api_client.http import AsyncService, Service
24
from mpt_api_client.http.mixins import (
35
AsyncCollectionMixin,
@@ -11,6 +13,108 @@
1113
class Parameter(Model):
1214
"""Parameter resource."""
1315

16+
_attribute_mapping: ClassVar[dict[str, str]] = {"externalId": "external_id"}
17+
18+
@property
19+
def type_(self) -> str:
20+
"""Returns the parameter type."""
21+
return str(self._box.get("type", "")) # type: ignore[no-untyped-call]
22+
23+
@type_.setter
24+
def type_(self, value: str) -> None:
25+
"""Sets the parameter type."""
26+
self._box.type = value
27+
28+
@property
29+
def scope(self) -> str:
30+
"""Returns the parameter scope."""
31+
return str(self._box.get("scope", "")) # type: ignore[no-untyped-call]
32+
33+
@scope.setter
34+
def scope(self, value: str) -> None:
35+
"""Sets the parameter scope."""
36+
self._box.scope = value
37+
38+
@property
39+
def phase(self) -> str:
40+
"""Returns the parameter phase."""
41+
return str(self._box.get("phase", "")) # type: ignore[no-untyped-call]
42+
43+
@phase.setter
44+
def phase(self, value: str) -> None:
45+
"""Sets the parameter phase."""
46+
self._box.phase = value
47+
48+
@property
49+
def context(self) -> str:
50+
"""Returns the parameter context."""
51+
return str(self._box.get("context", "")) # type: ignore[no-untyped-call]
52+
53+
@context.setter
54+
def context(self, value: str) -> None:
55+
"""Sets the parameter context."""
56+
self._box.context = value
57+
58+
@property
59+
def options(self) -> dict[str, Any]:
60+
"""Returns the parameter options."""
61+
return self._box.get("options", {}) # type: ignore[no-any-return, no-untyped-call]
62+
63+
@options.setter
64+
def options(self, value: dict[str, Any]) -> None:
65+
"""Sets the parameter options."""
66+
self._box.options = value
67+
68+
@property
69+
def multiple(self) -> bool:
70+
"""Returns whether the parameter allows multiple values."""
71+
return bool(self._box.get("multiple", False)) # type: ignore[no-untyped-call]
72+
73+
@multiple.setter
74+
def multiple(self, value: bool) -> None:
75+
"""Sets whether the parameter allows multiple values."""
76+
self._box.multiple = value
77+
78+
@property
79+
def constraints(self) -> dict[str, Any]:
80+
"""Returns the parameter constraints."""
81+
return self._box.get("constraints", {}) # type: ignore[no-any-return, no-untyped-call]
82+
83+
@constraints.setter
84+
def constraints(self, value: dict[str, Any]) -> None:
85+
"""Sets the parameter constraints."""
86+
self._box.constraints = value
87+
88+
@property
89+
def group(self) -> dict[str, Any]:
90+
"""Returns the parameter group."""
91+
return self._box.get("group", {}) # type: ignore[no-any-return,no-untyped-call]
92+
93+
@group.setter
94+
def group(self, value: dict[str, Any]) -> None:
95+
"""Sets the parameter group."""
96+
self._box.group = value
97+
98+
@property
99+
def external_id(self) -> str:
100+
"""Returns the parameter external ID."""
101+
return str(self._box.get("external_id", "")) # type: ignore[no-untyped-call]
102+
103+
@external_id.setter
104+
def external_id(self, value: str) -> None:
105+
"""Sets the parameter external ID."""
106+
self._box.external_id = value
107+
108+
@property
109+
def status(self) -> str:
110+
"""Returns the parameter status."""
111+
return str(self._box.get("status", "")) # type: ignore[no-untyped-call]
112+
113+
@status.setter
114+
def status(self, value: str) -> None:
115+
"""Sets the parameter status."""
116+
self._box.status = value
117+
14118

15119
class ParametersServiceConfig:
16120
"""Parameters service configuration."""
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
from mpt_api_client.resources.catalog.products_parameters import Parameter
2+
3+
4+
def test_parameter_properties_getters(): # noqa: WPS218
5+
parameter_data = {
6+
"id": "PAR-1",
7+
"scope": "Order",
8+
"phase": "Order",
9+
"context": "Purchase",
10+
"options": {"label": "Email"},
11+
"multiple": True,
12+
"constraints": {"required": True},
13+
"group": {"id": "GRP-1"},
14+
"externalId": "ext-1",
15+
"status": "Active",
16+
}
17+
18+
result = Parameter(parameter_data)
19+
20+
assert result.id == "PAR-1"
21+
assert result.scope == "Order"
22+
assert result.phase == "Order"
23+
assert result.context == "Purchase"
24+
assert result.options == {"label": "Email"}
25+
assert result.multiple is True
26+
assert result.constraints == {"required": True}
27+
assert result.group == {"id": "GRP-1"}
28+
assert result.external_id == "ext-1"
29+
assert result.status == "Active"
30+
31+
32+
def test_parameter_properties_setters(): # noqa: WPS218
33+
parameter = Parameter()
34+
parameter.scope = "Agreement"
35+
parameter.phase = "Configuration"
36+
parameter.context = "None"
37+
parameter.type_ = "Text"
38+
parameter.options = {"label": "Name"}
39+
parameter.multiple = False
40+
parameter.constraints = {"required": False}
41+
parameter.group = {"id": "GRP-2"}
42+
parameter.external_id = "ext-2"
43+
parameter.status = "Inactive"
44+
45+
result = parameter
46+
47+
assert result.scope == "Agreement"
48+
assert result.phase == "Configuration"
49+
assert result.context == "None"
50+
assert result.type_ == "Text"
51+
assert result.options == {"label": "Name"}
52+
assert result.multiple is False
53+
assert result.constraints == {"required": False}
54+
assert result.group == {"id": "GRP-2"}
55+
assert result.external_id == "ext-2"
56+
assert result.status == "Inactive"
57+
result_dict = result.to_dict()
58+
assert result_dict["scope"] == "Agreement"
59+
assert result_dict["externalId"] == "ext-2"
60+
assert result_dict["scope"] == "Agreement"
61+
assert result_dict["externalId"] == "ext-2"
62+
63+
64+
def test_parameter_default_values(): # noqa: WPS218
65+
result = Parameter()
66+
67+
assert not result.id
68+
assert not result.scope
69+
assert not result.phase
70+
assert not result.context
71+
assert result.options == {}
72+
assert result.multiple is False
73+
assert result.constraints == {}
74+
assert result.group == {}
75+
assert not result.external_id
76+
assert not result.status

0 commit comments

Comments
 (0)