Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 45 additions & 35 deletions pep.rst
Original file line number Diff line number Diff line change
Expand Up @@ -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]]
]
]
]: ...
Expand All @@ -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.
Expand All @@ -950,26 +952,28 @@ 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]
]
]
]
Expand All @@ -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"]],
]
]
]
Expand All @@ -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]]],
]


Expand Down Expand Up @@ -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
-------------------------------------------------------------
Expand Down
5 changes: 4 additions & 1 deletion scripts/update-examples.sh
Original file line number Diff line number Diff line change
Expand Up @@ -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
124 changes: 72 additions & 52 deletions tests/test_fastapilike_2.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand All @@ -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
]


Expand All @@ -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"]],
]
]
]
Expand All @@ -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__


####
Expand Down
Loading