Skip to content
Merged
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
7 changes: 2 additions & 5 deletions mpt_api_client/models/model.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import re
from collections import UserList
from collections.abc import Iterable
from typing import Any, ClassVar, Self, get_args, get_origin, override
from typing import Any, Self, get_args, get_origin, override

from mpt_api_client.http.types import Response
from mpt_api_client.models.meta import Meta
Expand Down Expand Up @@ -169,7 +169,6 @@ def _process_value(self, value: Any, target_class: Any = None) -> Any: # noqa:
class Model(BaseModel):
"""Provides a resource to interact with api data using fluent interfaces."""

_data_key: ClassVar[str | None] = None
id: str

def __init__(
Expand All @@ -192,16 +191,14 @@ def new(cls, resource_data: ResourceData | None = None, meta: Meta | None = None

@classmethod
def from_response(cls, response: Response) -> Self:
"""Creates a collection from a response.
"""Creates a Model from a response.

Args:
response: The httpx response object.
"""
response_data = response.json()
if isinstance(response_data, dict):
response_data.pop("$meta", None)
if cls._data_key:
response_data = response_data.get(cls._data_key)
if not isinstance(response_data, dict):
raise TypeError("Response data must be a dict.")
meta = Meta.from_response(response)
Expand Down
2 changes: 0 additions & 2 deletions tests/unit/conftest.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,6 @@
class DummyModel(Model):
"""Dummy resource for testing."""

_data_key = None


@pytest.fixture
def http_client():
Expand Down
17 changes: 0 additions & 17 deletions tests/unit/models/resource/test_resource_custom_key.py

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
from httpx import Response

from mpt_api_client.models import Meta, Model
from mpt_api_client.models.model import BaseModel, ModelList, to_snake_case # noqa: WPS347


class AgreementDummy(Model): # noqa: WPS431
Expand All @@ -20,6 +21,30 @@ class AgreementWithContactDummy(Model):
contact: ContactDummy


class TypedListItemDummy(BaseModel):
"""Dummy item for typed list tests."""

name: str


class TypedListContainerDummy(BaseModel):
"""Dummy container with a typed list field."""

entries: list[TypedListItemDummy]


class DictTypedContainerDummy(BaseModel):
"""Dummy container with a dict-typed field."""

metadata: dict[str, str]


class ScalarListContainerDummy(BaseModel):
"""Dummy container with a list[str] field."""

tags: list[str]


@pytest.fixture
def meta_data():
return {"pagination": {"limit": 10, "offset": 20, "total": 100}, "ignored": ["one"]} # noqa: WPS226
Expand Down Expand Up @@ -156,9 +181,11 @@ def test_append():
agreement.parameters.ordering.append(new_param) # act

assert agreement.id == "AGR-123"
assert agreement.parameters.ordering[0].external_id == "contact"
assert agreement.parameters.ordering[1].external_id == "address"
assert agreement.parameters.ordering[2].external_id == "email"
assert [agr_param.external_id for agr_param in agreement.parameters.ordering] == [
"contact",
"address",
"email",
]
agreement_data["parameters"]["ordering"].append(new_param)
assert agreement.to_dict() == agreement_data

Expand All @@ -177,8 +204,10 @@ def test_overwrite_list():
agreement.parameters.ordering = ordering_parameters # act

assert agreement.id == "AGR-123"
assert agreement.parameters.ordering[0].external_id == "contact"
assert agreement.parameters.ordering[1].external_id == "address"
assert [agr_param.external_id for agr_param in agreement.parameters.ordering] == [
"contact",
"address",
]
assert agreement.to_dict() == agreement_data


Expand All @@ -197,6 +226,103 @@ def test_advanced_mapping():
agreement.parameters.ordering = ordering_parameters # act

assert isinstance(agreement.contact, ContactDummy)
assert agreement.parameters.ordering[0].external_id == "contact"
assert agreement.parameters.ordering[1].external_id == "address"
assert [agr_param.external_id for agr_param in agreement.parameters.ordering] == [
"contact",
"address",
]
assert agreement.to_dict() == agreement_data


def test_to_snake_case_already_snake():
result = to_snake_case("already_snake") # act

assert result == "already_snake"


def test_model_list_extend():
ml = ModelList([{"id": "1"}])

ml.extend([{"id": "2"}, {"id": "3"}]) # act

assert [ml_item.id for ml_item in ml] == ["1", "2", "3"]


def test_model_list_insert():
ml = ModelList([{"id": "1"}, {"id": "3"}])

ml.insert(1, {"id": "2"}) # act

assert [ml_item.id for ml_item in ml] == ["1", "2", "3"]


def test_model_list_process_item_nested_list():
nested = [{"id": "a"}, {"id": "b"}]

ml = ModelList([nested]) # act

assert isinstance(ml[0], ModelList)
assert ml[0][0].id == "a"


def test_model_list_process_item_scalar():
ml = ModelList(["a", "b", "c"]) # act

assert ml == ["a", "b", "c"]


def test_base_model_getattr_from_dict():
model = BaseModel(foo="bar")

result = model.__getattr__("foo") # noqa: PLC2801

assert result == "bar"


def test_base_model_setattr_private():
model = BaseModel(foo="bar")

model._private = "secret" # noqa: SLF001 # act

assert model._private == "secret" # noqa: SLF001


def test_to_dict_excludes_private_attrs():
model = BaseModel(foo="bar")
model._private = "secret" # noqa: SLF001

result = model.to_dict()

assert result == {"foo": "bar"}
assert "_private" not in result


def test_process_value_typed_list():
container = TypedListContainerDummy(entries=[{"name": "one"}, {"name": "two"}]) # act

assert all(isinstance(entry, TypedListItemDummy) for entry in container.entries)
assert [entry.name for entry in container.entries] == ["one", "two"]


def test_process_value_existing_base_model():
nested = BaseModel(value="test")
model = BaseModel()

model.nested = nested # act

assert model.nested is nested


def test_process_value_non_list_target():
container = DictTypedContainerDummy()

container.metadata = [{"id": "1"}] # act

assert isinstance(container.metadata, ModelList)
assert container.metadata[0].id == "1"


def test_process_value_scalar_list_elements():
container = ScalarListContainerDummy(tags=["a", "b", "c"]) # act

assert isinstance(container.tags, ModelList)
assert list(container.tags) == ["a", "b", "c"]
Loading