I would appreciate if the PEP could add some consideration for API docs. This PEP seems to be written entirely around type-checking and runtime use of annotations. Type-checkers and libraries using annotations at runtime are not the only users of type annotations: users of these libraries expose documentation to their users, so tools that extract API docs will have to be able to understand the syntax proposed in this PEP. I’m not too afraid to say that API docs is a big part of Python development and library maintenance.
If FastAPI/Pydantic start using the syntax proposed by this PEP, docs extraction tools will have to support it too, because users of FastAPI/Pydantic will want to document their models. These tools are not equipped for this I believe. I’ll speak for myself: Griffe is not equipped for this. Supporting what this PEP proposes requires a proper type-checker, and Griffe and other documentation extraction tools are not type-checkers.
Currently, extracting API docs from Python source is straight-forward enough: parse source with ast or similar, introspect compiled modules, iterate on the AST or in-memory objects. Just a few tricks here and there to understand concepts like descriptors or typed dicts and return good results to documentation renderers. This PEP stops that: tools would now need to evaluate type annotations to understand what’s going on and return results that documentation readers are satisfied with.
Taking the FastAPI example:
class Hero(NewSQLModel, table=True):
id: int | None = Field(default=None, primary_key=True)
name: str = Field(index=True)
age: int | None = Field(default=None, index=True)
secret_name: str = Field(hidden=True)
type HeroPublic = Public[Hero]
type HeroCreate = Create[Hero]
type HeroUpdate = Update[Hero]
I will leave aside the fact that this is better documented as an HTTP API rather than a Python API, because I guess not all users of this PEP will be HTTP API projects.
Here the developers will probably want to expose and document HeroPublic, HeroCreate and HeroUpdate since they are public symbols, used in public functions/endpoints. They will want their users to know what is in each of these classes. They have three options for that: explicitly list that in docstrings, rely on docs extraction tools, or do nothing and let readers do the work.
Option 1, manual docs:
type HeroPublic = Public[Hero]
"""Output class for getting heroes.
Has `id`, `name` and `age` set.
"""
type HeroCreate = Create[Hero]
"""Input class for creating heroes.
Must have `name`, `age` and `secret_name` set.
"""
type HeroUpdate = Update[Hero]
"""Input class for updating heroes.
Can have `name`, `age` and/or `secret_name` set.
"""
This is already very redundant and doesn’t even show field types or descriptions.
Option 2, expect the tool to handle it:
The docs tool would have to understand how Public, Create and Update work (by the way, the example could make it more clear that these types would be imported from fastapi, as in the current code block they are NameErrors).
Understanding what Public does (essentially keeping only fields that are not hidden) means going and finding it in FastAPI’s source code (which might not be available in the current docs-building environment), and understanding the computation that Public does. That’s literally impossible for a static analysis tool that is not a type-checker. The PEP says:
the implementation of Public[], Create[], and Update[] computed types is relatively complex
To me, “relatively complex” here sounds like it’s hard to write, harder to read, harder yet to type-check, and IMO simply impossible to understand without a type-checker. Docs extraction tools that are not type-checkers (I believe none of them are) are effectively excluded from the party. Unless they start using proper type-checkers internally (I expand on that after option 3).
Option 3, left as an exercise to the reader:
We could say that if Public and Hero cross-link to the symbol in FastAPI’s docs and the symbol in the current docs respectively, users can read the docs for both symbols and gather that HeroPublic is Hero without hidden fields. This could be more than enough. But this example is just one simple example. I’m sure users of this PEP will find more advanced use-cases, with more elaborate type computations, for which asking readers to do the mental work wouldn’t be decent. Developers will want docs extraction tools to ease the documentation task, to make docs easy to read and understand for their users.
Supporting this PEP in the API docs world would require cooperation between type-checkers and documentation tooling. Ideally, type-checkers would expose (Python) APIs for querying their internal data/state/model based on symbols’ canonical paths. Exactly what the PEP mentions in the FastAPI example: “given the code in mypkg and fastapi, what shape do I get with fastapi.Public[mypkg.Hero]” (or simply “what is the shape of mypkg.HeroPublic”). And the answer would be “this type, evaluated, would look something like: […]”. There would probably be many more kinds of query that would need to be supported. I realize this is quite vague, and requires quite some thinking and coordination between type-checkers and documentation tools, and that’s exactly why I think it deserves some consideration in the PEP
Otherwise docs tools are left on their own, with feature requests accumulating and no expected support from type-checkers.
The PEP mentions Typescript, and it’s true that it makes type-checking a required part of API data extraction: just like how Typedoc has to run the Typescript compiler (up to type-checking and before emitting JS) to extract API data.