Skip to content

Commit

Permalink
Handle unknown attributes (#247)
Browse files Browse the repository at this point in the history
  • Loading branch information
felix-hilden committed Sep 9, 2021
1 parent 91470f7 commit 8144fef
Show file tree
Hide file tree
Showing 8 changed files with 61 additions and 5 deletions.
9 changes: 9 additions & 0 deletions docs/src/reference/models.rst
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,13 @@ See :class:`Serialisable` for more details on all available functionality.
album.asbuiltin()
album.json()
Responses will sometimes contain unknown attributes when the API changes.
They are parsed into the response model,
but are not included in serialisation and other model transforms.
An :class:`UnknownModelAttributeWarning` is issued
when encountering an unknown attribute.
Please consider upgrading Tekore if a newer version documents and handles it.

Models are made available in the :mod:`tekore.models` namespace.

Album
Expand Down Expand Up @@ -314,6 +321,7 @@ Model bases
Serialisable
Model
ModelList
UnknownModelAttributeWarning

Identifiable
Item
Expand All @@ -327,6 +335,7 @@ Functionality
.. autoclass:: Serialisable
.. autoclass:: Model
.. autoclass:: ModelList
.. autoclass:: UnknownModelAttributeWarning

Models
******
Expand Down
3 changes: 3 additions & 0 deletions docs/src/release_notes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,9 @@ Changed
Added
*****
- Improved documentation for type hints and response models (:issue:`109`)
- Responses can now parse unknown attributes, greatly improving backwards
compatibility. :class:`UnknownModelAttributeWarning
<tekore.model.UnknownModelAttributeWarning>` was introduced (:issue:`247`)

3.7.1 (2021-05-04)
------------------
Expand Down
10 changes: 5 additions & 5 deletions tekore/_client/process.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Callable
from tekore.model import ModelList
from tekore.model import ModelList, Model


def nothing(json):
Expand All @@ -14,23 +14,23 @@ def post_func(json: dict):
return post_func


def single(type_: type, from_item: str = None) -> Callable:
def single(type_: Model, from_item: str = None) -> Callable:
"""
Unpack dict or items in ``from_item`` into single constructor.
If dict or ``from_item`` is None - does nothing and returns None.
"""
def post_func(json: dict):
json = json if from_item is None else json[from_item]
return type_(**json) if json is not None else None
return type_.from_kwargs(json) if json is not None else None
return post_func


def model_list(type_: type, from_item: str = None) -> Callable:
def model_list(type_: Model, from_item: str = None) -> Callable:
"""Unpack items inside ``from_item`` of dict into constructors."""
def post_func(json: dict):
json = json if from_item is None else json[from_item]
return ModelList(type_(**i) if i is not None else None for i in json)
return ModelList(type_.from_kwargs(i) if i is not None else None for i in json)
return post_func


Expand Down
1 change: 1 addition & 0 deletions tekore/_model/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -99,4 +99,5 @@
Serialisable,
StrEnum,
Timestamp,
UnknownModelAttributeWarning,
)
30 changes: 30 additions & 0 deletions tekore/_model/serialise.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
from enum import Enum
from typing import Union, TypeVar, List
from pprint import pprint
from warnings import warn
from datetime import datetime
from dataclasses import dataclass, asdict, fields

Expand Down Expand Up @@ -168,6 +169,10 @@ def pprint(
pprint(self.asbuiltin(), depth=depth, compact=compact, **pprint_kwargs)


class UnknownModelAttributeWarning(RuntimeWarning):
"""The response model contains an unknown attribute."""


@dataclass(repr=False)
class Model(Serialisable):
"""Dataclass that provides a readable ``repr`` of its fields."""
Expand All @@ -184,6 +189,31 @@ def __repr__(self):

return '\n'.join(lines)

@classmethod
def from_kwargs(cls, kwargs):
"""Create the Model and patch unknown kwargs in."""
# Adapted from Stack Overflow: https://stackoverflow.com/a/55101438/7089239
cls_fields = {field.name for field in fields(cls)}

# split into known and unknown kwargs
known_kwargs, unknown_kwargs = {}, {}
for name, val in kwargs.items():
if name in cls_fields:
known_kwargs[name] = val
else:
unknown_kwargs[name] = val

model = cls(**known_kwargs)

for name, val in unknown_kwargs.items():
setattr(model, name, val)
msg = (
f'\nResponse contains unknown attribute: `{name}`\n'
'This warning may be safely ignored. Please consider upgrading Tekore.'
)
warn(msg, UnknownModelAttributeWarning, stacklevel=5)
return model


T = TypeVar('T')

Expand Down
2 changes: 2 additions & 0 deletions tekore/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -85,6 +85,7 @@
SavedTrackPaging,
FullTrackPaging,
ExplicitContent,
UnknownModelAttributeWarning,
User,
PrivateUser,
PublicUser,
Expand Down Expand Up @@ -182,6 +183,7 @@
SavedTrackPaging,
FullTrackPaging,
ExplicitContent,
UnknownModelAttributeWarning,
User,
PrivateUser,
PublicUser,
Expand Down
10 changes: 10 additions & 0 deletions tests/model.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
StrEnum,
ModelList,
Timestamp,
UnknownModelAttributeWarning,
)


Expand Down Expand Up @@ -188,6 +189,15 @@ class C(Model):
assert c.json() == '{"v": "2019-01-01T12:00:00Z"}'


class TestModel:
def test_unknown_attribute_passed(self):
with pytest.warns(UnknownModelAttributeWarning):
data = Data.from_kwargs({'i': 1, 'u': 2})

assert data.u == 2
assert 'u' not in data.json()


class TestModelList:
def test_list_of_dataclasses_serialised(self):
list_in = [{'i': 1}, {'i': 2}]
Expand Down
1 change: 1 addition & 0 deletions tox.ini
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
envlist = flake8,doc8,pydocstyle,coverage,docs

[flake8]
max-line-length = 80
select = C,E,F,W,B,B9
ignore = B305,E402,E501,E722,F401

Expand Down

0 comments on commit 8144fef

Please sign in to comment.