Skip to content

Commit aabb371

Browse files
committed
MPT-18701 Improve base model tests
1 parent 1602b14 commit aabb371

File tree

4 files changed

+130
-24
lines changed

4 files changed

+130
-24
lines changed

mpt_api_client/models/model.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import re
22
from collections import UserList
33
from collections.abc import Iterable
4-
from typing import Any, ClassVar, Self, get_args, get_origin, override
4+
from typing import Any, Self, get_args, get_origin, override
55

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

172-
_data_key: ClassVar[str | None] = None
173172
id: str
174173

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

193192
@classmethod
194193
def from_response(cls, response: Response) -> Self:
195-
"""Creates a collection from a response.
194+
"""Creates a Model from a response.
196195
197196
Args:
198197
response: The httpx response object.
199198
"""
200199
response_data = response.json()
201200
if isinstance(response_data, dict):
202201
response_data.pop("$meta", None)
203-
if cls._data_key:
204-
response_data = response_data.get(cls._data_key)
205202
if not isinstance(response_data, dict):
206203
raise TypeError("Response data must be a dict.")
207204
meta = Meta.from_response(response)

tests/unit/conftest.py

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,6 @@
1010
class DummyModel(Model):
1111
"""Dummy resource for testing."""
1212

13-
_data_key = None
14-
1513

1614
@pytest.fixture
1715
def http_client():

tests/unit/models/resource/test_resource_custom_key.py

Lines changed: 0 additions & 17 deletions
This file was deleted.

tests/unit/models/resource/test_resource.py renamed to tests/unit/models/test_model.py

Lines changed: 128 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from httpx import Response
33

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

67

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

2223

24+
class TypedListItemDummy(BaseModel):
25+
"""Dummy item for typed list tests."""
26+
27+
name: str
28+
29+
30+
class TypedListContainerDummy(BaseModel):
31+
"""Dummy container with a typed list field."""
32+
33+
entries: list[TypedListItemDummy]
34+
35+
36+
class DictTypedContainerDummy(BaseModel):
37+
"""Dummy container with a dict-typed field."""
38+
39+
metadata: dict[str, str]
40+
41+
42+
class ScalarListContainerDummy(BaseModel):
43+
"""Dummy container with a list[str] field."""
44+
45+
tags: list[str]
46+
47+
2348
@pytest.fixture
2449
def meta_data():
2550
return {"pagination": {"limit": 10, "offset": 20, "total": 100}, "ignored": ["one"]} # noqa: WPS226
@@ -200,3 +225,106 @@ def test_advanced_mapping():
200225
assert agreement.parameters.ordering[0].external_id == "contact"
201226
assert agreement.parameters.ordering[1].external_id == "address"
202227
assert agreement.to_dict() == agreement_data
228+
229+
230+
def test_to_snake_case_already_snake():
231+
result = to_snake_case("already_snake") # act
232+
233+
assert result == "already_snake"
234+
235+
236+
def test_model_list_extend():
237+
ml = ModelList([{"id": "1"}])
238+
239+
ml.extend([{"id": "2"}, {"id": "3"}]) # act
240+
241+
assert [ml_item.id for ml_item in ml] == ["1", "2", "3"]
242+
243+
244+
def test_model_list_insert():
245+
ml = ModelList([{"id": "1"}, {"id": "3"}])
246+
247+
ml.insert(1, {"id": "2"}) # act
248+
249+
assert len(ml) == 3
250+
assert ml[0].id == "1"
251+
assert ml[1].id == "2"
252+
assert ml[2].id == "3"
253+
254+
255+
def test_model_list_process_item_nested_list():
256+
nested = [{"id": "a"}, {"id": "b"}]
257+
258+
ml = ModelList([nested]) # act
259+
260+
assert isinstance(ml[0], ModelList)
261+
assert ml[0][0].id == "a"
262+
263+
264+
def test_model_list_process_item_scalar():
265+
ml = ModelList(["a", "b", "c"]) # act
266+
267+
assert ml[0] == "a"
268+
assert ml[1] == "b"
269+
assert ml[2] == "c"
270+
271+
272+
def test_base_model_getattr_from_dict():
273+
model = BaseModel(foo="bar")
274+
275+
result = model.__getattr__("foo") # noqa: PLC2801
276+
277+
assert result == "bar"
278+
279+
280+
def test_base_model_setattr_private():
281+
model = BaseModel(foo="bar")
282+
283+
model._private = "secret" # noqa: SLF001 # act
284+
285+
assert model._private == "secret" # noqa: SLF001
286+
287+
288+
def test_to_dict_excludes_private_attrs():
289+
model = BaseModel(foo="bar")
290+
model._private = "secret" # noqa: SLF001
291+
292+
result = model.to_dict()
293+
294+
assert result == {"foo": "bar"}
295+
assert "_private" not in result
296+
297+
298+
def test_process_value_typed_list():
299+
container = TypedListContainerDummy(entries=[{"name": "one"}, {"name": "two"}]) # act
300+
301+
assert isinstance(container.entries[0], TypedListItemDummy)
302+
assert container.entries[0].name == "one"
303+
assert isinstance(container.entries[1], TypedListItemDummy)
304+
assert container.entries[1].name == "two"
305+
306+
307+
def test_process_value_existing_base_model():
308+
nested = BaseModel(value="test")
309+
model = BaseModel()
310+
311+
model.nested = nested # act
312+
313+
assert model.nested is nested
314+
315+
316+
def test_process_value_non_list_target():
317+
container = DictTypedContainerDummy()
318+
319+
container.metadata = [{"id": "1"}] # act
320+
321+
assert isinstance(container.metadata, ModelList)
322+
assert container.metadata[0].id == "1"
323+
324+
325+
def test_process_value_scalar_list_elements():
326+
container = ScalarListContainerDummy(tags=["a", "b", "c"]) # act
327+
328+
assert isinstance(container.tags, ModelList)
329+
assert container.tags[0] == "a"
330+
assert container.tags[1] == "b"

0 commit comments

Comments
 (0)