Skip to content

Commit

Permalink
Address more review comments.
Browse files Browse the repository at this point in the history
Cuts redundant sections, moves things to writing_stubs, tries out a new
"these should be supported fully" and "these should be supported to at least
the described minimum" structure.
  • Loading branch information
rchen152 committed Jul 30, 2024
1 parent 46e133f commit 8a9eda8
Show file tree
Hide file tree
Showing 3 changed files with 61 additions and 217 deletions.
43 changes: 41 additions & 2 deletions docs/guides/writing_stubs.rst
Original file line number Diff line number Diff line change
Expand Up @@ -337,6 +337,45 @@ No::
DAY_FLAG: int
NIGHT_FLAG: int

Overloads
---------

All variants of overloaded functions and methods must have an ``@overload``
decorator. Do not include the implementation's final non-`@overload`-decorated
definition.

Yes::

@overload
def foo(x: str) -> str: ...
@overload
def foo(x: float) -> int: ...

No::

@overload
def foo(x: str) -> str: ...
@overload
def foo(x: float) -> int: ...
def foo(x: str | float) -> Any: ...

Decorators
----------

Include only those decorators whose effects type checkers understand, enumerated
:ref:`here <stub-decorators>`. The behavior of other decorators should instead
be incorporated into the types. For example, for the following function::

import contextlib
@contextlib.contextmanager
def f():
yield 42

the stub definition should be::

from contextlib import AbstractContextManager
def f() -> AbstractContextManager[int]: ...

Documentation or Implementation
-------------------------------

Expand Down Expand Up @@ -583,7 +622,7 @@ No::
Thing = TypedDict("Thing", {'stuff': str, 'index': int})

Built-in Generics
"""""""""""""""""
-----------------

:pep:`585` built-in generics are supported and should be used instead
of the corresponding types from ``typing``::
Expand All @@ -601,7 +640,7 @@ generally possible and recommended::
def foo(iter: Iterable[int]) -> None: ...

Unions
""""""
------

Declaring unions with the shorthand `|` syntax is recommended and supported by
all type checkers::
Expand Down
2 changes: 2 additions & 0 deletions docs/spec/directives.rst
Original file line number Diff line number Diff line change
Expand Up @@ -128,6 +128,8 @@ and return type annotations and treat the function as if it were unannotated.
The behavior for the ``no_type_check`` decorator when applied to a class is
left undefined by the typing spec at this time.

.. _`version-and-platform-checks`:

Version and platform checking
-----------------------------

Expand Down
233 changes: 18 additions & 215 deletions docs/spec/distributing.rst
Original file line number Diff line number Diff line change
Expand Up @@ -69,45 +69,22 @@ This section lists constructs that type checkers should accept in stub files. If
a construct is marked as "unspecified", type checkers may handle it as they best
see fit or report an error.

Typing Features
"""""""""""""""

Type checkers should support all features from the ``typing`` module of the
latest released Python version.

Comments
""""""""

Standard Python comments should be accepted everywhere Python syntax allows
them. Type declaration (``# type: X``) and error suppression (``type: ignore``)
comments should be supported.

Imports
"""""""

Stub files distinguish between imports that are re-exported and those
that are only used internally. See :ref:`import-conventions`.

Type aliases can be used to re-export an import under a different name::

from foo import bar as _bar
new_bar = _bar # "bar" gets re-exported with the name "new_bar"

Stubs support customizing star import semantics by defining a module-level
variable called ``__all__``. In stubs, this must be a string list literal.
Other types are not supported. Neither is the dynamic creation of this
variable (for example by concatenation).

When ``__all__`` is defined, exactly those names specified in ``__all__`` are
imported::

__all__ = ['public_attr', '_private_looking_public_attr']

public_attr: int
_private_looking_public_attr: int
private_attr: int

Type checkers support cyclic imports in stub files.
Type checkers should fully support all features from the ``typing`` module of
the latest released Python version, as well as these constructs:

* Comments, including type declaration (``# type: X``) and error suppression
(``type: ignore``) comments.
* Import statements, including the standard :ref:`import-conventions` and cyclic
imports.
* Module-level type aliases (e.g., ``X: TypeAlias = int``,
``Y: TypeAlias = dict[str, _V]``).
* Regular aliases (e.g., ``function_alias = some_function``) at both the module-
and class-level.
* Simple version and platform checks, as described
:ref:`here <version-and-platform-checks>`.

The constructs in the following subsections may be supported in a more limited
fashion, as described below.

Module Level Attributes
"""""""""""""""""""""""
Expand Down Expand Up @@ -162,41 +139,12 @@ Yes::
doStuff = do_stuff
class Inner: ...

More complex statements don't need to be supported.

The type of generic classes can be narrowed by annotating the ``self``
argument of the ``__init__`` method::

class Foo(Generic[_T]):
@overload
def __init__(self: Foo[str], type: Literal["s"]) -> None: ...
@overload
def __init__(self: Foo[int], type: Literal["i"]) -> None: ...
@overload
def __init__(self, type: str) -> None: ...

The class must match the class in which it is declared. Using other classes,
including sub or super classes, will not work. In addition, the ``self``
annotation cannot contain type variables.

Functions and Methods
"""""""""""""""""""""

Function and method definition syntax follows general Python syntax.
For backwards compatibility, positional-only parameters can also be marked by
prefixing their name with two underscores (but not suffixing it with two
underscores)::

# x is positional-only
# y can be used positionally or as keyword argument
# z is keyword-only
def foo(x, /, y, *, z): ... # recommended
def foo(__x, y, *, z): ... # backwards compatible syntax

If an argument or return type is unannotated, per :pep:`484` its
type is assumed to be ``Any``. It is preferred to leave unknown
types unannotated rather than explicitly marking them as ``Any``, as some
type checkers can optionally warn about unannotated arguments.
type is assumed to be ``Any``.

If an argument has a literal or constant default value, it must match the implementation
and the type of the argument (if specified) must match the default value.
Expand All @@ -209,84 +157,14 @@ Alternatively, ``...`` can be used in place of any default value::
# The following default values are invalid and the types are unspecified.
def invalid(a: int = "", b: Foo = Foo()): ...

For a class ``C``, the type of the first argument to a classmethod is
assumed to be ``type[C]``, if unannotated. For other non-static methods,
its type is assumed to be ``C``::

class Foo:
def do_things(self): ... # self has type Foo
@classmethod
def create_it(cls): ... # cls has type type[Foo]
@staticmethod
def utility(x): ... # x has type Any

But::

_T = TypeVar("_T")

class Foo:
def do_things(self: _T) -> _T: ... # self has type _T
@classmethod
def create_it(cls: _T) -> _T: ... # cls has type _T

Using a function or method body other than the ellipsis literal is currently
unspecified. Stub authors may experiment with other bodies, but it is up to
individual type checkers how to interpret them::

def foo(): ... # compatible
def bar(): pass # behavior undefined

All variants of overloaded functions and methods must have an ``@overload``
decorator::

@overload
def foo(x: str) -> str: ...
@overload
def foo(x: float) -> int: ...

The following (which would be used in the implementation) is wrong in stubs::

@overload
def foo(x: str) -> str: ...
@overload
def foo(x: float) -> int: ...
def foo(x: str | float) -> Any: ...

Aliases and NewType
"""""""""""""""""""

Type checkers should accept module-level type aliases, optionally using
``TypeAlias`` (:pep:`613`), e.g.::

_IntList = list[int]
_StrList: TypeAlias = list[str]

Type checkers should also accept regular module-level or class-level aliases,
e.g.::

def a() -> None: ...
b = a

class C:
def f(self) -> int: ...
g = f

A type alias may contain type variables. As per :pep:`484`,
all type variables must be substituted when the alias is used::

_K = TypeVar("_K")
_V = TypeVar("_V")
_MyMap: TypeAlias = dict[str, dict[_K, _V]]

# either concrete types or other type variables can be substituted
def f(x: _MyMap[str, _V]) -> _V: ...
# explicitly substitute in Any rather than using a bare alias
def g(x: _MyMap[Any, Any]) -> Any: ...

Otherwise, type variables in aliases follow the same rules as type variables in
generic class definitions.

``typing.NewType`` is also supported in stubs.
.. _stub-decorators:

Decorators
""""""""""
Expand All @@ -301,81 +179,6 @@ in the ``typing`` module, plus these additional ones:
* ``dataclasses.dataclass``
* functions decorated with ``@typing.dataclass_transform``

The behavior of other decorators should instead be incorporated into the types.
For example, for the following function::

import contextlib
@contextlib.contextmanager
def f():
yield 42

the stub definition should be::

from contextlib import AbstractContextManager
def f() -> AbstractContextManager[int]: ...

Version and Platform Checks
"""""""""""""""""""""""""""

Stub files for libraries that support multiple Python versions can use version
checks to supply version-specific type hints. Stubs for different Python
versions should still conform to the most recent supported Python version's
syntax, as explained in the :ref:`stub-file-syntax` section above.

Version checks are if-statements that use ``sys.version_info`` to determine the
current Python version. Version checks should only check against the ``major`` and
``minor`` parts of ``sys.version_info``. Type checkers are only required to
support the tuple-based version check syntax::

if sys.version_info >= (3,):
# Python 3-specific type hints. This tuple-based syntax is recommended.
else:
# Python 2-specific type hints.

if sys.version_info >= (3, 5):
# Specific minor version features can be easily checked with tuples.

if sys.version_info < (3,):
# This is only necessary when a feature has no Python 3 equivalent.

Stubs should avoid checking against ``sys.version_info.major`` directly and
should not use comparison operators other than ``<`` and ``>=``.

No::

if sys.version_info.major >= 3:
# Semantically the same as the first tuple check.

if sys.version_info[0] >= 3:
# This is also the same.

if sys.version_info <= (2, 7):
# This does not work because e.g. (2, 7, 1) > (2, 7).

Some stubs also may need to specify type hints for different platforms. Platform
checks must be equality comparisons between ``sys.platform`` and the name of a
platform as a string literal:

Yes::

if sys.platform == 'win32':
# Windows-specific type hints.
else:
# Posix-specific type hints.

No::

if sys.platform.startswith('linux'):
# Not necessary since Python 3.3.

if sys.platform in ['linux', 'cygwin', 'darwin']:
# Only '==' or '!=' should be used in platform checks.

Version and platform comparisons can be chained using the ``and`` and ``or``
operators::

if sys.platform == 'linux' and (sys.version_info < (3,) or sys,version_info >= (3, 7)): ...

The Typeshed Project
^^^^^^^^^^^^^^^^^^^^

Expand Down

0 comments on commit 8a9eda8

Please sign in to comment.