From 8d9667b4c83bd3a740e852c2c7cfa4ae766efc8b Mon Sep 17 00:00:00 2001 From: dnwpark Date: Mon, 29 Sep 2025 18:55:44 -0700 Subject: [PATCH 01/10] Add ParametricTypeName to support names of types like arrays and tuples. --- gel/_internal/_edgeql/_schema.py | 22 ++++++----- gel/_internal/_qb/_abstract.py | 10 ++--- gel/_internal/_qb/_expressions.py | 38 +++++++++---------- gel/_internal/_qb/_reflection.py | 4 +- gel/_internal/_qbmodel/_abstract/_globals.py | 2 + .../_qbmodel/_abstract/_primitive.py | 6 +-- gel/_internal/_reflection/_types.py | 10 ++--- gel/_internal/_schemapath.py | 34 +++++++++++++++++ 8 files changed, 82 insertions(+), 44 deletions(-) diff --git a/gel/_internal/_edgeql/_schema.py b/gel/_internal/_edgeql/_schema.py index f21b95b92..7b15e7c9b 100644 --- a/gel/_internal/_edgeql/_schema.py +++ b/gel/_internal/_edgeql/_schema.py @@ -11,6 +11,8 @@ import uuid from gel._internal._polyfills._strenum import StrEnum +from gel._internal._schemapath import SchemaPath, TypeName + if TYPE_CHECKING: from collections.abc import Iterable @@ -74,46 +76,46 @@ def _get_type_id(name: str, cls: str) -> uuid.UUID: def get_array_type_id_and_name( element: str, -) -> tuple[uuid.UUID, str]: +) -> tuple[uuid.UUID, TypeName]: type_id = _get_type_id(f"array<{_mangle_name(element)}>", "Array") type_name = f"array<{element}>" - return type_id, type_name + return type_id, SchemaPath(type_name) def get_range_type_id_and_name( element: str, -) -> tuple[uuid.UUID, str]: +) -> tuple[uuid.UUID, TypeName]: type_id = _get_type_id(f"range<{_mangle_name(element)}>", "Range") type_name = f"range<{element}>" - return type_id, type_name + return type_id, SchemaPath(type_name) def get_multirange_type_id_and_name( element: str, -) -> tuple[uuid.UUID, str]: +) -> tuple[uuid.UUID, TypeName]: type_id = _get_type_id( f"multirange<{_mangle_name(element)}>", "MultiRange" ) type_name = f"multirange<{element}>" - return type_id, type_name + return type_id, SchemaPath(type_name) def get_tuple_type_id_and_name( elements: Iterable[str], -) -> tuple[uuid.UUID, str]: +) -> tuple[uuid.UUID, TypeName]: body = ", ".join(elements) type_id = _get_type_id(f"tuple<{_mangle_name(body)}>", "Tuple") type_name = f"tuple<{body}>" - return type_id, type_name + return type_id, SchemaPath(type_name) def get_named_tuple_type_id_and_name( elements: dict[str, str], -) -> tuple[uuid.UUID, str]: +) -> tuple[uuid.UUID, TypeName]: body = ", ".join(f"{n}:{t}" for n, t in elements.items()) type_id = _get_type_id(f"tuple<{_mangle_name(body)}>", "Tuple") type_name = f"tuple<{body}>" - return type_id, type_name + return type_id, SchemaPath(type_name) __all__ = ( diff --git a/gel/_internal/_qb/_abstract.py b/gel/_internal/_qb/_abstract.py index 7a9afaa6b..b07729867 100644 --- a/gel/_internal/_qb/_abstract.py +++ b/gel/_internal/_qb/_abstract.py @@ -19,7 +19,7 @@ if TYPE_CHECKING: from collections.abc import Iterable, Iterator, Mapping - from gel._internal._schemapath import SchemaPath + from gel._internal._schemapath import TypeName @dataclass(kw_only=True, frozen=True) @@ -89,7 +89,7 @@ class Expr(Node): def precedence(self) -> _edgeql.Precedence: ... @abc.abstractproperty - def type(self) -> SchemaPath: ... + def type(self) -> TypeName: ... @abc.abstractmethod def __edgeql_expr__(self, *, ctx: ScopeContext) -> str: ... @@ -100,10 +100,10 @@ def __edgeql_qb_expr__(self) -> Self: @dataclass(kw_only=True, frozen=True) class TypedExpr(Expr): - type_: SchemaPath + type_: TypeName @property - def type(self) -> SchemaPath: + def type(self) -> TypeName: return self.type_ @@ -499,7 +499,7 @@ class ImplicitIteratorStmt(IteratorExpr, Stmt): """Base class for statements that are implicit iterators""" @property - def type(self) -> SchemaPath: + def type(self) -> TypeName: return self.iter_expr.type @property diff --git a/gel/_internal/_qb/_expressions.py b/gel/_internal/_qb/_expressions.py index ccb9d442a..bd55f486f 100644 --- a/gel/_internal/_qb/_expressions.py +++ b/gel/_internal/_qb/_expressions.py @@ -20,7 +20,7 @@ from gel._internal import _edgeql from gel._internal._polyfills import _strenum -from gel._internal._schemapath import SchemaPath +from gel._internal._schemapath import SchemaPath, TypeName from ._abstract import ( AtomicExpr, @@ -88,16 +88,16 @@ def __edgeql_expr__(self, *, ctx: ScopeContext) -> str: @dataclass(kw_only=True, frozen=True) class SchemaSet(IdentLikeExpr): - type_: SchemaPath + type_: TypeName def __edgeql_expr__(self, *, ctx: ScopeContext) -> str: - return "::".join(self.type.parts) + return self.type_.as_quoted_schema_name() @dataclass(kw_only=True, frozen=True) class Global(TypedExpr): name: SchemaPath - type_: SchemaPath + type_: TypeName @property def precedence(self) -> _edgeql.Precedence: @@ -175,7 +175,7 @@ def __init__( /, *, items: Iterable[ExprCompatible], - type_: SchemaPath, + type_: TypeName, ) -> None: object.__setattr__( self, @@ -239,7 +239,7 @@ def __init__( /, *, op: _edgeql.Token | str, - type_: SchemaPath, + type_: TypeName, ) -> None: super().__init__(type_=type_) if not isinstance(op, _edgeql.Token): @@ -260,7 +260,7 @@ def __init__( *, expr: ExprCompatible, op: _edgeql.Token | str, - type_: SchemaPath, + type_: TypeName, ) -> None: object.__setattr__(self, "expr", edgeql_qb_expr(expr)) super().__init__(op=op, type_=type_) @@ -281,7 +281,7 @@ def __init__( self, *, expr: ExprCompatible, - type_: SchemaPath, + type_: TypeName, ) -> None: op = _edgeql.Token.RANGBRACKET super().__init__(expr=expr, op=op, type_=type_) @@ -300,11 +300,11 @@ def __edgeql_expr__(self, *, ctx: ScopeContext) -> str: return f"<{self.type.as_quoted_schema_name()}>{expr}" -def empty_set(type_: SchemaPath) -> CastOp: +def empty_set(type_: TypeName) -> CastOp: return CastOp(expr=SetLiteral(items=(), type_=type_), type_=type_) -def empty_set_if_none(val: _T | None, type_: SchemaPath) -> _T | CastOp: +def empty_set_if_none(val: _T | None, type_: TypeName) -> _T | CastOp: return empty_set(type_) if val is None else val @@ -319,7 +319,7 @@ def __init__( lexpr: ExprCompatible, rexpr: ExprCompatible, op: _edgeql.Token | str, - type_: SchemaPath, + type_: TypeName, ) -> None: object.__setattr__(self, "lexpr", edgeql_qb_expr(lexpr)) object.__setattr__(self, "rexpr", edgeql_qb_expr(rexpr)) @@ -337,7 +337,7 @@ def __init__( lexpr: ExprCompatible, rexpr: ExprCompatible, op: _edgeql.Token | str, - type_: SchemaPath, + type_: TypeName, ) -> None: super().__init__(lexpr=lexpr, rexpr=rexpr, op=op, type_=type_) @@ -406,7 +406,7 @@ def __init__( fname: str, args: list[ExprCompatible] | None = None, kwargs: dict[str, ExprCompatible] | None = None, - type_: SchemaPath, + type_: TypeName, ) -> None: object.__setattr__(self, "fname", fname) if args is not None: @@ -508,7 +508,7 @@ def subnodes(self) -> Iterable[Node]: return (self.expr,) @property - def type(self) -> SchemaPath: + def type(self) -> TypeName: return self.expr.type @property @@ -757,7 +757,7 @@ def __post_init__(self) -> None: object.__setattr__(self, "var", var) @property - def type(self) -> SchemaPath: + def type(self) -> TypeName: return self.body.type def subnodes(self) -> Iterable[Node]: @@ -786,7 +786,7 @@ class Splat(_strenum.StrEnum): @dataclass(kw_only=True, frozen=True) class ShapeElement(Node): name: str | Splat - origin: SchemaPath + origin: TypeName expr: Expr | None = None def subnodes(self) -> Iterable[Node]: @@ -798,7 +798,7 @@ def subnodes(self) -> Iterable[Node]: @classmethod def splat( cls, - source: SchemaPath, + source: TypeName, *, kind: Splat = Splat.STAR, ) -> Self: @@ -815,7 +815,7 @@ def subnodes(self) -> Iterable[Node]: @classmethod def splat( cls, - source: SchemaPath, + source: TypeName, *, kind: Splat = Splat.STAR, ) -> Self: @@ -873,7 +873,7 @@ def subnodes(self) -> Iterable[Node]: return (self.iter_expr, self.shape) @property - def type(self) -> SchemaPath: + def type(self) -> TypeName: return self.iter_expr.type @property diff --git a/gel/_internal/_qb/_reflection.py b/gel/_internal/_qb/_reflection.py index 0a8fdb615..f775d7b36 100644 --- a/gel/_internal/_qb/_reflection.py +++ b/gel/_internal/_qb/_reflection.py @@ -10,7 +10,7 @@ if TYPE_CHECKING: import abc from gel._internal import _edgeql - from gel._internal._schemapath import SchemaPath + from gel._internal._schemapath import SchemaPath, TypeName @dataclasses.dataclass(frozen=True, kw_only=True) @@ -33,7 +33,7 @@ class GelReflectionProto(Protocol): class GelSchemaMetadata: class __gel_reflection__: # noqa: N801 - name: ClassVar[SchemaPath] + name: ClassVar[TypeName] # Whether this class is a "canonical" type - that is, the primary # representation of a database type, not an internal __shape__ class, diff --git a/gel/_internal/_qbmodel/_abstract/_globals.py b/gel/_internal/_qbmodel/_abstract/_globals.py index 5d4ab3a36..6b1951768 100644 --- a/gel/_internal/_qbmodel/_abstract/_globals.py +++ b/gel/_internal/_qbmodel/_abstract/_globals.py @@ -11,6 +11,7 @@ from gel._internal import _qb +from gel._internal._schemapath import SchemaPath from ._base import GelType @@ -21,6 +22,7 @@ class Global(_qb.GelSchemaMetadata): @classmethod def global_(cls, tp: type[_T]) -> type[_T]: + assert isinstance(cls.__gel_reflection__.name, SchemaPath) return _qb.AnnotatedGlobal( # type: ignore [return-value] tp, _qb.Global( diff --git a/gel/_internal/_qbmodel/_abstract/_primitive.py b/gel/_internal/_qbmodel/_abstract/_primitive.py index bb8427aa8..2c898e354 100644 --- a/gel/_internal/_qbmodel/_abstract/_primitive.py +++ b/gel/_internal/_qbmodel/_abstract/_primitive.py @@ -237,7 +237,7 @@ def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyr class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 id = tid - name = SchemaPath(tname) + name = tname return __gel_reflection__ @@ -325,7 +325,7 @@ def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyr class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 id = tid - name = SchemaPath(tname) + name = tname return __gel_reflection__ @@ -367,7 +367,7 @@ def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyr class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 id = tid - name = SchemaPath(tname) + name = tname return __gel_reflection__ diff --git a/gel/_internal/_reflection/_types.py b/gel/_internal/_reflection/_types.py index 5028d6b69..a6ca93a66 100644 --- a/gel/_internal/_reflection/_types.py +++ b/gel/_internal/_reflection/_types.py @@ -574,7 +574,7 @@ def _element_type_id(self) -> str: def get_id_and_name(self, element_type: Type) -> tuple[str, str]: id_, name = _edgeql.get_array_type_id_and_name(element_type.name) - return str(id_), name + return str(id_), name.as_schema_name() @struct @@ -600,7 +600,7 @@ def _element_type_id(self) -> str: def get_id_and_name(self, element_type: Type) -> tuple[str, str]: id_, name = _edgeql.get_range_type_id_and_name(element_type.name) - return str(id_), name + return str(id_), name.as_schema_name() @struct @@ -626,7 +626,7 @@ def _element_type_id(self) -> str: def get_id_and_name(self, element_type: Type) -> tuple[str, str]: id_, name = _edgeql.get_multirange_type_id_and_name(element_type.name) - return str(id_), name + return str(id_), name.as_schema_name() @struct @@ -665,7 +665,7 @@ def get_id_and_name( id_, name = _edgeql.get_tuple_type_id_and_name( el.name for el in element_types ) - return str(id_), name + return str(id_), name.as_schema_name() @struct @@ -694,7 +694,7 @@ def get_id_and_name( ) } ) - return str(id_), name + return str(id_), name.as_schema_name() def compare_type_generality(a: Type, b: Type, *, schema: Schema) -> int: diff --git a/gel/_internal/_schemapath.py b/gel/_internal/_schemapath.py index d7cbfbdfa..33e934404 100644 --- a/gel/_internal/_schemapath.py +++ b/gel/_internal/_schemapath.py @@ -8,6 +8,7 @@ from typing import SupportsIndex, TypeVar, overload from typing_extensions import Self, TypeAliasType from collections.abc import Sequence +import dataclasses import functools import pathlib @@ -142,6 +143,9 @@ def is_relative_to(self, other: SchemaPathLike) -> bool: def has_prefix(self, other: SchemaPath) -> bool: return self.parts[: len(other.parts)] == other.parts + def as_schema_name(self) -> str: + return _SEP.join(p for p in self.parts) + def as_quoted_schema_name(self) -> str: return _SEP.join(_edgeql.quote_ident(p) for p in self.parts) @@ -209,3 +213,33 @@ def __getitem__(self, idx: SupportsIndex | slice) -> _T | Sequence[_T]: def __repr__(self) -> str: return f"<{type(self._path).__name__}.parents>" + + +@dataclasses.dataclass(frozen=True) +class ParametricTypeName: + type_: SchemaPath + args: list[TypeName] + + def __str__(self) -> str: + return self.as_quoted_schema_name() + + def as_schema_name(self) -> str: + return ( + f"{self.type_.as_schema_name()}<" + f"{','.join(a.as_schema_name() for a in self.args)}" + f">" + ) + + def as_quoted_schema_name(self) -> str: + return ( + f"{self.type_.as_quoted_schema_name()}<" + f"{','.join(a.as_quoted_schema_name() for a in self.args)}" + f">" + ) + + @property + def name(self) -> str: + raise NotImplementedError + + +TypeName = TypeAliasType("TypeName", SchemaPath | ParametricTypeName) From 1a5d81c30c22f143879f6bc5d2a79c3f703a40d2 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 1 Oct 2025 15:04:36 -0700 Subject: [PATCH 02/10] Add pyright ignores. --- gel/_internal/_qbmodel/_abstract/_primitive.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/gel/_internal/_qbmodel/_abstract/_primitive.py b/gel/_internal/_qbmodel/_abstract/_primitive.py index 2c898e354..138ac7774 100644 --- a/gel/_internal/_qbmodel/_abstract/_primitive.py +++ b/gel/_internal/_qbmodel/_abstract/_primitive.py @@ -860,8 +860,8 @@ def __init__( # noqa: PLR0917 ) -> None: if hex is not None and isinstance(hex, uuid.UUID): super().__init__( - int=hex.int, - is_safe=hex.is_safe, + int=hex.int, # pyright: ignore[reportArgumentType] + is_safe=hex.is_safe, # pyright: ignore[reportArgumentType] ) else: super().__init__( From b12e21333b142634ec59028179d332edf42089b6 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Wed, 1 Oct 2025 19:58:46 -0700 Subject: [PATCH 03/10] Add type_name property to reflection types.. --- gel/_internal/_edgeql/_schema.py | 54 ++++++++------- .../_qbmodel/_abstract/_primitive.py | 6 +- gel/_internal/_reflection/_types.py | 68 ++++++++++++------- 3 files changed, 75 insertions(+), 53 deletions(-) diff --git a/gel/_internal/_edgeql/_schema.py b/gel/_internal/_edgeql/_schema.py index 7b15e7c9b..8c6f5b464 100644 --- a/gel/_internal/_edgeql/_schema.py +++ b/gel/_internal/_edgeql/_schema.py @@ -5,17 +5,13 @@ from __future__ import annotations -from typing import TYPE_CHECKING, final +from typing import final import re import uuid from gel._internal._polyfills._strenum import StrEnum -from gel._internal._schemapath import SchemaPath, TypeName - - -if TYPE_CHECKING: - from collections.abc import Iterable +from gel._internal._schemapath import ParametricTypeName, SchemaPath, TypeName @final @@ -75,44 +71,54 @@ def _get_type_id(name: str, cls: str) -> uuid.UUID: def get_array_type_id_and_name( - element: str, + element: TypeName, ) -> tuple[uuid.UUID, TypeName]: - type_id = _get_type_id(f"array<{_mangle_name(element)}>", "Array") - type_name = f"array<{element}>" - return type_id, SchemaPath(type_name) + type_id = _get_type_id( + f"array<{_mangle_name(element.as_schema_name())}>", "Array" + ) + type_name = ParametricTypeName(SchemaPath("std", "array"), [element]) + return type_id, type_name def get_range_type_id_and_name( - element: str, + element: TypeName, ) -> tuple[uuid.UUID, TypeName]: - type_id = _get_type_id(f"range<{_mangle_name(element)}>", "Range") - type_name = f"range<{element}>" - return type_id, SchemaPath(type_name) + type_id = _get_type_id( + f"range<{_mangle_name(element.as_schema_name())}>", "Range" + ) + type_name = ParametricTypeName( + SchemaPath("std", "range"), + [element], + ) + return type_id, type_name def get_multirange_type_id_and_name( - element: str, + element: TypeName, ) -> tuple[uuid.UUID, TypeName]: type_id = _get_type_id( - f"multirange<{_mangle_name(element)}>", "MultiRange" + f"multirange<{_mangle_name(element.as_schema_name())}>", "MultiRange" ) - type_name = f"multirange<{element}>" - return type_id, SchemaPath(type_name) + type_name = ParametricTypeName( + SchemaPath("std", "range"), + [element], + ) + return type_id, type_name def get_tuple_type_id_and_name( - elements: Iterable[str], + elements: list[TypeName], ) -> tuple[uuid.UUID, TypeName]: - body = ", ".join(elements) + body = ", ".join(element.as_schema_name() for element in elements) type_id = _get_type_id(f"tuple<{_mangle_name(body)}>", "Tuple") - type_name = f"tuple<{body}>" - return type_id, SchemaPath(type_name) + type_name = ParametricTypeName(SchemaPath("std", "tuple"), elements) + return type_id, type_name def get_named_tuple_type_id_and_name( - elements: dict[str, str], + elements: dict[str, TypeName], ) -> tuple[uuid.UUID, TypeName]: - body = ", ".join(f"{n}:{t}" for n, t in elements.items()) + body = ", ".join(f"{n}:{t.as_schema_name()}" for n, t in elements.items()) type_id = _get_type_id(f"tuple<{_mangle_name(body)}>", "Tuple") type_name = f"tuple<{body}>" return type_id, SchemaPath(type_name) diff --git a/gel/_internal/_qbmodel/_abstract/_primitive.py b/gel/_internal/_qbmodel/_abstract/_primitive.py index 138ac7774..046f2ea55 100644 --- a/gel/_internal/_qbmodel/_abstract/_primitive.py +++ b/gel/_internal/_qbmodel/_abstract/_primitive.py @@ -232,7 +232,7 @@ def __set__( @classmethod def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyright: ignore [reportIncompatibleVariableOverride] tid, tname = _edgeql.get_array_type_id_and_name( - str(cls.__element_type__.__gel_reflection__.name) + cls.__element_type__.__gel_reflection__.name ) class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 @@ -320,7 +320,7 @@ def __set__( @classmethod def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyright: ignore [reportIncompatibleVariableOverride] tid, tname = _edgeql.get_tuple_type_id_and_name( - str(el.__gel_reflection__.name) for el in cls.__element_types__ + [el.__gel_reflection__.name for el in cls.__element_types__] ) class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 @@ -362,7 +362,7 @@ def __set__( @classmethod def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyright: ignore [reportIncompatibleVariableOverride] tid, tname = _edgeql.get_range_type_id_and_name( - str(cls.__element_type__.__gel_reflection__.name) + cls.__element_type__.__gel_reflection__.name ) class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 diff --git a/gel/_internal/_reflection/_types.py b/gel/_internal/_reflection/_types.py index a6ca93a66..ad05daec1 100644 --- a/gel/_internal/_reflection/_types.py +++ b/gel/_internal/_reflection/_types.py @@ -23,7 +23,7 @@ from gel._internal import _dataclass_extras from gel._internal import _edgeql -from gel._internal._schemapath import SchemaPath +from gel._internal._schemapath import ParametricTypeName, TypeName from . import _query from ._base import struct, sobject, SchemaObject @@ -69,7 +69,7 @@ def __str__(self) -> str: @functools.cached_property def edgeql(self) -> str: - return self.schemapath.as_quoted_schema_name() + return self.type_name.as_quoted_schema_name() @functools.cached_property def generic(self) -> bool: @@ -80,6 +80,10 @@ def generic(self) -> bool: and sp.name.startswith("any") ) + @functools.cached_property + def type_name(self) -> TypeName: + return self.schemapath + def assignable_from( self, other: Type, @@ -182,10 +186,6 @@ class InheritingType(Type): bases: tuple[TypeRef, ...] ancestors: tuple[TypeRef, ...] - @functools.cached_property - def edgeql(self) -> str: - return self.schemapath.as_quoted_schema_name() - def _assignable_from( self, other: Type, @@ -328,14 +328,6 @@ class CollectionType(Type): def __post_init__(self) -> None: object.__setattr__(self, "_mutable_cached", None) - @functools.cached_property - def edgeql(self) -> str: - return str(self.schemapath) - - @functools.cached_property - def schemapath(self) -> SchemaPath: - return SchemaPath.from_segments(self.name) - @struct class HomogeneousCollectionType(CollectionType): @@ -361,6 +353,15 @@ def get_element_type(self, schema: Schema) -> Type: else: return schema[self._element_type_id] + @functools.cached_property + def type_name(self) -> TypeName: + if self.element_type is None: + return self.schemapath + else: + return ParametricTypeName( + self.schemapath, [self.element_type.type_name] + ) + @functools.cached_property def _element_type_id(self) -> str: raise NotImplementedError("_element_type_id") @@ -457,6 +458,19 @@ def get_element_types(self, schema: Schema) -> tuple[Type, ...]: else: return tuple(schema[el_tid] for el_tid in self._element_type_ids) + @functools.cached_property + def type_name(self) -> TypeName: + if self.element_types is None: + return self.schemapath + else: + return ParametricTypeName( + self.schemapath, + [ + element_type.type_name + for element_type in self.element_types + ], + ) + @functools.cached_property def _element_type_ids(self) -> list[str]: raise NotImplementedError("_element_type_ids") @@ -573,8 +587,8 @@ def _element_type_id(self) -> str: return self.array_element_id def get_id_and_name(self, element_type: Type) -> tuple[str, str]: - id_, name = _edgeql.get_array_type_id_and_name(element_type.name) - return str(id_), name.as_schema_name() + id_, _ = _edgeql.get_array_type_id_and_name(element_type.type_name) + return str(id_), "std::array" @struct @@ -599,8 +613,8 @@ def _element_type_id(self) -> str: return self.range_element_id def get_id_and_name(self, element_type: Type) -> tuple[str, str]: - id_, name = _edgeql.get_range_type_id_and_name(element_type.name) - return str(id_), name.as_schema_name() + id_, _ = _edgeql.get_range_type_id_and_name(element_type.type_name) + return str(id_), "std::range" @struct @@ -625,8 +639,10 @@ def _element_type_id(self) -> str: return self.multirange_element_id def get_id_and_name(self, element_type: Type) -> tuple[str, str]: - id_, name = _edgeql.get_multirange_type_id_and_name(element_type.name) - return str(id_), name.as_schema_name() + id_, _ = _edgeql.get_multirange_type_id_and_name( + element_type.type_name + ) + return str(id_), "std::multirange" @struct @@ -662,10 +678,10 @@ def kind(self) -> Literal[TypeKind.Tuple]: def get_id_and_name( self, element_types: tuple[Type, ...] ) -> tuple[str, str]: - id_, name = _edgeql.get_tuple_type_id_and_name( - el.name for el in element_types + id_, _ = _edgeql.get_tuple_type_id_and_name( + [el.type_name for el in element_types] ) - return str(id_), name.as_schema_name() + return str(id_), "std::tuple" @struct @@ -686,15 +702,15 @@ def kind(self) -> Literal[TypeKind.NamedTuple]: def get_id_and_name( self, element_types: tuple[Type, ...] ) -> tuple[str, str]: - id_, name = _edgeql.get_named_tuple_type_id_and_name( + id_, _ = _edgeql.get_named_tuple_type_id_and_name( { - el.name: el_type.name + el.name: el_type.type_name for el, el_type in zip( self.tuple_elements, element_types, strict=True ) } ) - return str(id_), name.as_schema_name() + return str(id_), "std::tuple" def compare_type_generality(a: Type, b: Type, *, schema: Schema) -> int: From 1c53fcf62ea3ec4128161c6bec017607cf87e204 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 2 Oct 2025 09:51:30 -0700 Subject: [PATCH 04/10] Remove reflection edgeql and use type_name directly. --- gel/_internal/_codegen/_models/_pydantic.py | 2 +- gel/_internal/_reflection/_types.py | 4 ---- 2 files changed, 1 insertion(+), 5 deletions(-) diff --git a/gel/_internal/_codegen/_models/_pydantic.py b/gel/_internal/_codegen/_models/_pydantic.py index b12cca007..8e7ae2b1d 100644 --- a/gel/_internal/_codegen/_models/_pydantic.py +++ b/gel/_internal/_codegen/_models/_pydantic.py @@ -1302,7 +1302,7 @@ def _reflect_pointer( kwargs: dict[str, str] = { "name": repr(ptr.name), "type": target_type.schemapath.as_code(classes["SchemaPath"]), - "typexpr": repr(target_type.edgeql), + "typexpr": repr(target_type.type_name.as_quoted_schema_name()), "kind": f"{classes['PointerKind']}({str(ptr.kind)!r})", "cardinality": f"{classes['Cardinality']}({str(ptr.card)!r})", "computed": str(ptr.is_computed), diff --git a/gel/_internal/_reflection/_types.py b/gel/_internal/_reflection/_types.py index ad05daec1..cb54d9816 100644 --- a/gel/_internal/_reflection/_types.py +++ b/gel/_internal/_reflection/_types.py @@ -67,10 +67,6 @@ def kind(self) -> TypeKind: def __str__(self) -> str: return self.name - @functools.cached_property - def edgeql(self) -> str: - return self.type_name.as_quoted_schema_name() - @functools.cached_property def generic(self) -> bool: sp = self.schemapath From bdb461194a38419739fc20d6fad924718b725d33 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 2 Oct 2025 11:31:41 -0700 Subject: [PATCH 05/10] Add __gel_reflection__.type_name and return name to a SchemaPath. --- gel/_internal/_codegen/_models/_pydantic.py | 48 +++++++++++++------ gel/_internal/_qb/_expressions.py | 2 +- gel/_internal/_qb/_reflection.py | 5 +- gel/_internal/_qbmodel/_abstract/_base.py | 6 +-- .../_qbmodel/_abstract/_descriptors.py | 2 +- .../_qbmodel/_abstract/_expressions.py | 9 ++-- gel/_internal/_qbmodel/_abstract/_globals.py | 4 +- gel/_internal/_qbmodel/_abstract/_methods.py | 2 +- .../_qbmodel/_abstract/_primitive.py | 19 ++++---- gel/_internal/_save.py | 11 +++-- gel/_internal/_schemapath.py | 22 ++++++++- gel/models/pydantic.py | 4 +- tests/test_schemapath.py | 34 +++++++++++-- 13 files changed, 119 insertions(+), 49 deletions(-) diff --git a/gel/_internal/_codegen/_models/_pydantic.py b/gel/_internal/_codegen/_models/_pydantic.py index 8e7ae2b1d..ba5640853 100644 --- a/gel/_internal/_codegen/_models/_pydantic.py +++ b/gel/_internal/_codegen/_models/_pydantic.py @@ -1131,7 +1131,8 @@ def write_schema_reflection( base_types: Iterable[str] = (), base_metadata_class: str = "GelSchemaMetadata", ) -> None: - schemapath = self.import_name(BASE_IMPL, "SchemaPath") + sp_clsname = self.import_name(BASE_IMPL, "SchemaPath") + ptn_clsname = self.import_name(BASE_IMPL, "ParametricTypeName") base_types = list(base_types) if not base_types: if isinstance(sobj, reflection.InheritingType): @@ -1148,14 +1149,21 @@ def write_schema_reflection( base_types, ), ): - self.write(f"name = {sobj.schemapath.as_code(schemapath)}") + obj_name = sobj.schemapath.as_python_code(sp_clsname, ptn_clsname) + self.write(f"name = {obj_name}") + if isinstance(sobj, reflection.Type): + type_name = sobj.type_name.as_python_code( + sp_clsname, ptn_clsname + ) + self.write(f"type_name = {type_name}") def write_object_type_reflection( self, objtype: reflection.ObjectType, base_types: list[str], ) -> None: - sp = self.import_name(BASE_IMPL, "SchemaPath") + sp_clsname = self.import_name(BASE_IMPL, "SchemaPath") + ptn_clsname = self.import_name(BASE_IMPL, "ParametricTypeName") lazyclassproperty = self.import_name(BASE_IMPL, "LazyClassProperty") objecttype_t = self.get_type( self._schema_object_type, @@ -1186,7 +1194,14 @@ def write_object_type_reflection( "__gel_reflection__", _map_name(lambda s: f"{s}.__gel_reflection__", class_bases), ): - self.write(f"name = {objtype.schemapath.as_code(sp)}") + obj_name = objtype.schemapath.as_python_code( + sp_clsname, ptn_clsname + ) + type_name = objtype.type_name.as_python_code( + sp_clsname, ptn_clsname + ) + self.write(f"name = {obj_name}") + self.write(f"type_name = {type_name}") # Need a cheap at runtime way to check if the type is abstract # in GelModel.__new__ self.write(f"abstract = {objtype.abstract!r}") @@ -1263,6 +1278,9 @@ def _write_pointers_reflection( self.write(f"my_ptrs: {ptr_ref_t} = {{") classes = { "SchemaPath": self.import_name(BASE_IMPL, "SchemaPath"), + "ParametricTypeName": self.import_name( + BASE_IMPL, "ParametricTypeName" + ), "GelPointerReflection": gel_ptr_ref, "Cardinality": self.import_name(BASE_IMPL, "Cardinality"), "PointerKind": self.import_name(BASE_IMPL, "PointerKind"), @@ -1301,7 +1319,9 @@ def _reflect_pointer( target_type = self._types[ptr.target_id] kwargs: dict[str, str] = { "name": repr(ptr.name), - "type": target_type.schemapath.as_code(classes["SchemaPath"]), + "type": target_type.schemapath.as_python_code( + classes["SchemaPath"], classes["ParametricTypeName"] + ), "typexpr": repr(target_type.type_name.as_quoted_schema_name()), "kind": f"{classes['PointerKind']}({str(ptr.kind)!r})", "cardinality": f"{classes['Cardinality']}({str(ptr.card)!r})", @@ -2800,7 +2820,7 @@ def _write_prefix_op_method_node_ctor( args = [ "expr=self", # The operand is always 'self' for method calls f'op="{op_name}"', # Gel operator name (e.g., "-", "+") - "type_=__rtype__.__gel_reflection__.name", # Result type info + "type_=__rtype__.__gel_reflection__.type_name", # Result type info ] self.write(self.format_list(f"{node_cls}({{list}}),", args)) @@ -2866,7 +2886,7 @@ def _write_prefix_op_func_node_ctor( args = [ f"expr={other}", f'op="{op_name}"', # Gel operator name (e.g., "-", "+") - "type_=__rtype__.__gel_reflection__.name", # Result type info + "type_=__rtype__.__gel_reflection__.type_name", # Result type info ] self.write(self.format_list(f"{node_cls}({{list}}),", args)) @@ -2928,7 +2948,7 @@ def _write_infix_op_func_node_ctor( self.write(f"{op_chain}(") self.write(f' "{op_name}",') self.write(" __args__,") - self.write(" __rtype__.__gel_reflection__.name,") + self.write(" __rtype__.__gel_reflection__.type_name,") self.write(")") return @@ -2951,7 +2971,7 @@ def _write_infix_op_func_node_ctor( f"lexpr={this}", f'op="{op_name}"', f"rexpr={other}", - "type_=__rtype__.__gel_reflection__.name", + "type_=__rtype__.__gel_reflection__.type_name", ] self.write(self.format_list(f"{node_cls}({{list}}),", args)) @@ -3027,7 +3047,7 @@ def _write_infix_op_method_node_ctor( args = [ f'op="{op_name}"', # Gel operator name (e.g., "+", "[]") - "type_=__rtype__.__gel_reflection__.name", # Result type info + "type_=__rtype__.__gel_reflection__.type_name", # Result type info ] if swapped: @@ -5164,7 +5184,7 @@ def _write_func_node_ctor( self.write("args=__args__,") if bool(sig.kwargs): self.write("kwargs=__kwargs__,") - self.write("type_=__rtype__.__gel_reflection__.name,") + self.write("type_=__rtype__.__gel_reflection__.type_name,") self.write(")") def _write_function_overload( @@ -5485,7 +5505,7 @@ def write_cast_match( with self.if_(f"{type_var} is None"): self.write( f"{type_var} = " - f"{cast_t}.__gel_reflection__.name" + f"{cast_t}.__gel_reflection__.type_name" ) if type_var is not None: anytype = self.get_type(self._types_by_name["anytype"]) @@ -5494,7 +5514,7 @@ def write_cast_match( with self.if_(f"{type_var} is None"): self.write( f"{type_var} = " - f"{var}.__gel_reflection__.name" + f"{var}.__gel_reflection__.type_name" ) # Build a `match` block for Python-to-Gel coercion of values. @@ -5608,7 +5628,7 @@ def write_cast_match( self.write( f"{new_pident} = {set_lit}(" f"items=(*{pident},), " - f"type_={canon_type}.__gel_reflection__.name)" + f"type_={canon_type}.__gel_reflection__.type_name)" ) rt_generics = generic_param_map.get("__return__") diff --git a/gel/_internal/_qb/_expressions.py b/gel/_internal/_qb/_expressions.py index bd55f486f..7d78a6238 100644 --- a/gel/_internal/_qb/_expressions.py +++ b/gel/_internal/_qb/_expressions.py @@ -929,7 +929,7 @@ def get_object_type_splat(cls: type[GelTypeMetadata]) -> Shape: shape = _type_splat_cache.get(cls) if shape is None: reflection = cls.__gel_reflection__ - shape = Shape.splat(source=reflection.name) + shape = Shape.splat(source=reflection.type_name) _type_splat_cache[cls] = shape return shape diff --git a/gel/_internal/_qb/_reflection.py b/gel/_internal/_qb/_reflection.py index f775d7b36..515e7d2d4 100644 --- a/gel/_internal/_qb/_reflection.py +++ b/gel/_internal/_qb/_reflection.py @@ -33,7 +33,7 @@ class GelReflectionProto(Protocol): class GelSchemaMetadata: class __gel_reflection__: # noqa: N801 - name: ClassVar[TypeName] + name: ClassVar[SchemaPath] # Whether this class is a "canonical" type - that is, the primary # representation of a database type, not an internal __shape__ class, @@ -47,7 +47,8 @@ class __gel_reflection__(GelSchemaMetadata.__gel_reflection__): # noqa: N801 class GelTypeMetadata(GelSchemaMetadata): - pass + class __gel_reflection__(GelSchemaMetadata.__gel_reflection__): # noqa: N801 + type_name: ClassVar[TypeName] if TYPE_CHECKING: diff --git a/gel/_internal/_qbmodel/_abstract/_base.py b/gel/_internal/_qbmodel/_abstract/_base.py index 475fdfdd5..b64a35c2c 100644 --- a/gel/_internal/_qbmodel/_abstract/_base.py +++ b/gel/_internal/_qbmodel/_abstract/_base.py @@ -202,7 +202,7 @@ def __init_subclass__(cls) -> None: @classmethod def __edgeql_qb_expr__(cls) -> _qb.Expr: # pyright: ignore [reportIncompatibleMethodOverride] - this_type = cls.__gel_reflection__.name + this_type = cls.__gel_reflection__.type_name return _qb.SchemaSet(type_=this_type) if TYPE_CHECKING: @@ -255,8 +255,8 @@ def maybe_collapse_object_type_variant_union( # Not an object type reflection union at all! return None if typename is None: - typename = union_arg.__gel_reflection__.name - elif typename != union_arg.__gel_reflection__.name: + typename = union_arg.__gel_reflection__.type_name + elif typename != union_arg.__gel_reflection__.type_name: # Reflections of different object types, cannot collapse. return None if union_arg.__gel_shape__ == "Default" and default_variant is None: diff --git a/gel/_internal/_qbmodel/_abstract/_descriptors.py b/gel/_internal/_qbmodel/_abstract/_descriptors.py index 449ba4038..5a5dc55c3 100644 --- a/gel/_internal/_qbmodel/_abstract/_descriptors.py +++ b/gel/_internal/_qbmodel/_abstract/_descriptors.py @@ -180,7 +180,7 @@ def get( ptr = owner.__gel_reflection__.pointers[self.__gel_name__] except KeyError: # This is a user-defined ad-hoc computed pointer - type_ = t.__gel_reflection__.name + type_ = t.__gel_reflection__.type_name else: type_ = ptr.type metadata = _qb.Path( diff --git a/gel/_internal/_qbmodel/_abstract/_expressions.py b/gel/_internal/_qbmodel/_abstract/_expressions.py index e2175c0af..b98b8e210 100644 --- a/gel/_internal/_qbmodel/_abstract/_expressions.py +++ b/gel/_internal/_qbmodel/_abstract/_expressions.py @@ -43,7 +43,7 @@ def _get_prefixed_ptr( ptrname: str, scope: _qb.Scope, ) -> tuple[_qb.PathAlias, _qb.Path]: - this_type = cls.__gel_reflection__.name + this_type = cls.__gel_reflection__.type_name ptr = getattr(cls, ptrname, Unspecified) if ptr is Unspecified: @@ -121,7 +121,7 @@ def select( __operand__: _qb.ExprAlias | None = None, **kwargs: bool | _Value, ) -> _qb.ShapeOp: - this_type = cls.__gel_reflection__.name + this_type = cls.__gel_reflection__.type_name scope = _qb.Scope() if __operand__ is not None: @@ -240,10 +240,9 @@ def update( __operand__: _qb.ExprAlias | None = None, **kwargs: _Value, ) -> _qb.UpdateStmt: - this_type = cls.__gel_reflection__.name scope = _qb.Scope() operand = _qb.edgeql_qb_expr(cls if __operand__ is None else __operand__) - this_type = cls.__gel_reflection__.name + this_type = cls.__gel_reflection__.type_name prefix = _qb.PathPrefix(type_=this_type, scope=scope) prefix_alias = _qb.PathAlias(cls, prefix) @@ -443,4 +442,4 @@ def empty_set_if_none( val: _T | None, type_: type[GelType], ) -> _T | _qb.CastOp: - return _qb.empty_set_if_none(val, type_=type_.__gel_reflection__.name) + return _qb.empty_set_if_none(val, type_=type_.__gel_reflection__.type_name) diff --git a/gel/_internal/_qbmodel/_abstract/_globals.py b/gel/_internal/_qbmodel/_abstract/_globals.py index 6b1951768..5e716bd93 100644 --- a/gel/_internal/_qbmodel/_abstract/_globals.py +++ b/gel/_internal/_qbmodel/_abstract/_globals.py @@ -11,7 +11,6 @@ from gel._internal import _qb -from gel._internal._schemapath import SchemaPath from ._base import GelType @@ -22,11 +21,10 @@ class Global(_qb.GelSchemaMetadata): @classmethod def global_(cls, tp: type[_T]) -> type[_T]: - assert isinstance(cls.__gel_reflection__.name, SchemaPath) return _qb.AnnotatedGlobal( # type: ignore [return-value] tp, _qb.Global( name=cls.__gel_reflection__.name, - type_=tp.__gel_reflection__.name, + type_=tp.__gel_reflection__.type_name, ), ) diff --git a/gel/_internal/_qbmodel/_abstract/_methods.py b/gel/_internal/_qbmodel/_abstract/_methods.py index 60c9d5a8f..f5f4d1659 100644 --- a/gel/_internal/_qbmodel/_abstract/_methods.py +++ b/gel/_internal/_qbmodel/_abstract/_methods.py @@ -204,5 +204,5 @@ def __gel_assert_single__( @classmethod def __edgeql_qb_expr__(cls) -> _qb.Expr: # pyright: ignore [reportIncompatibleMethodOverride] - this_type = cls.__gel_reflection__.name + this_type = cls.__gel_reflection__.type_name return _qb.SchemaSet(type_=this_type) diff --git a/gel/_internal/_qbmodel/_abstract/_primitive.py b/gel/_internal/_qbmodel/_abstract/_primitive.py index 046f2ea55..9a40d8849 100644 --- a/gel/_internal/_qbmodel/_abstract/_primitive.py +++ b/gel/_internal/_qbmodel/_abstract/_primitive.py @@ -156,7 +156,7 @@ class AnyEnum(GelScalarType, StrEnum, metaclass=AnyEnumMeta): def __edgeql_literal__(self) -> _qb.Literal | _qb.CastOp: return _qb.Literal( val=str(self), - type_=type(self).__gel_reflection__.name, + type_=type(self).__gel_reflection__.type_name, ) @@ -232,12 +232,13 @@ def __set__( @classmethod def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyright: ignore [reportIncompatibleVariableOverride] tid, tname = _edgeql.get_array_type_id_and_name( - cls.__element_type__.__gel_reflection__.name + cls.__element_type__.__gel_reflection__.type_name ) class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 id = tid - name = tname + name = SchemaPath.from_segments("std", "array") + type_name = tname return __gel_reflection__ @@ -320,12 +321,13 @@ def __set__( @classmethod def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyright: ignore [reportIncompatibleVariableOverride] tid, tname = _edgeql.get_tuple_type_id_and_name( - [el.__gel_reflection__.name for el in cls.__element_types__] + [el.__gel_reflection__.type_name for el in cls.__element_types__] ) class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 id = tid - name = tname + name = SchemaPath.from_segments("std", "tuple") + type_name = tname return __gel_reflection__ @@ -362,12 +364,13 @@ def __set__( @classmethod def __gel_reflection__(cls) -> type[GelPrimitiveType.__gel_reflection__]: # pyright: ignore [reportIncompatibleVariableOverride] tid, tname = _edgeql.get_range_type_id_and_name( - cls.__element_type__.__gel_reflection__.name + cls.__element_type__.__gel_reflection__.type_name ) class __gel_reflection__(GelPrimitiveType.__gel_reflection__): # noqa: N801 id = tid - name = tname + name = SchemaPath.from_segments("std", "range") + type_name = tname return __gel_reflection__ @@ -793,7 +796,7 @@ def get_literal_for_scalar( else: return _qb.CastOp( expr=_qb.StringLiteral(val=str(v)), - type_=t.__gel_reflection__.name, + type_=t.__gel_reflection__.type_name, ) diff --git a/gel/_internal/_save.py b/gel/_internal/_save.py index 5b93fd8f8..9b1e214b8 100644 --- a/gel/_internal/_save.py +++ b/gel/_internal/_save.py @@ -512,7 +512,7 @@ def get_linked_new_objects(obj: GelModel) -> Iterable[GelModel]: def obj_to_name_ql(obj: GelModel) -> str: - return type(obj).__gel_reflection__.name.as_quoted_schema_name() + return type(obj).__gel_reflection__.type_name.as_quoted_schema_name() def shift_dict_list(inp: dict[str, list[T]]) -> dict[str, T]: @@ -1476,7 +1476,9 @@ def _compile_refetch( for obj_id, link_ids in obj_links_all.items() ] - tp_ql_name = tp.__gel_reflection__.name.as_quoted_schema_name() + tp_ql_name = ( + tp.__gel_reflection__.type_name.as_quoted_schema_name() + ) query = f""" with @@ -1545,7 +1547,10 @@ def _compile_batch( # Queries must be independent of each other within the same # ChangeBatch, so we can sort them to group queries. compiled.sort( - key=lambda x: (x[0].__gel_reflection__.name, x[1].single_query) + key=lambda x: ( + x[0].__gel_reflection__.type_name, + x[1].single_query, + ) ) icomp = iter(compiled) diff --git a/gel/_internal/_schemapath.py b/gel/_internal/_schemapath.py index 33e934404..3b017fa1e 100644 --- a/gel/_internal/_schemapath.py +++ b/gel/_internal/_schemapath.py @@ -149,9 +149,13 @@ def as_schema_name(self) -> str: def as_quoted_schema_name(self) -> str: return _SEP.join(_edgeql.quote_ident(p) for p in self.parts) - def as_code(self, clsname: str = "SchemaPath") -> str: + def as_python_code( + self, + schemapath_clsname: str = "SchemaPath", + parametrictype_clsname: str = "ParametricTypeName", + ) -> str: parts = ", ".join(repr(p) for p in self.parts) - return f"{clsname}.from_segments({parts})" + return f"{schemapath_clsname}.from_segments({parts})" def as_pathlib_path(self) -> pathlib.Path: return pathlib.Path(*self.parts) @@ -237,6 +241,20 @@ def as_quoted_schema_name(self) -> str: f">" ) + def as_python_code( + self, + schemapath_clsname: str = "SchemaPath", + parametrictype_clsname: str = "ParametricTypeName", + ) -> str: + type_code = self.type_.as_python_code( + schemapath_clsname, parametrictype_clsname + ) + args_code = ', '.join( + a.as_python_code(schemapath_clsname, parametrictype_clsname) + for a in self.args + ) + return f"{parametrictype_clsname}({type_code}, [{args_code}])" + @property def name(self) -> str: raise NotImplementedError diff --git a/gel/models/pydantic.py b/gel/models/pydantic.py index 986667787..12c820497 100644 --- a/gel/models/pydantic.py +++ b/gel/models/pydantic.py @@ -13,7 +13,7 @@ from gel._internal._deferred_import import DeferredImport from gel._internal._edgeql import Cardinality, PointerKind from gel._internal._lazyprop import LazyClassProperty -from gel._internal._schemapath import SchemaPath +from gel._internal._schemapath import ParametricTypeName, SchemaPath, TypeName from gel._internal._typing_dispatch import dispatch_overload from gel._internal._utils import UnspecifiedType, Unspecified from gel._internal._xmethod import classonlymethod @@ -162,6 +162,7 @@ "OptionalMultiLink", "OptionalMultiLinkWithProps", "OptionalProperty", + "ParametricTypeName", "PathAlias", "PointerKind", "PrefixOp", @@ -183,6 +184,7 @@ "TimeImpl", "Tuple", "TupleMeta", + "TypeName", "UUIDImpl", "Unspecified", "UnspecifiedType", diff --git a/tests/test_schemapath.py b/tests/test_schemapath.py index 270b2c66e..1473062f6 100644 --- a/tests/test_schemapath.py +++ b/tests/test_schemapath.py @@ -20,7 +20,7 @@ import pathlib from typing import Any -from gel._internal._schemapath import SchemaPath +from gel._internal._schemapath import ParametricTypeName, SchemaPath class TestSchemaPath(unittest.TestCase): @@ -304,16 +304,40 @@ def test_schemapath_as_quoted_schema_name(self): def test_schemapath_as_code(self): """Test as_code method.""" - path = SchemaPath("std::int64") - code = path.as_code() + type_name = SchemaPath("std::int64") + code = type_name.as_python_code() self.assertEqual(code, "SchemaPath.from_segments('std', 'int64')") + type_name = ParametricTypeName( + SchemaPath("std", "array"), [SchemaPath("std::int64")] + ) + code = type_name.as_python_code() + self.assertEqual( + code, + "ParametricTypeName(" + "SchemaPath.from_segments('std', 'array'), " + "[SchemaPath.from_segments('std', 'int64')]" + ")", + ) + def test_schemapath_as_code_custom_classname(self): """Test as_code method with custom class name.""" - path = SchemaPath("std::int64") - code = path.as_code("MySchemaPath") + type_name = SchemaPath("std::int64") + code = type_name.as_python_code("MySchemaPath", "MyParametricTypeName") self.assertEqual(code, "MySchemaPath.from_segments('std', 'int64')") + type_name = ParametricTypeName( + SchemaPath("std", "array"), [SchemaPath("std::int64")] + ) + code = type_name.as_python_code("MySchemaPath", "MyParametricTypeName") + self.assertEqual( + code, + "MyParametricTypeName(" + "MySchemaPath.from_segments('std', 'array'), " + "[MySchemaPath.from_segments('std', 'int64')]" + ")", + ) + def test_schemapath_as_pathlib_path(self): """Test as_pathlib_path method.""" path = SchemaPath("std::cal::local_time") From 0327eded09881e28548c5b2cf2a67793377d4c83 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 2 Oct 2025 18:37:11 -0700 Subject: [PATCH 06/10] Update save to use TypeName from reflection. --- gel/_internal/_codegen/_models/_pydantic.py | 7 +- gel/_internal/_qb/_reflection.py | 3 +- gel/_internal/_reflection/_types.py | 122 +++++++++----- gel/_internal/_save.py | 175 +++++++++++++++----- tests/test_link_with_props_set.py | 1 - 5 files changed, 216 insertions(+), 92 deletions(-) diff --git a/gel/_internal/_codegen/_models/_pydantic.py b/gel/_internal/_codegen/_models/_pydantic.py index ba5640853..2ec780849 100644 --- a/gel/_internal/_codegen/_models/_pydantic.py +++ b/gel/_internal/_codegen/_models/_pydantic.py @@ -1152,7 +1152,7 @@ def write_schema_reflection( obj_name = sobj.schemapath.as_python_code(sp_clsname, ptn_clsname) self.write(f"name = {obj_name}") if isinstance(sobj, reflection.Type): - type_name = sobj.type_name.as_python_code( + type_name = sobj.get_type_name(self._types).as_python_code( sp_clsname, ptn_clsname ) self.write(f"type_name = {type_name}") @@ -1197,7 +1197,7 @@ def write_object_type_reflection( obj_name = objtype.schemapath.as_python_code( sp_clsname, ptn_clsname ) - type_name = objtype.type_name.as_python_code( + type_name = objtype.get_type_name(self._types).as_python_code( sp_clsname, ptn_clsname ) self.write(f"name = {obj_name}") @@ -1319,10 +1319,9 @@ def _reflect_pointer( target_type = self._types[ptr.target_id] kwargs: dict[str, str] = { "name": repr(ptr.name), - "type": target_type.schemapath.as_python_code( + "type": target_type.get_type_name(self._types).as_python_code( classes["SchemaPath"], classes["ParametricTypeName"] ), - "typexpr": repr(target_type.type_name.as_quoted_schema_name()), "kind": f"{classes['PointerKind']}({str(ptr.kind)!r})", "cardinality": f"{classes['Cardinality']}({str(ptr.card)!r})", "computed": str(ptr.is_computed), diff --git a/gel/_internal/_qb/_reflection.py b/gel/_internal/_qb/_reflection.py index 515e7d2d4..5442c9cd6 100644 --- a/gel/_internal/_qb/_reflection.py +++ b/gel/_internal/_qb/_reflection.py @@ -16,8 +16,7 @@ @dataclasses.dataclass(frozen=True, kw_only=True) class GelPointerReflection: name: str - type: SchemaPath - typexpr: str + type: TypeName kind: _edgeql.PointerKind cardinality: _edgeql.Cardinality computed: bool diff --git a/gel/_internal/_reflection/_types.py b/gel/_internal/_reflection/_types.py index cb54d9816..0f07f9f37 100644 --- a/gel/_internal/_reflection/_types.py +++ b/gel/_internal/_reflection/_types.py @@ -7,6 +7,7 @@ from typing import ( TYPE_CHECKING, Any, + ClassVar, Literal, TypeGuard, TypeVar, @@ -40,10 +41,7 @@ @struct class TypeRef: id: str - name: str - - def __str__(self) -> str: - return self.name + name: TypeName @sobject @@ -64,9 +62,6 @@ class Type(SchemaObject, abc.ABC): def kind(self) -> TypeKind: return kind_of_type(self) - def __str__(self) -> str: - return self.name - @functools.cached_property def generic(self) -> bool: sp = self.schemapath @@ -76,8 +71,7 @@ def generic(self) -> bool: and sp.name.startswith("any") ) - @functools.cached_property - def type_name(self) -> TypeName: + def get_type_name(self, schema: Schema) -> TypeName: return self.schemapath def assignable_from( @@ -318,6 +312,8 @@ def mutable(self, schema: Schema) -> bool: class CollectionType(Type): + collection_name: ClassVar[str] + if TYPE_CHECKING: _mutable_cached: bool | None @@ -339,7 +335,12 @@ def mutable(self, schema: Schema) -> bool: object.__setattr__(self, "_mutable_cached", mut) # noqa: PLC2801 return mut - def get_id_and_name(self, element_type: Type) -> tuple[str, str]: + def get_id_and_name( + self, + element_type: Type, + *, + schema: Mapping[str, Type], + ) -> tuple[str, str]: cls = type(self) raise NotImplementedError(f"{cls.__qualname__}.get_id_and_name()") @@ -349,14 +350,11 @@ def get_element_type(self, schema: Schema) -> Type: else: return schema[self._element_type_id] - @functools.cached_property - def type_name(self) -> TypeName: - if self.element_type is None: - return self.schemapath - else: - return ParametricTypeName( - self.schemapath, [self.element_type.type_name] - ) + def get_type_name(self, schema: Schema) -> TypeName: + return ParametricTypeName( + self.schemapath, + [self.get_element_type(schema).get_type_name(schema)], + ) @functools.cached_property def _element_type_id(self) -> str: @@ -386,11 +384,10 @@ def specialize( element_spec, schema=schema, ) - id_, name = self.get_id_and_name(element_type) + id_, _ = self.get_id_and_name(element_type, schema=schema) return dataclasses.replace( self, id=id_, - name=name, element_type=element_type, ) @@ -443,7 +440,10 @@ def mutable(self, schema: Schema) -> bool: return mut def get_id_and_name( - self, element_types: tuple[Type, ...] + self, + element_types: tuple[Type, ...], + *, + schema: Mapping[str, Type], ) -> tuple[str, str]: cls = type(self) raise NotImplementedError(f"{cls.__qualname__}.get_id_and_name()") @@ -454,18 +454,14 @@ def get_element_types(self, schema: Schema) -> tuple[Type, ...]: else: return tuple(schema[el_tid] for el_tid in self._element_type_ids) - @functools.cached_property - def type_name(self) -> TypeName: - if self.element_types is None: - return self.schemapath - else: - return ParametricTypeName( - self.schemapath, - [ - element_type.type_name - for element_type in self.element_types - ], - ) + def get_type_name(self, schema: Schema) -> TypeName: + return ParametricTypeName( + self.schemapath, + [ + element_type.get_type_name(schema) + for element_type in self.get_element_types(schema) + ], + ) @functools.cached_property def _element_type_ids(self) -> list[str]: @@ -507,11 +503,10 @@ def specialize( element_types.append(element_type) element_types_tup = tuple(element_types) - id_, name = self.get_id_and_name(element_types_tup) + id_, _ = self.get_id_and_name(element_types_tup, schema=schema) return dataclasses.replace( self, id=id_, - name=name, element_types=element_types_tup, ) @@ -563,6 +558,8 @@ def _assignable_from( @struct class ArrayType(HomogeneousCollectionType): + collection_name: ClassVar[str] = "std::array" + array_element_id: str is_object: Literal[False] @@ -582,13 +579,22 @@ def kind(self) -> Literal[TypeKind.Array]: def _element_type_id(self) -> str: return self.array_element_id - def get_id_and_name(self, element_type: Type) -> tuple[str, str]: - id_, _ = _edgeql.get_array_type_id_and_name(element_type.type_name) + def get_id_and_name( + self, + element_type: Type, + *, + schema: Mapping[str, Type], + ) -> tuple[str, str]: + id_, _ = _edgeql.get_array_type_id_and_name( + element_type.get_type_name(schema) + ) return str(id_), "std::array" @struct class RangeType(HomogeneousCollectionType): + collection_name: ClassVar[str] = "std::range" + is_object: Literal[False] is_scalar: Literal[False] is_array: Literal[False] @@ -608,13 +614,22 @@ def kind(self) -> Literal[TypeKind.Range]: def _element_type_id(self) -> str: return self.range_element_id - def get_id_and_name(self, element_type: Type) -> tuple[str, str]: - id_, _ = _edgeql.get_range_type_id_and_name(element_type.type_name) + def get_id_and_name( + self, + element_type: Type, + *, + schema: Mapping[str, Type], + ) -> tuple[str, str]: + id_, _ = _edgeql.get_range_type_id_and_name( + element_type.get_type_name(schema) + ) return str(id_), "std::range" @struct class MultiRangeType(HomogeneousCollectionType): + collection_name: ClassVar[str] = "std::multirange" + is_object: Literal[False] is_scalar: Literal[False] is_array: Literal[False] @@ -634,9 +649,14 @@ def kind(self) -> Literal[TypeKind.MultiRange]: def _element_type_id(self) -> str: return self.multirange_element_id - def get_id_and_name(self, element_type: Type) -> tuple[str, str]: + def get_id_and_name( + self, + element_type: Type, + *, + schema: Mapping[str, Type], + ) -> tuple[str, str]: id_, _ = _edgeql.get_multirange_type_id_and_name( - element_type.type_name + element_type.get_type_name(schema) ) return str(id_), "std::multirange" @@ -658,6 +678,8 @@ def _element_type_ids(self) -> list[str]: @struct class TupleType(_TupleType): + collection_name: ClassVar[str] = "std::tuple" + is_object: Literal[False] is_scalar: Literal[False] is_array: Literal[False] @@ -672,16 +694,21 @@ def kind(self) -> Literal[TypeKind.Tuple]: return TypeKind.Tuple def get_id_and_name( - self, element_types: tuple[Type, ...] + self, + element_types: tuple[Type, ...], + *, + schema: Mapping[str, Type], ) -> tuple[str, str]: id_, _ = _edgeql.get_tuple_type_id_and_name( - [el.type_name for el in element_types] + [el.get_type_name(schema) for el in element_types] ) return str(id_), "std::tuple" @struct class NamedTupleType(_TupleType): + collection_name: ClassVar[str] = "std::tuple" + is_object: Literal[False] is_scalar: Literal[False] is_array: Literal[False] @@ -696,11 +723,14 @@ def kind(self) -> Literal[TypeKind.NamedTuple]: return TypeKind.NamedTuple def get_id_and_name( - self, element_types: tuple[Type, ...] + self, + element_types: tuple[Type, ...], + *, + schema: Mapping[str, Type], ) -> tuple[str, str]: id_, _ = _edgeql.get_named_tuple_type_id_and_name( { - el.name: el_type.type_name + el.name: el_type.get_type_name(schema) for el, el_type in zip( self.tuple_elements, element_types, strict=True ) @@ -893,7 +923,7 @@ def fetch_types( cls = _kind_to_class[kind_of_type(t)] replace: dict[str, Any] = {} if issubclass(cls, CollectionType): - replace["name"] = _edgeql.unmangle_unqual_name(t.name) + replace["name"] = cls.collection_name vt = _dataclass_extras.coerce_to_dataclass( cls, t, diff --git a/gel/_internal/_save.py b/gel/_internal/_save.py index 9b1e214b8..958572df6 100644 --- a/gel/_internal/_save.py +++ b/gel/_internal/_save.py @@ -45,6 +45,7 @@ TrackedList, ) from gel._internal._edgeql import PointerKind, quote_ident +from gel._internal._schemapath import ParametricTypeName, SchemaPath, TypeName import operator if TYPE_CHECKING: @@ -515,6 +516,10 @@ def obj_to_name_ql(obj: GelModel) -> str: return type(obj).__gel_reflection__.type_name.as_quoted_schema_name() +def ptr_type_name(ptr: GelPointerReflection) -> str: + return ptr.type.as_quoted_schema_name() + + def shift_dict_list(inp: dict[str, list[T]]) -> dict[str, T]: ret: dict[str, T] = {} for key, lst in list(inp.items()): @@ -1930,11 +1935,16 @@ def _compile_change( shape_parts: list[str] = [] args: list[object] = [] - args_types: list[str] = [] + args_types: list[TypeName] = [] + + def typename_is_array(type_name: TypeName) -> bool: + return isinstance(type_name, ParametricTypeName) and ( + type_name.type_.parts in {("array",), ("std", "array")} + ) def arg_cast( - type_ql: str, - ) -> tuple[str, Callable[[str], str], Callable[[object], object]]: + type_name: TypeName, + ) -> tuple[TypeName, Callable[[str], str], Callable[[object], object]]: # As a workaround for the current limitation # of EdgeQL (tuple arguments can't have empty # sets as elements, and free objects input @@ -1952,22 +1962,33 @@ def arg_cast( # # The nested tuple indirection is needed to support # link props that have types of arrays. - if type_ql.startswith("array<"): - cast = f"array>" + if typename_is_array(ch.info.type): + cast = ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [ + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + [type_name], + ), + ], + ) ret = lambda x: f"(select std::array_unpack({x}).0 limit 1)" # noqa: E731 arg_pack = lambda x: [(x,)] if x is not None else [] # noqa: E731 else: - cast = f"array<{type_ql}>" + cast = ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [type_name], + ) ret = lambda x: f"(select std::array_unpack({x}) limit 1)" # noqa: E731 arg_pack = lambda x: [x] if x is not None else [] # noqa: E731 return cast, ret, arg_pack def add_arg( - type_ql: str, + type_name: TypeName, value: Any, ) -> str: argnum = str(len(args)) - args_types.append(type_ql) + args_types.append(type_name) args.append(value) return f"__data.{argnum}" @@ -1978,9 +1999,9 @@ def add_arg( for ch in change.fields.values(): if isinstance(ch, PropertyChange): if ch.info.cardinality.is_optional(): - tp_ql, tp_unpack, arg_pack = arg_cast(ch.info.typexpr) + tp_cast, tp_unpack, arg_pack = arg_cast(ch.info.type) arg = add_arg( - tp_ql, + tp_cast, arg_pack(ch.value), ) shape_parts.append( @@ -1988,7 +2009,7 @@ def add_arg( ) else: assert ch.value is not None - arg = add_arg(ch.info.typexpr, ch.value) + arg = add_arg(ch.info.type, ch.value) shape_parts.append(f"{quote_ident(ch.name)} := {arg}") elif isinstance(ch, MultiPropAdd): @@ -1997,15 +2018,26 @@ def add_arg( # or arrays etc. assign_op = ":=" if for_insert else "+=" - if ch.info.typexpr.startswith("array<"): - arg_t = f"array>" + if typename_is_array(ch.info.type): + arg_t = ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [ + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + [ch.info.type], + ), + ], + ) arg = add_arg(arg_t, [(el,) for el in ch.added]) shape_parts.append( f"{quote_ident(ch.name)} {assign_op} " f"std::array_unpack({arg}).0" ) else: - arg_t = f"array<{ch.info.typexpr}>" + arg_t = ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [ch.info.type], + ) arg = add_arg(arg_t, ch.added) shape_parts.append( f"{quote_ident(ch.name)} {assign_op} " @@ -2015,14 +2047,25 @@ def add_arg( elif isinstance(ch, MultiPropRemove): assert not for_insert - if ch.info.typexpr.startswith("array<"): - arg_t = f"array>" + if typename_is_array(ch.info.type): + arg_t = ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [ + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + [ch.info.type], + ), + ], + ) arg = add_arg(arg_t, [(el,) for el in ch.removed]) shape_parts.append( f"{quote_ident(ch.name)} -= std::array_unpack({arg}).0" ) else: - arg_t = f"array<{ch.info.typexpr}>" + arg_t = ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [ch.info.type], + ) arg = add_arg(arg_t, ch.removed) shape_parts.append( f"{quote_ident(ch.name)} -= std::array_unpack({arg})" @@ -2032,14 +2075,14 @@ def add_arg( tid = ( self._get_id(ch.target) if ch.target is not None else None ) - linked_name = ch.info.typexpr + linked_name = ptr_type_name(ch.info) if ch.props and ch.target is not None: assert ch.props_info is not None assert tid is not None arg_casts = { - k: arg_cast(ch.props_info[k].typexpr) + k: arg_cast(ch.props_info[k].type) for k in ch.props_info } @@ -2047,8 +2090,15 @@ def add_arg( # - whether the lprop is being set # - whether the lprop is being set to the default value # - the value being set if present - sl_subt = [ - f"tuple" + sl_subt: list[TypeName] = [ + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + [ + SchemaPath.from_segments("std", "bool"), + SchemaPath.from_segments("std", "bool"), + arg_casts[lp_name][0], + ], + ) for lp_name in ch.props_info ] @@ -2067,7 +2117,13 @@ def add_arg( sl_args = [tid, *lprop_args] arg = add_arg( - f"tuple", + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + [ + SchemaPath.from_segments("std", "uuid"), + *sl_subt, + ], + ), sl_args, ) @@ -2157,7 +2213,10 @@ def add_arg( else: if ch.info.cardinality.is_optional(): arg = add_arg( - "array", + ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [SchemaPath.from_segments("std", "uuid")], + ), [tid] if tid is not None else [], ) shape_parts.append( @@ -2168,7 +2227,9 @@ def add_arg( ) else: assert tid is not None - arg = add_arg("std::uuid", tid) + arg = add_arg( + SchemaPath.from_segments("std", "uuid"), tid + ) shape_parts.append( f"{quote_ident(ch.name)} := " f"<{linked_name}>{arg}" @@ -2178,13 +2239,16 @@ def add_arg( assert not for_insert arg = add_arg( - "array", + ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [SchemaPath.from_segments("std", "uuid")], + ), [self._get_id(o) for o in ch.removed], ) shape_parts.append( f"{quote_ident(ch.name)} -= " - f"<{ch.info.typexpr}>std::array_unpack({arg})" + f"<{ptr_type_name(ch.info)}>std::array_unpack({arg})" ) elif isinstance(ch, MultiLinkAdd): @@ -2195,10 +2259,10 @@ def add_arg( assert ch.props_info link_args: list[tuple[Any, ...]] = [] - tuple_subt: list[str] | None = None + tuple_subt: list[TypeName] | None = None arg_casts = { - lp_name: arg_cast(ch.props_info[lp_name].typexpr) + lp_name: arg_cast(ch.props_info[lp_name].type) for lp_name in ch.props_info } @@ -2207,7 +2271,14 @@ def add_arg( # - whether the lprop is being set to the default value # - the value being set if present tuple_subt = [ - f"tuple" + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + [ + SchemaPath.from_segments("std", "bool"), + SchemaPath.from_segments("std", "bool"), + arg_casts[lp_name][0], + ], + ) for lp_name in ch.props_info ] @@ -2229,7 +2300,20 @@ def add_arg( link_args.append((self._get_id(addo), *lprop_args)) arg = add_arg( - f"array>", + ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [ + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + [ + SchemaPath.from_segments( + "std", "uuid" + ), + *tuple_subt, + ], + ), + ], + ), link_args, ) @@ -2253,7 +2337,7 @@ def add_arg( f"{quote_ident(ch.name)} {assign_op} " f"assert_distinct((" f"for __tup in std::array_unpack({arg}) union (" - f"select (<{ch.info.typexpr}>__tup.0) {{ " + f"select (<{ptr_type_name(ch.info)}>__tup.0) {{ " f"{lp_assign}" f"}})))" ) @@ -2299,7 +2383,8 @@ def add_arg( ) filter __m.id = __tup.0 ) - select (<{ch.info.typexpr}>__tup.0) {{ + select (<{ptr_type_name(ch.info)}>__tup.0) + {{ {lp_assign_reload} }} ) @@ -2309,14 +2394,17 @@ def add_arg( else: arg = add_arg( - "array", + ParametricTypeName( + SchemaPath.from_segments("std", "array"), + [SchemaPath.from_segments("std", "uuid")], + ), [self._get_id(o) for o in ch.added], ) shape_parts.append( f"{quote_ident(ch.name)} {assign_op} " f"assert_distinct(" - f"<{ch.info.typexpr}>std::array_unpack({arg})" + f"<{ptr_type_name(ch.info)}>std::array_unpack({arg})" f")" ) @@ -2324,7 +2412,7 @@ def add_arg( assert not for_insert shape_parts.append( - f"{quote_ident(ch.name)} := <{ch.info.typexpr}>{{}}" + f"{quote_ident(ch.name)} := <{ptr_type_name(ch.info)}>{{}}" ) else: @@ -2345,7 +2433,10 @@ def add_arg( else: assert shape - arg = add_arg("std::uuid", self._get_id(obj)) + arg = add_arg( + SchemaPath.from_segments("std", "uuid"), + self._get_id(obj), + ) query = f"""\ update <{q_type_name}>{arg} set {{ {shape} }} @@ -2359,14 +2450,18 @@ def add_arg( single_query = f""" with __query := ( - with __data := >$0 + with __data := >$0 select ({query}) ) select __query{select_shape} """ multi_query = f""" with __query := ( - with __all_data := >>$0 + with __all_data := >>$0 for __data in std::array_unpack(__all_data) union ( ({query}) ) @@ -2374,7 +2469,9 @@ def add_arg( """ args_query = f""" - with __all_data := >>$0 + with __all_data := >>$0 select std::count(std::array_unpack(__all_data)) """ diff --git a/tests/test_link_with_props_set.py b/tests/test_link_with_props_set.py index 4f31cf6cf..e6620b16d 100644 --- a/tests/test_link_with_props_set.py +++ b/tests/test_link_with_props_set.py @@ -53,7 +53,6 @@ class __gel_reflection__(AbstractGelLinkModel.__gel_reflection__): # noqa: N801 "comment": _qb.GelPointerReflection( name="comment", type=SchemaPath("std", "str"), - typexpr="std::str", kind=PointerKind.Property, cardinality=Cardinality.AtMostOne, computed=False, From 520a1d322484531d3befc46ee8ca7ec0e41ef9be Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 2 Oct 2025 23:18:58 -0700 Subject: [PATCH 07/10] Re-add __str__. Removing it breaks codegen! --- gel/_internal/_reflection/_types.py | 3 +++ gel/_internal/_schemapath.py | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/gel/_internal/_reflection/_types.py b/gel/_internal/_reflection/_types.py index 0f07f9f37..4d9db0d43 100644 --- a/gel/_internal/_reflection/_types.py +++ b/gel/_internal/_reflection/_types.py @@ -43,6 +43,9 @@ class TypeRef: id: str name: TypeName + def __str__(self) -> str: + return str(self.name) + @sobject class Type(SchemaObject, abc.ABC): diff --git a/gel/_internal/_schemapath.py b/gel/_internal/_schemapath.py index 3b017fa1e..a928a2861 100644 --- a/gel/_internal/_schemapath.py +++ b/gel/_internal/_schemapath.py @@ -225,7 +225,7 @@ class ParametricTypeName: args: list[TypeName] def __str__(self) -> str: - return self.as_quoted_schema_name() + return self.as_schema_name() def as_schema_name(self) -> str: return ( From 99fd21279242bd4691783719e10bfeb9c72211a2 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 2 Oct 2025 18:56:01 -0700 Subject: [PATCH 08/10] Remove unused name. --- gel/_internal/_reflection/_types.py | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/gel/_internal/_reflection/_types.py b/gel/_internal/_reflection/_types.py index 4d9db0d43..f5bbbf101 100644 --- a/gel/_internal/_reflection/_types.py +++ b/gel/_internal/_reflection/_types.py @@ -442,12 +442,12 @@ def mutable(self, schema: Schema) -> bool: object.__setattr__(self, "_mutable_cached", mut) # noqa: PLC2801 return mut - def get_id_and_name( + def get_id( self, element_types: tuple[Type, ...], *, schema: Mapping[str, Type], - ) -> tuple[str, str]: + ) -> str: cls = type(self) raise NotImplementedError(f"{cls.__qualname__}.get_id_and_name()") @@ -506,7 +506,7 @@ def specialize( element_types.append(element_type) element_types_tup = tuple(element_types) - id_, _ = self.get_id_and_name(element_types_tup, schema=schema) + id_ = self.get_id(element_types_tup, schema=schema) return dataclasses.replace( self, id=id_, @@ -696,16 +696,16 @@ class TupleType(_TupleType): def kind(self) -> Literal[TypeKind.Tuple]: return TypeKind.Tuple - def get_id_and_name( + def get_id( self, element_types: tuple[Type, ...], *, schema: Mapping[str, Type], - ) -> tuple[str, str]: + ) -> str: id_, _ = _edgeql.get_tuple_type_id_and_name( [el.get_type_name(schema) for el in element_types] ) - return str(id_), "std::tuple" + return str(id_) @struct @@ -725,12 +725,12 @@ class NamedTupleType(_TupleType): def kind(self) -> Literal[TypeKind.NamedTuple]: return TypeKind.NamedTuple - def get_id_and_name( + def get_id( self, element_types: tuple[Type, ...], *, schema: Mapping[str, Type], - ) -> tuple[str, str]: + ) -> str: id_, _ = _edgeql.get_named_tuple_type_id_and_name( { el.name: el_type.get_type_name(schema) @@ -739,7 +739,7 @@ def get_id_and_name( ) } ) - return str(id_), "std::tuple" + return str(id_) def compare_type_generality(a: Type, b: Type, *, schema: Schema) -> int: From 30718dc76880e674bdf45c3a07a22848b8077157 Mon Sep 17 00:00:00 2001 From: dnwpark Date: Thu, 2 Oct 2025 19:58:18 -0700 Subject: [PATCH 09/10] Add optional named args for ParametricTypeName. --- gel/_internal/_edgeql/_schema.py | 4 +- gel/_internal/_reflection/_types.py | 9 +++++ gel/_internal/_schemapath.py | 61 +++++++++++++++++++++-------- tests/test_schemapath.py | 30 ++++++++++++++ 4 files changed, 86 insertions(+), 18 deletions(-) diff --git a/gel/_internal/_edgeql/_schema.py b/gel/_internal/_edgeql/_schema.py index 8c6f5b464..0f38a158a 100644 --- a/gel/_internal/_edgeql/_schema.py +++ b/gel/_internal/_edgeql/_schema.py @@ -120,8 +120,8 @@ def get_named_tuple_type_id_and_name( ) -> tuple[uuid.UUID, TypeName]: body = ", ".join(f"{n}:{t.as_schema_name()}" for n, t in elements.items()) type_id = _get_type_id(f"tuple<{_mangle_name(body)}>", "Tuple") - type_name = f"tuple<{body}>" - return type_id, SchemaPath(type_name) + type_name = ParametricTypeName(SchemaPath("std", "tuple"), elements) + return type_id, type_name __all__ = ( diff --git a/gel/_internal/_reflection/_types.py b/gel/_internal/_reflection/_types.py index f5bbbf101..a6729e477 100644 --- a/gel/_internal/_reflection/_types.py +++ b/gel/_internal/_reflection/_types.py @@ -741,6 +741,15 @@ def get_id( ) return str(id_) + def get_type_name(self, schema: Schema) -> TypeName: + return ParametricTypeName( + self.schemapath, + { + element.name: schema[element.type_id].get_type_name(schema) + for element in self.tuple_elements + }, + ) + def compare_type_generality(a: Type, b: Type, *, schema: Schema) -> int: """Return 1 if a is more general than b, -1 if a is more specific diff --git a/gel/_internal/_schemapath.py b/gel/_internal/_schemapath.py index a928a2861..9927c59d5 100644 --- a/gel/_internal/_schemapath.py +++ b/gel/_internal/_schemapath.py @@ -222,24 +222,39 @@ def __repr__(self) -> str: @dataclasses.dataclass(frozen=True) class ParametricTypeName: type_: SchemaPath - args: list[TypeName] + args: list[TypeName] | dict[str, TypeName] def __str__(self) -> str: return self.as_schema_name() def as_schema_name(self) -> str: - return ( - f"{self.type_.as_schema_name()}<" - f"{','.join(a.as_schema_name() for a in self.args)}" - f">" - ) + if isinstance(self.args, list): + return ( + f"{self.type_.as_schema_name()}<" + f"{','.join(a.as_schema_name() for a in self.args)}" + f">" + ) + + else: + args_names = ",".join( + f"{n}:{a.as_schema_name()}" for n, a in self.args.items() + ) + return f"{self.type_.as_schema_name()}<{args_names}>" def as_quoted_schema_name(self) -> str: - return ( - f"{self.type_.as_quoted_schema_name()}<" - f"{','.join(a.as_quoted_schema_name() for a in self.args)}" - f">" - ) + if isinstance(self.args, list): + return ( + f"{self.type_.as_quoted_schema_name()}<" + f"{','.join(a.as_quoted_schema_name() for a in self.args)}" + f">" + ) + + else: + args_names = ",".join( + f"{n}:{a.as_quoted_schema_name()}" + for n, a in self.args.items() + ) + return f"{self.type_.as_schema_name()}<{args_names}>" def as_python_code( self, @@ -249,11 +264,25 @@ def as_python_code( type_code = self.type_.as_python_code( schemapath_clsname, parametrictype_clsname ) - args_code = ', '.join( - a.as_python_code(schemapath_clsname, parametrictype_clsname) - for a in self.args - ) - return f"{parametrictype_clsname}({type_code}, [{args_code}])" + if isinstance(self.args, list): + args_code = ', '.join( + a.as_python_code(schemapath_clsname, parametrictype_clsname) + for a in self.args + ) + return f"{parametrictype_clsname}({type_code}, [{args_code}])" + + else: + args_code = ', '.join( + ( + f"'{n}': " + + a.as_python_code( + schemapath_clsname, + parametrictype_clsname, + ) + ) + for n, a in self.args.items() + ) + return f"{parametrictype_clsname}({type_code}, {{{args_code}}})" @property def name(self) -> str: diff --git a/tests/test_schemapath.py b/tests/test_schemapath.py index 1473062f6..17930d0bd 100644 --- a/tests/test_schemapath.py +++ b/tests/test_schemapath.py @@ -320,6 +320,21 @@ def test_schemapath_as_code(self): ")", ) + type_name = ParametricTypeName( + SchemaPath("std", "tuple"), + {'a': SchemaPath("std::int64"), 'b': SchemaPath("std::str")}, + ) + code = type_name.as_python_code() + self.assertEqual( + code, + "ParametricTypeName(" + "SchemaPath.from_segments('std', 'tuple'), {" + "'a': SchemaPath.from_segments('std', 'int64'), " + "'b': SchemaPath.from_segments('std', 'str')" + "}" + ")", + ) + def test_schemapath_as_code_custom_classname(self): """Test as_code method with custom class name.""" type_name = SchemaPath("std::int64") @@ -338,6 +353,21 @@ def test_schemapath_as_code_custom_classname(self): ")", ) + type_name = ParametricTypeName( + SchemaPath("std", "tuple"), + {'a': SchemaPath("std::int64"), 'b': SchemaPath("std::str")}, + ) + code = type_name.as_python_code("MySchemaPath", "MyParametricTypeName") + self.assertEqual( + code, + "MyParametricTypeName(" + "MySchemaPath.from_segments('std', 'tuple'), {" + "'a': MySchemaPath.from_segments('std', 'int64'), " + "'b': MySchemaPath.from_segments('std', 'str')" + "}" + ")", + ) + def test_schemapath_as_pathlib_path(self): """Test as_pathlib_path method.""" path = SchemaPath("std::cal::local_time") From f0dcc2c3b0371abe49370e87142f282b02ad6dca Mon Sep 17 00:00:00 2001 From: dnwpark Date: Fri, 3 Oct 2025 08:46:09 -0700 Subject: [PATCH 10/10] Fix test. --- tests/test_model_generator.py | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/tests/test_model_generator.py b/tests/test_model_generator.py index e859da133..7d7ed8774 100644 --- a/tests/test_model_generator.py +++ b/tests/test_model_generator.py @@ -53,7 +53,7 @@ from gel._internal._qbmodel._abstract import LinkSet, LinkWithPropsSet from gel._internal._edgeql import Cardinality, PointerKind from gel._internal._qbmodel._pydantic._models import GelModel -from gel._internal._schemapath import SchemaPath +from gel._internal._schemapath import ParametricTypeName, SchemaPath from gel._internal._testbase import _models as tb @@ -5487,8 +5487,20 @@ def test_modelgen_reflection_1(self): ntup_t = default.sub.TypeInSub.__gel_reflection__.pointers["ntup"].type self.assertEqual( - str(ntup_t), - "tuple>", + ntup_t, + ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + { + 'a': SchemaPath.from_segments("std", "str"), + 'b': ParametricTypeName( + SchemaPath.from_segments("std", "tuple"), + { + 'c': SchemaPath.from_segments("std", "int64"), + 'd': SchemaPath.from_segments("std", "str"), + } + ), + } + ), ) def test_modelgen_json_schema_1(self):