Advanced topics

Configuring model key

By default, the primary key of a model inheriting from hrefs.BaseReferrableModel is called id. This can be changed by annotating another field with hrefs.PrimaryKey.

from typing import Annotated
from hrefs import Href, BaseReferrableModel, PrimaryKey
from pydantic import parse_obj_as

class MyModel(BaseReferrableModel):
    my_id: Annotated[int, PrimaryKey]

    class Config:
        details_view = "get_my_model"

@app.get("/my_models/{my_id}")
def get_my_model(my_id: int) -> MyModel:
    ...

Then MyModel.my_id is the key used by Href[MyModel].

>>> model = MyModel(my_id=1)
>>> model.get_key()
1
>>> parse_obj_as(Href[MyModel], model)
Href(key=1, url=AnyHttpUrl(...'http://example.com/my_models/1'...))

Note

Before Python 3.8, typing_extensions.Annotated can be used to annotate the fields.

Composite keys

It is also possible to annotate multiple fields with PrimaryKey. When using composite keys with FastAPI or Starlette models, each part of the key must appear in the route template.

from typing import Annotated
from hrefs import Href, BaseReferrableModel, PrimaryKey
from pydantic import parse_obj_as

class Page(BaseReferrableModel):
    book_id: Annotated[int, PrimaryKey]
    page_number: Annotated[int, PrimaryKey]

    class Config:
        details_view = "get_page"

@app.get("/books/{book_id}/pages/{page_number}")
def get_page(book_id: int, page_number: int) -> Page:
    ...

The primary key of the model will be a named tuple of the annotated parts.

>>> page = Page(book_id=1, page_number=123)
>>> page.get_key()
key(book_id=1, page_number=123)
>>> parse_obj_as(Href[Page], page)
Href(key=key(book_id=1, page_number=123), url=AnyHttpUrl(...'http://example.com/books/1/pages/123'...))

Inheritance

It is possible for a referrable model to inherit another:

from hrefs import Href, BaseReferrableModel
from pydantic import parse_obj_as

class Book(BaseReferrableModel):
    id: int
    title: str

class Textbook(Book):
    subject: str

    class Config:
        details_view = "get_textbook"

@app.get("/textbooks/{id}")
def get_textbook(id: int) -> Textbook:
    ...

The derived model Textbook inherits the key id and details view "get_book" from its parent Book.

>>> textbook = Textbook(id=1, title="Introduction to hrefs", subject="hrefs")
>>> textbook.get_key()
1
>>> parse_obj_as(Href[Textbook], textbook)
Href(key=1, url=AnyHttpUrl(...'http://example.com/textbooks/1'...))

Primary key annotations are not composable across inheritance. it is not possible to define a part of the model key in the parent and another part in the derived model. Model key definitions — whether implicit or explicit — should only exist in one class of the inheritance tree.