-
-
Notifications
You must be signed in to change notification settings - Fork 1
Description
Hi,
I am having trouble creating a Model with a non-automatic primary key using the PrimaryKey type annotation using Python 3.11.
E.g. for this model I want to make the 'file_identifier' column the primary key but the automatic 'id' column/logic is used instead:
from sqlorm import Model, PrimaryKey
class CachedRecord(Model):
table = "record"
file_identifier: PrimaryKey[str]
file_revision: str
sha1: strTracking that logic back, the mapper primary_key property returns None.
I can trace that back to the process_mapped_attributes() method which fails on the 'if annotated type' check (https://github.com/hyperflask/sqlorm/blob/main/src/sqlorm/model.py#L45) because .__class__ equates as a string (both literally and as a type), rather than an annotated type.
Asking AI it's said this is due to a change in Python 3.11+:
You’re hitting Python 3.11’s postponed evaluation of annotations: many annotations are stored as strings, and typing.Annotated no longer appears as t._AnnotatedAlias.
Fix by resolving annotations with typing.get_type_hints(..., include_extras=True) and using typing.get_origin/get_args to detect Annotated and Optional.
I don't fully understand what that's saying but I was able to workaround this problem with a modified version of its suggested code [1].
I imagine that code isn't suitable to use as-is though or if there's a better way to solve the problem. I may very well have done something wrong in my usage.
Apart from this, I've found this library really easy to use, and really closely matches what I've wanted as a database layer, so thanks for realising this.
# extra imports
import sys
from typing import get_type_hints, get_origin, get_args
class ModelMetaclass(abc.ABCMeta):
# ...
@staticmethod
def process_mapped_attributes(dct):
module_name = dct.get("__module__", None)
globalns = sys.modules[module_name].__dict__ if module_name and module_name in sys.modules else {}
raw_annotations = dct.get("__annotations__", {}) or {}
try:
annotations = get_type_hints(
type("Tmp", (), {"__annotations__": raw_annotations}),
globalns=globalns,
localns=None,
include_extras=True,
)
except Exception:
annotations = raw_annotations
mapped_attrs = {}
for name, annotation in annotations.items():
primary_key = False
nullable = None
if get_origin(annotation) is t.Annotated:
ann_args = get_args(annotation)
base = ann_args[0] if ann_args else annotation
meta = ann_args[1:] if len(ann_args) > 1 else ()
primary_key = PrimaryKeyColumn in meta
annotation = base
# ...