Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions mpt_api_client/models/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,7 @@ class MptBox(Box):
"""python-box that preserves camelCase keys when converted to json."""

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

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

@override
Expand All @@ -50,16 +50,20 @@ def to_dict(self) -> dict[str, Any]: # noqa: WPS210
}
out_dict = {}
for parsed_key, item_value in super().to_dict().items():
original_key = reverse_mapping[parsed_key]
original_key = reverse_mapping.get(parsed_key, parsed_key)
out_dict[original_key] = item_value
return out_dict

def _prep_key(self, key: str) -> str:
try:
return self._attribute_mapping[key]
except KeyError:
self._attribute_mapping[key] = _camel_killer(key)
return self._attribute_mapping[key]
# Check if key is already a value in the mapping (it's already the API key)
if key in self._attribute_mapping.values():
return key
mapped_key = _camel_killer(key)
self._attribute_mapping[key] = mapped_key
return mapped_key # type: ignore[no-any-return]


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

# Respect descriptors (e.g., @property setters) defined on the class
cls_attr = getattr(self.__class__, attribute, None)
if isinstance(cls_attr, property) and cls_attr.fset is not None:
cls_attr.fset(self, attribute_value)
return

self._box.__setattr__(attribute, attribute_value)

@classmethod
Expand Down
104 changes: 104 additions & 0 deletions mpt_api_client/resources/catalog/products_parameters.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,5 @@
from typing import Any, ClassVar

from mpt_api_client.http import AsyncService, Service
from mpt_api_client.http.mixins import (
AsyncCollectionMixin,
Expand All @@ -11,6 +13,108 @@
class Parameter(Model):
"""Parameter resource."""

_attribute_mapping: ClassVar[dict[str, str]] = {"externalId": "external_id"}

@property
def type_(self) -> str:
"""Returns the parameter type."""
return str(self._box.get("type", "")) # type: ignore[no-untyped-call]

@type_.setter
def type_(self, value: str) -> None:
"""Sets the parameter type."""
self._box.type = value

@property
def scope(self) -> str:
"""Returns the parameter scope."""
return str(self._box.get("scope", "")) # type: ignore[no-untyped-call]

@scope.setter
def scope(self, value: str) -> None:
"""Sets the parameter scope."""
self._box.scope = value

@property
def phase(self) -> str:
"""Returns the parameter phase."""
return str(self._box.get("phase", "")) # type: ignore[no-untyped-call]

@phase.setter
def phase(self, value: str) -> None:
"""Sets the parameter phase."""
self._box.phase = value

@property
def context(self) -> str:
"""Returns the parameter context."""
return str(self._box.get("context", "")) # type: ignore[no-untyped-call]

@context.setter
def context(self, value: str) -> None:
"""Sets the parameter context."""
self._box.context = value

@property
def options(self) -> dict[str, Any]:
"""Returns the parameter options."""
return self._box.get("options", {}) # type: ignore[no-any-return, no-untyped-call]

@options.setter
def options(self, value: dict[str, Any]) -> None:
"""Sets the parameter options."""
self._box.options = value

@property
def multiple(self) -> bool:
"""Returns whether the parameter allows multiple values."""
return bool(self._box.get("multiple", False)) # type: ignore[no-untyped-call]

@multiple.setter
def multiple(self, value: bool) -> None:
"""Sets whether the parameter allows multiple values."""
self._box.multiple = value

@property
def constraints(self) -> dict[str, Any]:
"""Returns the parameter constraints."""
return self._box.get("constraints", {}) # type: ignore[no-any-return, no-untyped-call]

@constraints.setter
def constraints(self, value: dict[str, Any]) -> None:
"""Sets the parameter constraints."""
self._box.constraints = value

@property
def group(self) -> dict[str, Any]:
"""Returns the parameter group."""
return self._box.get("group", {}) # type: ignore[no-any-return,no-untyped-call]

@group.setter
def group(self, value: dict[str, Any]) -> None:
"""Sets the parameter group."""
self._box.group = value

@property
def external_id(self) -> str:
"""Returns the parameter external ID."""
return str(self._box.get("external_id", "")) # type: ignore[no-untyped-call]

@external_id.setter
def external_id(self, value: str) -> None:
"""Sets the parameter external ID."""
self._box.external_id = value

@property
def status(self) -> str:
"""Returns the parameter status."""
return str(self._box.get("status", "")) # type: ignore[no-untyped-call]

@status.setter
def status(self, value: str) -> None:
"""Sets the parameter status."""
self._box.status = value


class ParametersServiceConfig:
"""Parameters service configuration."""
Expand Down
76 changes: 76 additions & 0 deletions tests/unit/resources/catalog/test_parameter_properties.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
from mpt_api_client.resources.catalog.products_parameters import Parameter


def test_parameter_properties_getters(): # noqa: WPS218
parameter_data = {
"id": "PAR-1",
"scope": "Order",
"phase": "Order",
"context": "Purchase",
"options": {"label": "Email"},
"multiple": True,
"constraints": {"required": True},
"group": {"id": "GRP-1"},
"externalId": "ext-1",
"status": "Active",
}

result = Parameter(parameter_data)

assert result.id == "PAR-1"
assert result.scope == "Order"
assert result.phase == "Order"
assert result.context == "Purchase"
assert result.options == {"label": "Email"}
assert result.multiple is True
assert result.constraints == {"required": True}
assert result.group == {"id": "GRP-1"}
assert result.external_id == "ext-1"
assert result.status == "Active"


def test_parameter_properties_setters(): # noqa: WPS218
parameter = Parameter()
parameter.scope = "Agreement"
parameter.phase = "Configuration"
parameter.context = "None"
parameter.type_ = "Text"
parameter.options = {"label": "Name"}
parameter.multiple = False
parameter.constraints = {"required": False}
parameter.group = {"id": "GRP-2"}
parameter.external_id = "ext-2"
parameter.status = "Inactive"

result = parameter

assert result.scope == "Agreement"
assert result.phase == "Configuration"
assert result.context == "None"
assert result.type_ == "Text"
assert result.options == {"label": "Name"}
assert result.multiple is False
assert result.constraints == {"required": False}
assert result.group == {"id": "GRP-2"}
assert result.external_id == "ext-2"
assert result.status == "Inactive"
result_dict = result.to_dict()
assert result_dict["scope"] == "Agreement"
assert result_dict["externalId"] == "ext-2"
assert result_dict["scope"] == "Agreement"
assert result_dict["externalId"] == "ext-2"
Comment on lines +58 to +61
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor

Remove duplicate assertions.

Lines 60-61 are exact duplicates of lines 58-59. This appears to be a copy-paste error.

🧹 Proposed fix
     result_dict = result.to_dict()
     assert result_dict["scope"] == "Agreement"
     assert result_dict["externalId"] == "ext-2"
-    assert result_dict["scope"] == "Agreement"
-    assert result_dict["externalId"] == "ext-2"
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
assert result_dict["scope"] == "Agreement"
assert result_dict["externalId"] == "ext-2"
assert result_dict["scope"] == "Agreement"
assert result_dict["externalId"] == "ext-2"
assert result_dict["scope"] == "Agreement"
assert result_dict["externalId"] == "ext-2"
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@tests/unit/resources/catalog/test_parameter_properties.py` around lines 58 -
61, The test contains duplicate assertions on result_dict ("scope" ==
"Agreement" and "externalId" == "ext-2") in test_parameter_properties.py; remove
the repeated assertions so each check for result_dict["scope"] and
result_dict["externalId"] appears only once, leaving a single pair of asserts
referencing result_dict to avoid redundant assertions.



def test_parameter_default_values(): # noqa: WPS218
result = Parameter()

assert not result.id
assert not result.scope
assert not result.phase
assert not result.context
assert result.options == {}
assert result.multiple is False
assert result.constraints == {}
assert result.group == {}
assert not result.external_id
assert not result.status