@@ -33,13 +33,17 @@ system, some libraries come with custom mypy plugins (though then
3333other typecheckers suffer). The case of dataclass-like transformations
3434was considered common enough that a special-case
3535``@dataclass_transform `` decorator was added specifically to cover
36- that case (:pep: `681 `).
36+ that case (:pep: `681 `). The problem with this approach is that many
37+ typecheckers do not (and will not) have a plugin API, so having
38+ consistent typechecking across IDEs, CI, and tooling is not
39+ achievable.
3740
38- We are proposing to add type manipulation facilities to the type
39- system that are more capable of keeping up with dynamic Python
40- code.
41+ Given the significant mismatch between the expressiveness of
42+ the Python language and its type system, we propose to bridge
43+ this gap by adding type manipulation facilities that
44+ are better able to keep up with dynamic Python code.
4145
42- There does seem to be demand for this. In the analysis of the
46+ There is demand for this. In the analysis of the
4347responses to Meta's 2025 Typed Python Survey [#survey ]_, the first
4448entry on the list of "Most Requested Features" was:
4549
@@ -50,13 +54,15 @@ entry on the list of "Most Requested Features" was:
5054 dictionaries/dicts (e.g., more flexible TypedDict or anonymous types).
5155
5256We will present a few examples of problems that could be solved with
53- more powerful type manipulation.
57+ more powerful type manipulation, but the proposal is generic and will
58+ unlock many more use cases.
5459
5560Prisma-style ORMs
5661-----------------
5762
5863`Prisma <#prisma _>`_, a popular ORM for TypeScript, allows writing
59- queries like (adapted from `this example <#prisma-example _>`_)::
64+ database queries in TypeScript like
65+ (adapted from `this example <#prisma-example _>`_)::
6066
6167 const user = await prisma.user.findMany({
6268 select: {
@@ -66,7 +72,7 @@ queries like (adapted from `this example <#prisma-example_>`_)::
6672 },
6773 });
6874
69- for which the inferred type will be something like::
75+ for which the inferred type of `` user `` will be something like::
7076
7177 {
7278 email: string;
@@ -79,15 +85,16 @@ for which the inferred type will be something like::
7985 }[];
8086 }[]
8187
82- Here, the output type is a combination of both existing information
83- about the type of ``prisma.user `` and the type of the argument to
84- `` findMany ``. It returns an array of objects containing the properties
85- of ``user `` that were requested; one of the requested elements,
86- `` posts ``, is a "relation" referencing another model; it has * all * of
87- its properties fetched but not its relations .
88+ Here, the output type is an intersection of the existing information
89+ about the type of ``prisma.user `` (a TypeScript type reflected from
90+ the database `` user `` table) and the type of the argument to
91+ the ``findMany `` method. It returns an array of objects containing
92+ the properties of `` user `` that were explicitly requested;
93+ where `` posts `` is a "relation" referencing another type .
8894
89- We would like to be able to do something similar in Python, perhaps
90- with a schema defined like::
95+ We would like to be able to do something similar in Python. Suppose
96+ our database schema is defined in Python (or code-generated from
97+ the database) like::
9198
9299 class Comment:
93100 id: Property[int]
@@ -112,11 +119,7 @@ with a schema defined like::
112119 email: Property[str]
113120 posts: Link[Post]
114121
115- (In Prisma, a code generator generates type definitions based on a
116- prisma schema in its own custom format; you could imagine something
117- similar here, or that the definitions were hand-written)
118-
119- and a call like::
122+ So, in Python code, a call like::
120123
121124 db.select(
122125 User,
@@ -125,7 +128,7 @@ and a call like::
125128 posts=True,
126129 )
127130
128- which would have return type ``list[<User>] `` where::
131+ would have a dynamically computed return type ``list[<User>] `` where::
129132
130133 class <User>:
131134 name: str
@@ -137,6 +140,8 @@ which would have return type ``list[<User>]`` where::
137140 title: str
138141 content: str
139142
143+ Even further, an IDE could offer code completion for
144+ all arguments of the ``db.select() `` call, recursively.
140145
141146(Example code for implementing this :ref: `below <qb-impl >`.)
142147
@@ -182,13 +187,14 @@ the types, using `Pydantic <#pydantic_>`_).
182187Despite the multiple types and duplication here, mechanical rules
183188could be written for deriving these types:
184189
185- * Public should include all non-"hidden" fields, and the primary key
190+ * The " Public" version should include all non-"hidden" fields, and the primary key
186191 should be made non-optional
187- * Create should include all fields except the primary key
188- * Update should include all fields except the primary key, but they
192+ * " Create" should include all fields except the primary key
193+ * " Update" should include all fields except the primary key, but they
189194 should all be made optional and given a default value
190195
191- With the definition of appropriate helpers, this proposal would allow writing::
196+ With the definition of appropriate helpers inside FastAPI framework,
197+ this proposal would allow its users to write::
192198
193199 class Hero(NewSQLModel, table=True):
194200 id: int | None = Field(default=None, primary_key=True)
@@ -222,13 +228,12 @@ Those types, evaluated, would look something like::
222228 secret_name: str | None = None
223229
224230
231+ While the implementation of ``Public[] ``, ``Create[] ``, and ``Update[] ``
232+ computed types is relatively complex, they perform quite mechanical
233+ operations and if included in the framework library they would significantly
234+ reduce the boilerplate the users of FastAPI have to maintain.
225235
226- While the implementation of ``Public ``, ``Create ``, and ``Update `` are
227- certainly more complex than duplicating code would be, they perform
228- quite mechanical operations and could be included in the framework
229- library.
230-
231- A notable feature of this use case is that it **depends on performing
236+ A notable feature of this use case is that it **requires performing
232237runtime evaluation of the type annotations **. FastAPI uses the
233238Pydantic models to validate and convert to/from JSON for both input
234239and output from endpoints.
@@ -279,8 +284,6 @@ This proposal will cover those cases.
279284Specification of Some Prerequisites
280285===================================
281286
282- (Some content is still in `spec-draft.rst <spec-draft.rst >`_).
283-
284287We have two subproposals that are necessary to get mileage out of the
285288main part of this proposal.
286289
@@ -337,10 +340,9 @@ read-only items are invariant.)
337340This is potentially moderately useful on its own but is being done to
338341support processing ``**kwargs `` with type level computation.
339342
340- ---
341343
342- Extended Callables, take 2
343- --------------------------
344+ Extended Callables
345+ ------------------
344346
345347We introduce a new extended callable proposal for expressing arbitrarily
346348complex callable types. The goal here is not really to produce a new
@@ -411,9 +413,9 @@ or, using the type abbreviations we provide::
411413
412414(Rationale discussed :ref: `below <callable-rationale >`.)
413415
414- TODO: Should the extended argument list be wrapped in a
415- ``typing.Parameters[*Params] `` type (that will also kind of serve as a
416- bound for ``ParamSpec ``)?
416+ .. TODO: Should the extended argument list be wrapped in a
417+ .. ``typing.Parameters[*Params]`` type (that will also kind of serve as a
418+ .. bound for ``ParamSpec``)?
417419
418420
419421 Specification
@@ -427,14 +429,9 @@ forms of valid types, but much of the power comes from type level
427429Grammar specification of the extensions to the type language
428430------------------------------------------------------------
429431
430- Note first that no changes to the **Python ** grammar are being
431- proposed, only to the grammar of what Python expressions are
432- considered as valid types.
432+ No changes to the **Python ** grammar are being proposed, only
433+ to the grammar of what Python expressions are considered as valid types.
433434
434- (It's also slightly imprecise to call this a grammar:
435- ``<bool-operator> `` refers to any of the names defined in the
436- :ref: `Boolean Operators <boolean-ops >` section, which might be
437- imported qualified or with some other name)
438435
439436::
440437
@@ -476,9 +473,14 @@ imported qualified or with some other name)
476473 <type-for-if> =
477474 if <type-bool>
478475
476+ Where:
479477
480- (``<type-bool-for> `` is identical to ``<type-for> `` except that the
481- result type is a ``<type-bool> `` instead of a ``<type> ``.)
478+ * ``<bool-operator> `` refers to any of the names defined in the
479+ :ref: `Boolean Operators <boolean-ops >` section, whether used directly,
480+ qualified, or under another name.
481+
482+ * ``<type-bool-for> `` is identical to ``<type-for> `` except that the
483+ result type is a ``<type-bool> `` instead of a ``<type> ``.
482484
483485There are three and a half core syntactic features introduced: type booleans,
484486conditional types, unpacked comprehension types, and type member access.
@@ -496,13 +498,13 @@ Operators <boolean-ops>`, defined below, potentially combined with
496498``any ``, the argument is a comprehension of type booleans, evaluated
497499in the same way as the :ref: `unpacked comprehensions <unpacked >`.
498500
499- When evaluated, they will evaluate to `` Literal[True] `` or
500- ``Literal[False] ``.
501+ When evaluated in type annotation context , they will evaluate to
502+ ``Literal[True] `` or `` Literal[ False] ``.
501503
502- (We want to restrict what operators may be used in a conditional
504+ We restrict what operators may be used in a conditional
503505so that at runtime, we can have those operators produce "type" values
504506with appropriate behavior, without needing to change the behavior of
505- existing ``Literal[False] `` values and the like.)
507+ existing ``Literal[False] `` values and the like.
506508
507509
508510Conditional types
@@ -578,14 +580,12 @@ Basic operators
578580
579581 Negative indexes work in the usual way.
580582
581- N.B: Runtime evaluation will only be able to support proper classes
583+ Note that runtime evaluation will only be able to support proper classes
582584 as ``Base ``, *not * protocols. So, for example, ``GetArg[Ty,
583- Iterable, 0] `` to get the type of something iterable will need to
584- fail in a runtime evaluator. We should be able to allow it
585- statically though.
585+ Iterable, 0] `` to get the type of something iterable will
586+ fail in the runtime evaluator.
586587
587- Special forms unfortunately
588- require some special handling: the arguments list of a ``Callable ``
588+ Special forms require special handling: the arguments list of a ``Callable ``
589589 will be packed in a tuple, and a ``... `` will become
590590 ``SpecialFormEllipsis ``.
591591
@@ -681,11 +681,11 @@ Object creation
681681 something of a new concept.)
682682
683683* ``NewTypedDict[*Ps: Member] `` - Creates a new ``TypedDict `` with
684- items specified by the ``Member `` arguments. TODO: Do we want a way
685- to specify ``extra_items ``?
686-
684+ items specified by the ``Member `` arguments.
687685
688- N.B: Currently we aren't proposing any way to create nominal classes
686+ .. TODO: Do we want a way to specify ``extra_items``?
687+
688+ Note that we are not currently proposing any way to create *nominal * classes
689689or any way to make new *generic * types.
690690
691691
@@ -696,7 +696,7 @@ InitField
696696
697697We want to be able to support transforming types based on
698698dataclasses/attrs/pydantic style field descriptors. In order to do
699- that, we need to be able to consume things like calls to ``Field ``.
699+ that, we need to be able to consume operations like calls to ``Field ``.
700700
701701Our strategy for this is to introduce a new type
702702``InitField[KwargDict] `` that collects arguments defined by a
@@ -728,14 +728,14 @@ that would be made available as the ``Init`` field of the ``Member``.
728728Annotated
729729'''''''''
730730
731- TODO: This could maybe be dropped if it doesn't seem implementable?
731+ .. TODO: This could maybe be dropped if it doesn't seem implementable?
732732
733733 Libraries like FastAPI use annotations heavily, and we would like to
734734be able to use annotations to drive type-level computation decision
735735making.
736736
737- We understand that this may be controversial, as currently `` Annotated ``
738- may be fully ignored by typecheckers. The operations proposed are:
737+ Note that currently `` Annotated `` may be fully ignored by typecheckers.
738+ The operations proposed are:
739739
740740* ``GetAnnotations[T] `` - Fetch the annotations of a potentially
741741 Annotated type, as Literals. Examples::
@@ -762,8 +762,7 @@ Callable format discussed above.
762762The names, type, and qualifiers share associated type names with
763763``Member `` (``.name ``, ``.type ``, and ``.quals ``).
764764
765- TODO: Should we make ``.init `` be literal types of default parameter
766- values too?
765+ .. TODO: Should we make ``.init`` be literal types of default parameter values too?
767766
768767 .. _generic-callable :
769768
@@ -776,16 +775,13 @@ Generic Callable
776775 variables in ``Vs `` via the bound variables in ``<vs> ``.
777776
778777For now, we restrict the use of ``GenericCallable `` to
779- the type argument of ``Member `` (that is , to disallow its use for
778+ the type argument of ``Member ``, to disallow its use for
780779locals, parameter types, return types, nested inside other types,
781- etc) .
780+ etc. Rationale discussed :ref: ` below < generic-callable-rationale >` .
782781
783- (This is a little unsatisfying. Rationale discussed :ref: `below
784- <generic-callable-rationale>`.)
785-
786- TODO: Decide if we have any mechanisms to inspect/destruct
787- ``GenericCallable ``. Maybe can fetch the variable information and
788- maybe can apply it to concrete types?
782+ .. TODO: Decide if we have any mechanisms to inspect/destruct
783+ .. ``GenericCallable``. Maybe can fetch the variable information and
784+ .. maybe can apply it to concrete types?
789785
790786 Overloaded function types
791787'''''''''''''''''''''''''
@@ -797,8 +793,8 @@ String manipulation
797793'''''''''''''''''''
798794
799795String manipulation operations for string ``Literal `` types.
800- We can put more in, but this is what typescript has.
801- ``Slice `` and ``Concat `` are a poor man's literal template.
796+
797+ ``Slice `` and ``Concat `` allow for basic literal template-like manipulation .
802798We can actually implement the case functions in terms of them and a
803799bunch of conditionals, but shouldn't (especially if we want it to work
804800for all unicode!).
@@ -1151,9 +1147,9 @@ I am proposing a fully new extended callable syntax because:
11511147 2. They use parentheses and not brackets, which really goes against
11521148 the philosophy here.
11531149 3. We can make an API that more nicely matches what we are going to
1154- do for inspecting members (We could introduce extended callables that
1150+ do for inspecting members (we could introduce extended callables that
11551151 closely mimic the ``mypy_extensions `` version though, if something new
1156- is a non starter)
1152+ is a non- starter)
11571153
11581154TODO: Currently I made the qualifiers be short strings, for code brevity
11591155when using them, but an alternate approach would be to mirror
@@ -1600,10 +1596,10 @@ situation at lower cost.
16001596Make the type-level operations more "strictly-typed"
16011597----------------------------------------------------
16021598
1603- This proposal is less "strictly-typed" than typescript
1599+ This proposal is less "strictly-typed" than TypeScript
16041600(strictly-kinded, maybe?).
16051601
1606- Typescript has better typechecking at the alias definition site:
1602+ TypeScript has better typechecking at the alias definition site:
16071603For ``P[K] ``, ``K `` needs to have ``keyof P ``...
16081604
16091605We could do potentially better but it would require more machinery.
0 commit comments