Skip to content

Commit f09ce60

Browse files
Merge pull request #31 from hostnfly/feat/deserializer-union-types
feat: allow Union typed schemas
2 parents 9ddb9cf + dc0f116 commit f09ce60

File tree

3 files changed

+79
-5
lines changed

3 files changed

+79
-5
lines changed

jsonapi_client/client.py

Lines changed: 17 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
1-
from typing import Any, Generic, TypeVar, cast
1+
from types import UnionType
2+
from typing import Any, Generic, TypeVar, cast, get_args
23

34
import jsonpickle # type: ignore[import-untyped]
45
from requests import request # type: ignore[import-untyped]
@@ -38,7 +39,7 @@ def __init__(self, status_code: int, jsonapi_errors: list[JsonAPIError]) -> None
3839

3940

4041
class JsonAPIClient(Generic[T]):
41-
def __init__(self, url: str, schema: type[JsonAPIResourceSchema], auth: AuthBase | None = None) -> None:
42+
def __init__(self, url: str, schema: type[JsonAPIResourceSchema] | UnionType, auth: AuthBase | None = None) -> None:
4243
self.url = url
4344
self.schema = schema
4445
self.auth = auth
@@ -79,8 +80,19 @@ def __deserialize_payload(self, response: Response) -> tuple[T | list[T], dict[s
7980
parsed = JsonAPIParser().parse(**json)
8081
meta = cast("dict[str, Any]", json["meta"])
8182
if isinstance(parsed, list):
82-
results = [cast("T", cast("Any", self.schema).from_dict(r)) for r in parsed]
83+
results = [self.__deserializer_resource(r) for r in parsed]
8384
return results, meta
8485

85-
result = cast("T", cast("Any", self.schema).from_dict(parsed))
86-
return result, meta
86+
return self.__deserializer_resource(parsed), meta
87+
88+
def __deserializer_resource(self, jsonapi_resource: dict[str, Any]) -> T:
89+
if isinstance(self.schema, UnionType):
90+
for schema_type in get_args(self.schema):
91+
try:
92+
return cast("T", cast("Any", schema_type).from_dict(jsonapi_resource))
93+
except KeyError as e:
94+
last_error = e
95+
pass
96+
raise last_error
97+
98+
return cast("T", cast("Any", self.schema).from_dict(jsonapi_resource))

tests/fixtures/media.json

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
{
2+
"meta": {
3+
"total": 2
4+
},
5+
"data": [
6+
{
7+
"id": "1",
8+
"type": "media",
9+
"attributes": {
10+
"title": "Das weiße Band - Eine deutsche Kindergeschichte",
11+
"year": 2009
12+
},
13+
"relationships": {
14+
"director": {
15+
"data": {
16+
"id": "1",
17+
"type": "person"
18+
}
19+
}
20+
}
21+
},
22+
{
23+
"id": "2",
24+
"type": "media",
25+
"attributes": {
26+
"title": "Good Morning, Veronica",
27+
"seasons": 3
28+
}
29+
}
30+
],
31+
"included": [
32+
{
33+
"id": "1",
34+
"type": "person",
35+
"attributes": {
36+
"full_name": "Michael Haneke"
37+
}
38+
}
39+
]
40+
}

tests/test_client.py

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,12 @@ class Movie(JsonAPIResourceSchema):
2525
director: Person
2626

2727

28+
@dataclass
29+
class Series(JsonAPIResourceSchema):
30+
title: str
31+
seasons: int
32+
33+
2834
class TestClient(TestCase):
2935
@patch("jsonapi_client.client.request")
3036
def test_get(self, test_request: MagicMock) -> None:
@@ -46,3 +52,19 @@ def test_get(self, test_request: MagicMock) -> None:
4652
self.assertEqual(result[1].year, 1997)
4753
self.assertEqual(result[1].director.full_name, "Michael Haneke")
4854

55+
@patch("jsonapi_client.client.request")
56+
def test_get_polymorphic(self, test_request: MagicMock) -> None:
57+
client: JsonAPIClient = JsonAPIClient(url="http://example.com/api", schema=Movie | Series, auth=None)
58+
fixture = Path("tests/fixtures/media.json")
59+
response = Response()
60+
response.status_code = 200
61+
response._content = fixture.read_bytes()
62+
test_request.return_value = response
63+
64+
result, meta = client.get()
65+
66+
self.assertEqual(len(result), 2)
67+
self.assertEqual(meta["total"], 2)
68+
self.assertIsInstance(result[0], Movie)
69+
self.assertIsInstance(result[1], Series)
70+

0 commit comments

Comments
 (0)