Models#

APIModel is inspired by pydantic and based on annotations. If you have used pydantic before this library should be seem familiar to you.

Models are defined by inheriting from apimodel.APIModel.

Basic usage#

We define a model with two fields. An integer id and an optional string name.

import apimodel

class User(apimodel.APIModel):
    id: int
    name: str = "Anonymous"

user = User(id="123")
print(user)
# User(id=123, name='Anonymous')

user.id has been casted to an integer and user.name has the default value.

print(user.as_dict())
# {'id': 123, 'name': 'Anonymous'}

Model Nesting#

Models can be nested

class Item(apimodel.APIModel):
    name: str

class Storage(apimodel.APIModel):
    max_size: int = 256
    items: list[Item]

class User(apimodel.APIModel):
    id: int
    storage: Storage


user = User(id="123", storage={"items": [{"name": "foo"}, {"name": "bar"}]})
print(user)
# User(id=123, storage=Storage(items=[Item(name='foo'), Item(name='bar')], max_size=256))

print(user.storage.items[0].name)
# foo

Warning

list[object] is available only in python 3.9+

Please use typing.Sequence on lower version.

Fields#

Metadata can be added to fields through apimodel.fields.FieldInfo at runtime.

The first positional argument is always the default value. ... denotes a required value.

See apimodel.FieldInfo for details.

import datetime
import apimodel

class Item(apimodel.APIModel):
    id: int = apimodel.Field(..., private=True)
    created_at: datetime.datetime = apimodel.Field(..., name="createdAt")
    available: bool = apimodel.Field(False)


item = Item({"id": "42", "name": "foo", "createdAt": 1577836800})
print(item.as_dict())
# {'created_at': datetime.datetime(2020, 1, 1, 0, 0, tzinfo=datetime.timezone.utc), 'available': False}
print(item.id)
# 42

Extras#

Some values may require to be shared across all nested models. For example your API client. This can be done with inheritance and apimodel.Extra.

Extra values do not appear in model.as_dict()

class Client:
    def __init__(self, token: str) -> None:
        self.token = token

    def fetch_user(self, id: int) -> User:
        data = self._request_endpoint("GET", f"/users/{id}")
        return User(data, client=self)

    def edit_user(self, user_id: int, **kwargs: object) -> None:
        ...

    def buy_item(self, item_id: int) -> None:
        ...

    ...

class BaseModel(apimodel.APIModel):
    # underscore gets stripped at runtime
    _client: Client = apimodel.Extra()

class Item(BaseModel):
    id: int
    name: str

    def buy(self) -> None:
        self.client.buy_item(self.id)

class User(BaseModel):
    id: int
    name: str
    items: list[Item]

    def edit(self, **kwargs: object) -> None:
        self.client.edit_user(self.id, **kwargs)

client = Client("token")

user = client.fetch_user(42)
print(user)
# User(id=1, name='John', items=[Item(id=42, name='foo'), Item(id=43, name='bar')])

user.edit(name="John Doe")
user.items[1].buy()

Debugging#

APIModel can be used with python-devtools developed by the author of pydantic.