diff --git a/pep.rst b/pep.rst index 356c74e..b555b02 100644 --- a/pep.rst +++ b/pep.rst @@ -908,18 +908,18 @@ as a literal type--all of these mechanisms lean very heavily on literal types. :: - def select[ModelT, K: BaseTypedDict]( + def select[ModelT, K: typing.BaseTypedDict]( typ: type[ModelT], /, **kwargs: Unpack[K], ) -> list[ - NewProtocol[ + typing.NewProtocol[ *[ - Member[ - GetName[c], - ConvertField[GetMemberType[ModelT, GetName[c]]], + typing.Member[ + typing.GetName[c], + ConvertField[typing.GetMemberType[ModelT, typing.GetName[c]]], ] - for c in Iter[Attrs[K]] + for c in typing.Iter[typing.Attrs[K]] ] ] ]: ... @@ -936,7 +936,9 @@ producing a new target type containing only properties and wrapping :: type ConvertField[T] = ( - AdjustLink[PropsOnly[PointerArg[T]], T] if IsSub[T, Link] else PointerArg[T] + AdjustLink[PropsOnly[PointerArg[T]], T] + if typing.IsSub[T, Link] + else PointerArg[T] ) ``PointerArg`` gets the type argument to ``Pointer`` or a subclass. @@ -950,14 +952,16 @@ grabs the argument to a ``Pointer``). :: - type PointerArg[T: Pointer] = GetArg[T, Pointer, Literal[0]] + type PointerArg[T: Pointer] = typing.GetArg[T, Pointer, Literal[0]] ``AdjustLink`` sticks a ``list`` around ``MultiLink``, using features we've discussed already. :: - type AdjustLink[Tgt, LinkTy] = list[Tgt] if IsSub[LinkTy, MultiLink] else Tgt + type AdjustLink[Tgt, LinkTy] = ( + list[Tgt] if typing.IsSub[LinkTy, MultiLink] else Tgt + ) And the final helper, ``PropsOnly[T]``, generates a new type that contains all the ``Property`` attributes of ``T``. @@ -965,11 +969,11 @@ contains all the ``Property`` attributes of ``T``. :: type PropsOnly[T] = list[ - NewProtocol[ + typing.NewProtocol[ *[ - Member[GetName[p], PointerArg[GetType[p]]] - for p in Iter[Attrs[T]] - if IsSub[GetType[p], Property] + typing.Member[typing.GetName[p], PointerArg[typing.GetType[p]]] + for p in typing.Iter[typing.Attrs[T]] + if typing.IsSub[typing.GetType[p], Property] ] ] ] @@ -983,22 +987,32 @@ Automatically deriving FastAPI CRUD models ------------------------------------------ We have a more `fully-worked example <#fastapi-test_>`_ in our test -suite, but here is a possible implementation of just ``Public``:: +suite, but here is a possible implementation of just ``Public`` + +:: # Extract the default type from an Init field. # If it is a Field, then we try pulling out the "default" field, # otherwise we return the type itself. type GetDefault[Init] = ( - GetFieldItem[Init, Literal["default"]] if IsSub[Init, Field] else Init + GetFieldItem[Init, Literal["default"]] + if typing.IsSub[Init, Field] + else Init ) # Create takes everything but the primary key and preserves defaults - type Create[T] = NewProtocol[ + type Create[T] = typing.NewProtocol[ *[ - Member[GetName[p], GetType[p], GetQuals[p], GetDefault[GetInit[p]]] - for p in Iter[Attrs[T]] - if not IsSub[ - Literal[True], GetFieldItem[GetInit[p], Literal["primary_key"]] + typing.Member[ + typing.GetName[p], + typing.GetType[p], + typing.GetQuals[p], + GetDefault[typing.GetInit[p]], + ] + for p in typing.Iter[typing.Attrs[T]] + if not typing.IsSub[ + Literal[True], + GetFieldItem[typing.GetInit[p], Literal["primary_key"]], ] ] ] @@ -1023,34 +1037,34 @@ dataclasses-style method generation :: # Generate the Member field for __init__ for a class - type InitFnType[T] = Member[ + type InitFnType[T] = typing.Member[ Literal["__init__"], Callable[ [ - Param[Literal["self"], Self], + typing.Param[Literal["self"], Self], *[ - Param[ - GetName[p], - GetType[p], + typing.Param[ + typing.GetName[p], + typing.GetType[p], # All arguments are keyword-only # It takes a default if a default is specified in the class Literal["keyword"] - if IsSub[ - GetDefault[GetInit[p]], + if typing.IsSub[ + GetDefault[typing.GetInit[p]], Never, ] else Literal["keyword", "default"], ] - for p in Iter[Attrs[T]] + for p in typing.Iter[typing.Attrs[T]] ], ], None, ], Literal["ClassVar"], ] - type AddInit[T] = NewProtocol[ + type AddInit[T] = typing.NewProtocol[ InitFnType[T], - *[x for x in Iter[Members[T]]], + *[x for x in typing.Iter[typing.Members[T]]], ] @@ -1121,11 +1135,7 @@ Renounce all cares of runtime evaluation This would have a lot of simplifying features. -We wouldn't need to worry about making ``IsSub`` be checkable at -runtime, - -XXX - +TODO: Expand Support TypeScript style pattern matching in subtype checking ------------------------------------------------------------- diff --git a/scripts/update-examples.sh b/scripts/update-examples.sh index b845f35..7dd38b0 100755 --- a/scripts/update-examples.sh +++ b/scripts/update-examples.sh @@ -4,5 +4,8 @@ scripts/py2rst.py tests/test_qblike_2.py --start "Begin PEP section" --end "End | scripts/rst_replace_section.py pep.rst qb-impl -i -scripts/py2rst.py tests/test_fastapilike_2.py --start "Begin PEP section" --end "End PEP section" \ +scripts/py2rst.py tests/test_fastapilike_2.py --start "Begin PEP section: dataclass like" --end "End PEP section: __init__" \ | scripts/rst_replace_section.py pep.rst init-impl -i + +scripts/py2rst.py tests/test_fastapilike_2.py --start "Begin PEP section: Automatically deriving FastAPI CRUD models" --end "End PEP section: CRUD" \ + | scripts/rst_replace_section.py pep.rst fastapi-impl -i diff --git a/tests/test_fastapilike_2.py b/tests/test_fastapilike_2.py index c3e35c9..14eaabe 100644 --- a/tests/test_fastapilike_2.py +++ b/tests/test_fastapilike_2.py @@ -11,23 +11,7 @@ ) from typemap.type_eval import eval_typing -from typemap.typing import ( - NewProtocol, - Iter, - Attrs, - IsSub, - FromUnion, - GetArg, - GetMemberType, - GetType, - GetName, - GetQuals, - GetInit, - InitField, - Member, - Members, - Param, -) +from typemap import typing from . import format_helper @@ -39,15 +23,15 @@ class FieldArgs(TypedDict, total=False): default: ReadOnly[object] -class Field[T: FieldArgs](InitField[T]): +class Field[T: FieldArgs](typing.InitField[T]): pass #### # TODO: Should this go into the stdlib? -type GetFieldItem[T: InitField, K] = GetMemberType[ - GetArg[T, InitField, Literal[0]], K +type GetFieldItem[T: typing.InitField, K] = typing.GetMemberType[ + typing.GetArg[T, typing.InitField, Literal[0]], K ] @@ -56,59 +40,96 @@ class Field[T: FieldArgs](InitField[T]): # Strip `| None` from a type by iterating over its union components # and filtering type NotOptional[T] = Union[ - *[x for x in Iter[FromUnion[T]] if not IsSub[x, None]] + *[x for x in typing.Iter[typing.FromUnion[T]] if not typing.IsSub[x, None]] ] # Adjust an attribute type for use in Public below by dropping | None for # primary keys and stripping all annotations. type FixPublicType[T, Init] = ( NotOptional[T] - if IsSub[Literal[True], GetFieldItem[Init, Literal["primary_key"]]] + if typing.IsSub[Literal[True], GetFieldItem[Init, Literal["primary_key"]]] else T ) # Strip out everything that is Hidden and also make the primary key required # Drop all the annotations, since this is for data getting returned to users # from the DB, so we don't need default values. -type Public[T] = NewProtocol[ +type Public[T] = typing.NewProtocol[ *[ - Member[GetName[p], FixPublicType[GetType[p], GetInit[p]], GetQuals[p]] - for p in Iter[Attrs[T]] - if not IsSub[Literal[True], GetFieldItem[GetInit[p], Literal["hidden"]]] + typing.Member[ + typing.GetName[p], + FixPublicType[typing.GetType[p], typing.GetInit[p]], + typing.GetQuals[p], + ] + for p in typing.Iter[typing.Attrs[T]] + if not typing.IsSub[ + Literal[True], GetFieldItem[typing.GetInit[p], Literal["hidden"]] + ] ] ] +# Begin PEP section: Automatically deriving FastAPI CRUD models +""" +We have a more `fully-worked example <#fastapi-test_>`_ in our test +suite, but here is a possible implementation of just ``Public`` +""" + # Extract the default type from an Init field. # If it is a Field, then we try pulling out the "default" field, # otherwise we return the type itself. type GetDefault[Init] = ( - GetFieldItem[Init, Literal["default"]] if IsSub[Init, Field] else Init + GetFieldItem[Init, Literal["default"]] + if typing.IsSub[Init, Field] + else Init ) # Create takes everything but the primary key and preserves defaults -type Create[T] = NewProtocol[ +type Create[T] = typing.NewProtocol[ *[ - Member[GetName[p], GetType[p], GetQuals[p], GetDefault[GetInit[p]]] - for p in Iter[Attrs[T]] - if not IsSub[ - Literal[True], GetFieldItem[GetInit[p], Literal["primary_key"]] + typing.Member[ + typing.GetName[p], + typing.GetType[p], + typing.GetQuals[p], + GetDefault[typing.GetInit[p]], + ] + for p in typing.Iter[typing.Attrs[T]] + if not typing.IsSub[ + Literal[True], + GetFieldItem[typing.GetInit[p], Literal["primary_key"]], ] ] ] +""" +The ``Create`` type alias creates a new type (via ``NewProtocol``) by +iterating over the attributes of the original type. It has access to +names, types, qualifiers, and the literal types of initializers (in +part through new facilities to handle the extremely common +``= Field(...)`` like pattern used here. + +Here, we filter out attributes that have ``primary_key=True`` in their +``Field`` as well as extracting default arguments (which may be either +from a ``default`` argument to a field or specified directly as an +initializer). +""" + +# End PEP section: CRUD + + # Update takes everything but the primary key, but makes them all have # None defaults -type Update[T] = NewProtocol[ +type Update[T] = typing.NewProtocol[ *[ - Member[ - GetName[p], - GetType[p] | None, - GetQuals[p], + typing.Member[ + typing.GetName[p], + typing.GetType[p] | None, + typing.GetQuals[p], Literal[None], ] - for p in Iter[Attrs[T]] - if not IsSub[ - Literal[True], GetFieldItem[GetInit[p], Literal["primary_key"]] + for p in typing.Iter[typing.Attrs[T]] + if not typing.IsSub[ + Literal[True], + GetFieldItem[typing.GetInit[p], Literal["primary_key"]], ] ] ] @@ -117,40 +138,39 @@ class Field[T: FieldArgs](InitField[T]): # Begin PEP section: dataclass like __init__ - # Generate the Member field for __init__ for a class -type InitFnType[T] = Member[ +type InitFnType[T] = typing.Member[ Literal["__init__"], Callable[ [ - Param[Literal["self"], Self], + typing.Param[Literal["self"], Self], *[ - Param[ - GetName[p], - GetType[p], + typing.Param[ + typing.GetName[p], + typing.GetType[p], # All arguments are keyword-only # It takes a default if a default is specified in the class Literal["keyword"] - if IsSub[ - GetDefault[GetInit[p]], + if typing.IsSub[ + GetDefault[typing.GetInit[p]], Never, ] else Literal["keyword", "default"], ] - for p in Iter[Attrs[T]] + for p in typing.Iter[typing.Attrs[T]] ], ], None, ], Literal["ClassVar"], ] -type AddInit[T] = NewProtocol[ +type AddInit[T] = typing.NewProtocol[ InitFnType[T], - *[x for x in Iter[Members[T]]], + *[x for x in typing.Iter[typing.Members[T]]], ] -# End PEP section +# End PEP section: __init__ #### diff --git a/tests/test_qblike_2.py b/tests/test_qblike_2.py index 7eaa820..59db753 100644 --- a/tests/test_qblike_2.py +++ b/tests/test_qblike_2.py @@ -3,18 +3,7 @@ from typing import Literal, Unpack from typemap.type_eval import eval_call, eval_typing -from typemap.typing import ( - BaseTypedDict, - NewProtocol, - Iter, - Attrs, - IsSub, - GetType, - Member, - GetName, - GetMemberType, - GetArg, -) +from typemap import typing from . import format_helper @@ -63,18 +52,18 @@ class MultiLink[T](Link[T]): """ -def select[ModelT, K: BaseTypedDict]( +def select[ModelT, K: typing.BaseTypedDict]( typ: type[ModelT], /, **kwargs: Unpack[K], ) -> list[ - NewProtocol[ + typing.NewProtocol[ *[ - Member[ - GetName[c], - ConvertField[GetMemberType[ModelT, GetName[c]]], + typing.Member[ + typing.GetName[c], + ConvertField[typing.GetMemberType[ModelT, typing.GetName[c]]], ] - for c in Iter[Attrs[K]] + for c in typing.Iter[typing.Attrs[K]] ] ] ]: ... @@ -91,7 +80,9 @@ def select[ModelT, K: BaseTypedDict]( """ type ConvertField[T] = ( - AdjustLink[PropsOnly[PointerArg[T]], T] if IsSub[T, Link] else PointerArg[T] + AdjustLink[PropsOnly[PointerArg[T]], T] + if typing.IsSub[T, Link] + else PointerArg[T] ) """``PointerArg`` gets the type argument to ``Pointer`` or a subclass. @@ -104,25 +95,27 @@ def select[ModelT, K: BaseTypedDict]( grabs the argument to a ``Pointer``). """ -type PointerArg[T: Pointer] = GetArg[T, Pointer, Literal[0]] +type PointerArg[T: Pointer] = typing.GetArg[T, Pointer, Literal[0]] """ ``AdjustLink`` sticks a ``list`` around ``MultiLink``, using features we've discussed already. """ -type AdjustLink[Tgt, LinkTy] = list[Tgt] if IsSub[LinkTy, MultiLink] else Tgt +type AdjustLink[Tgt, LinkTy] = ( + list[Tgt] if typing.IsSub[LinkTy, MultiLink] else Tgt +) """And the final helper, ``PropsOnly[T]``, generates a new type that contains all the ``Property`` attributes of ``T``. """ type PropsOnly[T] = list[ - NewProtocol[ + typing.NewProtocol[ *[ - Member[GetName[p], PointerArg[GetType[p]]] - for p in Iter[Attrs[T]] - if IsSub[GetType[p], Property] + typing.Member[typing.GetName[p], PointerArg[typing.GetType[p]]] + for p in typing.Iter[typing.Attrs[T]] + if typing.IsSub[typing.GetType[p], Property] ] ] ] @@ -198,7 +191,7 @@ class select[...]: posts: list[tests.test_qblike_2.PropsOnly[tests.test_qblike_2.Post]] """) - res = eval_typing(GetMemberType[ret, Literal["posts"]]) + res = eval_typing(typing.GetMemberType[ret, Literal["posts"]]) tgt = res.__args__[0] # XXX: this should probably be pre-evaluated already? fmt = format_helper.format_class(tgt)