Skip to content

Commit a434c99

Browse files
Add content from typeshed/CONTRIBUTING.md (#1882)
* add content from typeshed/CONTRIBUTING.md * fix build errors * address review comments * Update docs/guides/writing_stubs.rst Co-authored-by: Rebecca Chen <[email protected]> * Update docs/guides/writing_stubs.rst Co-authored-by: Rebecca Chen <[email protected]> * add Incomplete vs Any, the Any trick sections; add reference to error suppression formats section * fix ref * format and fix link * fix backticks, add Docstrings section * fix one more backtick * fix typo * fix label * Minor style and formatting fixes Removed repeated example, fixed formatting, removed stray "the". --------- Co-authored-by: Rebecca Chen <[email protected]>
1 parent 59081c2 commit a434c99

File tree

1 file changed

+188
-27
lines changed

1 file changed

+188
-27
lines changed

docs/guides/writing_stubs.rst

Lines changed: 188 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,9 @@ Liskov substitutability or detecting problematic overloads.
9393
It may be instructive to examine `typeshed <https://github.com/python/typeshed/>`__'s
9494
`setup for testing stubs <https://github.com/python/typeshed/blob/main/tests/README.md>`__.
9595

96+
To suppress type errors in stubs, use ``# type: ignore`` comments. Refer to the :ref:`type-checker-error-suppression` section of the style guide for
97+
error suppression formats specific to individual typecheckers.
98+
9699
..
97100
TODO: consider adding examples and configurations for specific type checkers
98101
@@ -113,18 +116,6 @@ Stub Content
113116
This section documents best practices on what elements to include or
114117
leave out of stub files.
115118

116-
Modules excluded from stubs
117-
---------------------------
118-
119-
Not all modules should be included in stubs.
120-
121-
It is recommended to exclude:
122-
123-
1. Implementation details, with `multiprocessing/popen_spawn_win32.py <https://github.com/python/cpython/blob/main/Lib/multiprocessing/popen_spawn_win32.py>`_ as a notable example
124-
2. Modules that are not supposed to be imported, such as ``__main__.py``
125-
3. Protected modules that start with a single ``_`` char. However, when needed protected modules can still be added (see :ref:`undocumented-objects` section below)
126-
4. Tests
127-
128119
Public Interface
129120
----------------
130121

@@ -138,7 +129,17 @@ The following should always be included:
138129
* All objects included in ``__all__`` (if present).
139130

140131
Other objects may be included if they are not prefixed with an underscore
141-
or if they are being used in practice. (See the next section.)
132+
or if they are being used in practice.
133+
134+
Modules excluded from stubs
135+
---------------------------
136+
137+
The following should not be included in stubs:
138+
139+
1. Implementation details, with `multiprocessing/popen_spawn_win32.py <https://github.com/python/cpython/blob/main/Lib/multiprocessing/popen_spawn_win32.py>`_ as a notable example
140+
2. Modules that are not supposed to be imported, such as ``__main__.py``
141+
3. Protected modules that start with a single ``_`` char. However, when needed protected modules can still be added (see :ref:`undocumented-objects` section below)
142+
4. Tests
142143

143144
.. _undocumented-objects:
144145

@@ -212,23 +213,28 @@ to use them freely to describe simple structural types.
212213
Incomplete Stubs
213214
----------------
214215

216+
When writing new stubs, it is not necessary to fully annotate all arguments,
217+
return types, and fields. Some items may be left unannotated or
218+
annotated with ``_typeshed.Incomplete`` (`documentation <https://github.com/python/typeshed/blob/main/stdlib/_typeshed/README.md>`_)::
219+
220+
from _typeshed import Incomplete
221+
222+
field: Incomplete # unannotated
223+
224+
def foo(x): ... # unannotated argument and return type
225+
226+
``_typeshed.Incomplete`` can also be used for partially known types::
227+
228+
def foo(x: Incomplete | None = None) -> list[Incomplete]: ...
229+
215230
Partial stubs can be useful, especially for larger packages, but they should
216231
follow the following guidelines:
217232

218233
* Included functions and methods should list all arguments, but the arguments
219234
can be left unannotated.
220235
* Do not use ``Any`` to mark unannotated or partially annotated values. Leave
221236
function parameters and return values unannotated. In all other cases, use
222-
``_typeshed.Incomplete``
223-
(`documentation <https://github.com/python/typeshed/blob/main/stdlib/_typeshed/README.md>`_)::
224-
225-
from _typeshed import Incomplete
226-
227-
field1: Incomplete
228-
field2: dict[str, Incomplete]
229-
230-
def foo(x): ...
231-
237+
``_typeshed.Incomplete``.
232238
* Partial classes should include a ``__getattr__()`` method marked with
233239
``_typeshed.Incomplete`` (see example below).
234240
* Partial modules (i.e. modules that are missing some or all classes,
@@ -253,6 +259,17 @@ annotated function ``bar()``::
253259

254260
def bar(x: str, y, *, z=...): ...
255261

262+
``Any`` vs. ``Incomplete``
263+
--------------------------
264+
265+
While ``Incomplete`` is a type alias of ``Any``, they serve different purposes:
266+
``Incomplete`` is a placeholder where a proper type might be substituted.
267+
It's a "to do" item and should be replaced if possible.
268+
269+
``Any`` is used when it's not possible to accurately type an item using the current
270+
type system. It should be used sparingly, as described in the :ref:`using-any`
271+
section of the style guide.
272+
256273
Attribute Access
257274
----------------
258275

@@ -475,6 +492,28 @@ and the :ref:`best-practices`. There are a few exceptions, outlined below, that
475492
different structure of stub files into account and aim to create
476493
more concise files.
477494

495+
Syntax Example
496+
--------------
497+
498+
The below is an excerpt from the types for the ``datetime`` module.
499+
500+
MAXYEAR: int
501+
MINYEAR: int
502+
503+
class date:
504+
def __new__(cls, year: SupportsIndex, month: SupportsIndex, day: SupportsIndex) -> Self: ...
505+
@classmethod
506+
def fromtimestamp(cls, timestamp: float, /) -> Self: ...
507+
@classmethod
508+
def today(cls) -> Self: ...
509+
@classmethod
510+
def fromordinal(cls, n: int, /) -> Self: ...
511+
@property
512+
def year(self) -> int: ...
513+
def replace(self, year: SupportsIndex = ..., month: SupportsIndex = ..., day: SupportsIndex = ...) -> Self: ...
514+
def ctime(self) -> str: ...
515+
def weekday(self) -> int: ...
516+
478517
Maximum Line Length
479518
-------------------
480519

@@ -506,7 +545,7 @@ No::
506545

507546
def time_func() -> None: ...
508547

509-
def date_func() -> None: ... # do no leave unnecessary empty lines
548+
def date_func() -> None: ... # do not leave unnecessary empty lines
510549

511550
def ip_func() -> None: ...
512551

@@ -563,7 +602,7 @@ Yes::
563602

564603
class Color(Enum):
565604
# An assignment with no type annotation is a convention used to indicate
566-
# an enum member.
605+
# an enum member.
567606
RED = 1
568607

569608
No::
@@ -633,6 +672,13 @@ No::
633672
...
634673
def to_int3(x: str) -> int: pass
635674

675+
Avoid invariant collection types (``list``, ``dict``) for function parameters,
676+
in favor of covariant types like ``Mapping`` or ``Sequence``.
677+
678+
Avoid union return types. See https://github.com/python/mypy/issues/1693
679+
680+
Use ``float`` instead of ``int | float`` for parameter annotations. See :pep:`484` for more details.
681+
636682
Language Features
637683
-----------------
638684

@@ -662,6 +708,14 @@ No::
662708

663709
class OtherClass: ...
664710

711+
Use variable annotations instead of type comments, even for stubs that target
712+
older versions of Python.
713+
714+
Platform-dependent APIs
715+
-----------------------
716+
717+
Use :ref:`platform checks<version-and-platform-checks>` like ``if sys.platform == 'win32'`` to denote platform-dependent APIs.
718+
665719
NamedTuple and TypedDict
666720
------------------------
667721

@@ -689,7 +743,7 @@ No::
689743
Built-in Generics
690744
-----------------
691745

692-
:pep:`585` built-in generics are supported and should be used instead
746+
:pep:`585` built-in generics (such as ``list``, ``dict``, ``tuple``, ``set``) are supported and should be used instead
693747
of the corresponding types from ``typing``::
694748

695749
from collections import defaultdict
@@ -707,8 +761,115 @@ generally possible and recommended::
707761
Unions
708762
------
709763

710-
Declaring unions with the shorthand `|` syntax is recommended and supported by
764+
Declaring unions with the shorthand ``|`` syntax is recommended and supported by
711765
all type checkers::
712766

713767
def foo(x: int | str) -> int | None: ... # recommended
714768
def foo(x: Union[int, str]) -> Optional[int]: ... # ok
769+
770+
.. _using-any:
771+
772+
Using ``Any`` and ``object``
773+
----------------------------
774+
775+
When adding type hints, avoid using the ``Any`` type when possible. Reserve
776+
the use of ``Any`` for when:
777+
778+
* the correct type cannot be expressed in the current type system; and
779+
* to avoid union returns (see above).
780+
781+
Note that ``Any`` is not the correct type to use if you want to indicate
782+
that some function can accept literally anything: in those cases use
783+
``object`` instead.
784+
785+
When using ``Any``, document the reason for using it in a comment. Ideally,
786+
document what types could be used.
787+
788+
The ``Any`` Trick
789+
-----------------
790+
791+
In cases where a function or method can return ``None``, but where forcing the
792+
user to explicitly check for ``None`` can be detrimental, use
793+
``_typeshed.MaybeNone`` (an alias to ``Any``), instead of ``None``.
794+
795+
Consider the following (simplified) signature of ``re.Match[str].group``::
796+
797+
class Match:
798+
def group(self, group: str | int, /) -> str | MaybeNone: ...
799+
800+
This avoid forcing the user to check for ``None``::
801+
802+
match = re.fullmatch(r"\d+_(.*)", some_string)
803+
assert match is not None
804+
name_group = match.group(1) # The user knows that this will never be None
805+
return name_group.uper() # This typo will be flagged by the type checker
806+
807+
In this case, the user of ``match.group()`` must be prepared to handle a ``str``,
808+
but type checkers are happy with ``if name_group is None`` checks, because we're
809+
saying it can also be something else than an ``str``.
810+
811+
This is sometimes called "the Any trick".
812+
813+
Context Managers
814+
----------------
815+
816+
When adding type annotations for context manager classes, annotate
817+
the return type of ``__exit__`` as bool only if the context manager
818+
sometimes suppresses exceptions -- if it sometimes returns ``True``
819+
at runtime. If the context manager never suppresses exceptions,
820+
have the return type be either ``None`` or ``bool | None``. If you
821+
are not sure whether exceptions are suppressed or not or if the
822+
context manager is meant to be subclassed, pick ``bool | None``.
823+
See https://github.com/python/mypy/issues/7214 for more details.
824+
825+
``__enter__`` methods and other methods that return ``self`` or ``cls(...)``
826+
should be annotated with ``typing.Self``
827+
(`example <https://github.com/python/typeshed/blob/3581846/stdlib/contextlib.pyi#L151>`_).
828+
829+
Naming
830+
------
831+
832+
Type variables and aliases you introduce purely for legibility reasons
833+
should be prefixed with an underscore to make it obvious to the reader
834+
they are not part of the stubbed API.
835+
836+
A few guidelines for protocol names below. In cases that don't fall
837+
into any of those categories, use your best judgement.
838+
839+
* Use plain names for protocols that represent a clear concept
840+
(e.g. ``Iterator``, ``Container``).
841+
* Use ``SupportsX`` for protocols that provide callable methods (e.g.
842+
``SupportsInt``, ``SupportsRead``, ``SupportsReadSeek``).
843+
* Use ``HasX`` for protocols that have readable and/or writable attributes
844+
or getter/setter methods (e.g. ``HasItems``, ``HasFileno``).
845+
846+
.. _type-checker-error-suppression:
847+
848+
Type Checker Error Suppression Formats
849+
--------------------------------------
850+
851+
* Use mypy error codes for mypy-specific ``# type: ignore`` annotations, e.g. ``# type: ignore[override]`` for Liskov Substitution Principle violations.
852+
* Use pyright error codes for pyright-specific suppressions, e.g. ``# pyright: ignore[reportGeneralTypeIssues]``.
853+
* If you need both on the same line, mypy's annotation needs to go first, e.g. ``# type: ignore[override] # pyright: ignore[reportGeneralTypeIssues]``.
854+
855+
856+
``@deprecated``
857+
---------------
858+
859+
The ``@typing_extensions.deprecated`` decorator (``@warnings.deprecated``
860+
since Python 3.13) can be used to mark deprecated functionality; see
861+
:pep:`702`.
862+
863+
Keep the deprecation message concise, but try to mention the projected
864+
version when the functionality is to be removed, and a suggested
865+
replacement.
866+
867+
Docstrings
868+
----------
869+
870+
There are several tradeoffs around including docstrings in type stubs. Consider the intended purpose
871+
of your stubs when deciding whether to include docstrings in your project's stubs.
872+
873+
* They do not affect type checking results and will be ignored by type checkers.
874+
* Docstrings can improve certain IDE functionality, such as hover info.
875+
* Duplicating docstrings between source code and stubs requires extra work to keep them in sync.

0 commit comments

Comments
 (0)