from collections import namedtuple from dataclasses import dataclass import nanoid Metadata = namedtuple("Metadata", ["table", "fields"]) @dataclass class Field: """ Represents a single field in a Record. """ name: str value_type: type = str default: value_type | None = None unique: bool = False class Record(dict): """ Base type for a single database record. """ _fields = [Field("uid", default="", unique=True)] def __init__(self, raw_doc: dict = {}, doc_id: int = None, **params): # populate the metadata fields = Record._fields if self.__class__ != Record: fields += self._fields self._metadata = Metadata(table=self.__class__.__name__, fields={f.name: f for f in fields}) self.doc_id = doc_id vals = dict({field.name: field.default for field in fields}, **raw_doc, **params) if not vals["uid"]: vals["uid"] = nanoid.generate(size=8) # 1% collision rate at ~2M records super().__init__(vals) def __setattr__(self, key, value): if key in self: self[key] = value super().__setattr__(key, value) def __getattr__(self, attr_name): if attr_name in self: return self.get(attr_name) return super().__getattr__(attr_name) def __repr__(self): return f"{self.__class__.__name__}[{self.doc_id}]: {self.items()}" class User(Record): _fields = [Field("name"), Field("email", unique=True)]