Skip to content

DM-Modelle#

Modul: restalchemy.dm.models

Dieses Modul definiert Basisklassen und Mixins für Datenmodelle (DM) in RESTAlchemy.


MetaModel und Model#

MetaModel#

MetaModel ist die Metaklasse aller DM-Modelle. Sie:

  • Sammelt Felddefinitionen, die mit properties.property() und properties.container() erstellt wurden.
  • Führt Properties aus Basisklassen zusammen.
  • Verfolgt ID-Properties in id_properties.
  • Hängt pro Modellklasse ein operatives Storage __operational_storage__ an.

Normalerweise benutzen Sie MetaModel nicht direkt, sondern erben von Model oder dessen Subklassen.

Model#

Model ist die grundlegende Basisklasse für DM-Modelle:

from restalchemy.dm import models, properties, types


class Foo(models.Model):
    foo_id = properties.property(types.Integer(), id_property=True, required=True)
    name = properties.property(types.String(max_length=255), default="")

Wichtiges Verhalten:

  • Der Konstruktor nimmt Keyword-Argumente entgegen und gibt sie an pour() weiter.
  • pour() baut einen PropertyManager aus der properties-Collection und validiert die Felder.
  • Attributzugriffe werden auf Properties gemappt:
  • model.field liest properties[field].value.
  • model.field = value setzt den Wert mit Validierung.
  • as_plain_dict() gibt eine einfache Dictionary-Repräsentation zurück.
  • Das Modell verhält sich wie ein Mapping über seine Properties (__getitem__, __iter__, __len__).

Fehlerbehandlung:

  • Falscher Typ → ModelTypeError.
  • Fehlendes Pflichtfeld → PropertyRequired.
  • Änderung eines read-only oder ID-Feldes → ReadOnlyProperty.

Eigene Validierung:

class PositiveFoo(models.Model):
    value = properties.property(types.Integer(), required=True)

    def validate(self):
        if self.value <= 0:
            raise ValueError("value must be positive")

validate() wird aus pour() heraus aufgerufen.


ID-Verarbeitung#

ModelWithID#

ModelWithID erweitert Model für Modelle mit genau einem ID-Property:

  • get_id() gibt den aktuellen Wert des ID-Feldes zurück.
  • Gleichheit und Hashing basieren auf get_id().

Wenn es kein oder mehrere ID-Felder gibt, wirft get_id_property() einen TypeError, und Sie sollten die ID-Logik selbst implementieren.

ModelWithUUID und ModelWithRequiredUUID#

ModelWithUUID definiert eine UUID als Primärschlüssel:

class ModelWithUUID(ModelWithID):
    uuid = properties.property(
        types.UUID(),
        read_only=True,
        id_property=True,
        default=lambda: uuid.uuid4(),
    )

Beispiel:

class Foo(models.ModelWithUUID):
    value = properties.property(types.Integer(), required=True)

foo = Foo(value=10)
print(foo.uuid)       # automatisch generierte UUID
print(foo.get_id())   # identisch zu foo.uuid

ModelWithRequiredUUID ist ähnlich, verlangt aber eine explizit gesetzte UUID ohne Default.


Operational Storage#

DmOperationalStorage#

Ein kleines Hilfsobjekt, das von MetaModel als __operational_storage__ verwendet wird:

  • store(name, data) — speichert beliebige Daten unter einem Namen.
  • get(name) — liest Daten oder wirft NotFoundOperationalStorageError.

Beispiel:

from restalchemy.dm import models


class Foo(models.ModelWithUUID):
    pass

Foo.__operational_storage__.store("table_name", "foos")

assert Foo.__operational_storage__.get("table_name") == "foos"

Häufige Mixins#

ModelWithTimestamp#

Fügt created_at und updated_at Felder mit UTC-Zeit hinzu:

  • Beide Felder sind Pflichtfelder, read-only und verwenden types.UTCDateTimeZ().
  • update() aktualisiert updated_at automatisch, wenn das Modell "dirty" ist (oder force=True).
class TimestampedFoo(models.ModelWithUUID, models.ModelWithTimestamp):
    value = properties.property(types.Integer(), required=True)

ModelWithProject#

Fügt ein Pflichtfeld project_id vom Typ types.UUID() hinzu:

class ProjectResource(models.ModelWithUUID, models.ModelWithProject):
    name = properties.property(types.String(max_length=255), required=True)

ModelWithTags#

Fügt ein Feld tags hinzu — eine standardmäßig leere Liste von Zeichenketten —, um Zeilen mit Werten zu versehen, nach denen eine Abfrage suchen kann:

class TaggedResource(models.ModelWithUUID, models.ModelWithTags):
    name = properties.property(types.String(max_length=255), required=True)

Die Spalte ist ein PostgreSQL-Array, und die Suche darin braucht einen GIN-Index; die Migration, die die Tabelle anlegt, deklariert beides:

tags TEXT[] NOT NULL DEFAULT '{}';
CREATE INDEX idx_tagged_tags ON tagged USING GIN (tags);

Gesucht wird aus Python mit ContainsAll / ContainsAny oder über HTTP mit einem Filterausdruck (?q=tags:"env:prod") — siehe Sammlungen über HTTP filtern.

ModelWithNameDesc und ModelWithRequiredNameDesc#

Gemeinsame Felder name und description:

  • ModelWithNameDesc:
  • name: String bis 255 Zeichen, Default "".
  • description: String bis 255 Zeichen, Default "".
  • ModelWithRequiredNameDesc:
  • name ist Pflichtfeld.

Custom Properties und Simple Views#

CustomPropertiesMixin#

Erlaubt zusätzliche "Custom Properties" mit eigenen Typen:

  • __custom_properties__: Mapping Name → Typ (types.BaseType).
  • get_custom_properties() liefert (name, type) Paare.
  • get_custom_property_type(name) gibt den Typ eines Custom-Feldes zurück.
  • _check_custom_property_value() validiert Werte und kann statische Werte erzwingen.

DumpToSimpleViewMixin#

dump_to_simple_view() konvertiert ein Modell in eine Struktur aus einfachen Typen (für JSON, OpenAPI, Storage):

result = model.dump_to_simple_view(
    skip=["internal_field"],
    save_uuid=True,
    custom_properties=False,
)
  • Iteriert über self.properties und nutzt to_simple_type() des jeweiligen Typs.
  • Bei save_uuid=True werden UUID-Felder als Strings ausgegeben.
  • Optional werden Custom-Properties konvertiert.

RestoreFromSimpleViewMixin#

restore_from_simple_view() baut ein Modell aus einer "Simple View":

user = User.restore_from_simple_view(
    skip_unknown_fields=True,
    name="Alice",
    created_at="2006-01-02T15:04:05.000576Z",
)

Verhalten:

  • Normalisiert Feldnamen (-_).
  • Kann unbekannte Felder ignorieren.
  • Nutzt from_simple_type() / from_unicode() der Typen.

SimpleViewMixin#

Kombiniert beide Mixins:

class User(models.ModelWithUUID, models.SimpleViewMixin):
    name = properties.property(types.String(max_length=255), required=True)

Round-trip Beispiel:

plain = user.dump_to_simple_view()
user2 = User.restore_from_simple_view(**plain)

Zusammenfassung#

  • Verwenden Sie Model oder seine Helferklassen als Basis für alle DM-Modelle.
  • Nutzen Sie Mixins wie ModelWithUUID, ModelWithTimestamp, ModelWithProject, ModelWithNameDesc, um wiederkehrende Muster abzubilden.
  • Verwenden Sie Simple-View-Mixins für die Konvertierung von Modellen in einfache Strukturen und zurück.