diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 05d3fd5735eb..3dc6b3de94d4 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -240,19 +240,13 @@ recommend starting by opening an issue laying out what you want to do. That lets a conversation happen early in case other contributors disagree with what you'd like to do or have ideas that will help you do it. -### Format +### Stub Content and Style -Each Python module is represented by a `.pyi` "stub file". This is a -syntactically valid Python file, although it usually cannot be run by -Python (since forward references don't require string quotes). All -the methods are empty. +Refer to the documentation on [Writing and Maintaining Stubs](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html) for information on how to write stubs, including guidelines for content and coding style. -Python function annotations ([PEP 3107](https://www.python.org/dev/peps/pep-3107/)) -are used to describe the signature of each function or method. +### Docstrings -See [PEP 484](http://www.python.org/dev/peps/pep-0484/) for the exact -syntax of the stub files and [below](#stub-file-coding-style) for the -coding style used in typeshed. +Typeshed stubs should not include duplicated docstrings from the source code. ### Auto-generating stub files @@ -323,278 +317,6 @@ do not happen, so a parameter that accepts either `bytes` or Often one of the aliases from `_typeshed`, such as `_typeshed.ReadableBuffer`, can be used instead. -### What to include - -Stubs should include the complete interface (classes, functions, -constants, etc.) of the module they cover, but it is not always -clear exactly what is part of the interface. - -The following should always be included: -- All objects listed in the module's documentation. -- All objects included in ``__all__`` (if present). - -Other objects may be included if they are being used in practice -or if they are not prefixed with an underscore. This means -that typeshed will generally accept contributions that add missing -objects, even if they are undocumented. Undocumented objects should -be marked with a comment of the form ``# undocumented``. -Example: - -```python -def list2cmdline(seq: Sequence[str]) -> str: ... # undocumented -``` - -We accept such undocumented objects because omitting objects can confuse -users. Users who see an error like "module X has no attribute Y" will -not know whether the error appeared because their code had a bug or -because the stub is wrong. Although it may also be helpful for a type -checker to point out usage of private objects, we usually prefer false -negatives (no errors for wrong code) over false positives (type errors -for correct code). In addition, even for private objects a type checker -can be helpful in pointing out that an incorrect type was used. - -### What to do when a project's documentation and implementation disagree - -Type stubs are meant to be external type annotations for a given -library. While they are useful documentation in its own merit, they -augment the project's concrete implementation, not the project's -documentation. Whenever you find them disagreeing, model the type -information after the actual implementation and file an issue on the -project's tracker to fix their documentation. - -### Stub versioning - -You can use checks -like `if sys.version_info >= (3, 12):` to denote new functionality introduced -in a given Python version or solve type differences. When doing so, only use -two-tuples. Because of this, if a given functionality was -introduced in, say, Python 3.11.4, your check: - -* should be expressed as `if sys.version_info >= (3, 11):` -* should NOT be expressed as `if sys.version_info >= (3, 11, 4):` -* should NOT be expressed as `if sys.version_info >= (3, 12):` - -When your stub contains if statements for different Python versions, -always put the code for the most recent Python version first. - -## Stub file coding style - -### Syntax example - -The below is an excerpt from the types for the `datetime` module. - -```python -MAXYEAR: int -MINYEAR: int - -class date: - def __new__(cls, year: SupportsIndex, month: SupportsIndex, day: SupportsIndex) -> Self: ... - @classmethod - def fromtimestamp(cls, timestamp: float, /) -> Self: ... - @classmethod - def today(cls) -> Self: ... - @classmethod - def fromordinal(cls, n: int, /) -> Self: ... - @property - def year(self) -> int: ... - def replace(self, year: SupportsIndex = ..., month: SupportsIndex = ..., day: SupportsIndex = ...) -> Self: ... - def ctime(self) -> str: ... - def weekday(self) -> int: ... -``` - -### Conventions - -Stub files are *like* Python files and you should generally expect them -to look the same. Your tools should be able to successfully treat them -as regular Python files. However, there are a few important differences -you should know about. - -Style conventions for stub files are different from PEP 8. The general -rule is that they should be as concise as possible. Specifically: -* all function bodies should be empty; -* prefer ``...`` over ``pass``; -* prefer ``...`` on the same line as the class/function signature; -* avoid vertical whitespace between consecutive module-level functions, - names, or methods and fields within a single class; -* use a single blank line between top-level class definitions, or none - if the classes are very small; -* do not use docstrings; -* use variable annotations instead of type comments, even for stubs - that target older versions of Python. - -The primary users for stub files are type checkers, -so stub files should generally only contain information necessary for the type -checker, and leave out unnecessary detail. -However, stubs also have other use cases: -* stub files are often used as a data source for IDEs, - which will often use the signature in a stub to provide information - on functions or classes in tooltip messages. -* stubs can serve as useful documentation to human readers, - as well as machine-readable sources of data. - -As such, we recommend that default values be retained for "simple" default values -(e.g. bools, ints, bytes, strings, and floats are all permitted). -Use `= ...` for more complex default values, -rather than trying to exactly reproduce the default at runtime. - -Some further tips for good type hints: -* for arguments that default to `None`, use `Foo | None` explicitly for the type annotation; -* use `float` instead of `int | float` for parameter annotations - (see [PEP 484](https://peps.python.org/pep-0484/#the-numeric-tower) for motivation). -* use built-in generics (`list`, `dict`, `tuple`, `set`), instead - of importing them from `typing`. -* use `X | Y` instead of `Union[X, Y]` and `X | None`, instead of - `Optional[X]`; -* import collections (`Mapping`, `Iterable`, etc.) - from `collections.abc` instead of `typing`; -* avoid invariant collection types (`list`, `dict`) for function - parameters, in favor of covariant types like `Mapping` or `Sequence`; -* avoid union return types: https://github.com/python/mypy/issues/1693; -* use platform checks like `if sys.platform == 'win32'` to denote - platform-dependent APIs; -* use mypy error codes for mypy-specific `# type: ignore` annotations, - e.g. `# type: ignore[override]` for Liskov Substitution Principle violations. -* use pyright error codes for pyright-specific suppressions, - e.g. `# pyright: ignore[reportGeneralTypeIssues]`. - - pyright is configured to discard `# type: ignore` annotations. - If you need both on the same line, mypy's annotation needs to go first, - e.g. `# type: ignore[override] # pyright: ignore[reportGeneralTypeIssues]`. - -Imports in stubs are considered private (not part of the exported API) -unless: -* they use the form ``from library import name as name`` (sic, using - explicit ``as`` even if the name stays the same); or -* they use the form ``from library import *`` which means all names - from that library are exported. - -Stub files support forward references natively. In other words, the -order of class declarations and type aliases does not matter in -a stub file. You can also use the name of the class within its own -body. Focus on making your stubs clear to the reader. Avoid using -string literals in type annotations. - -### Using `Any` and `object` - -When adding type hints, avoid using the `Any` type when possible. Reserve -the use of `Any` for when: -* the correct type cannot be expressed in the current type system; and -* to avoid union returns (see above). - -Note that `Any` is not the correct type to use if you want to indicate -that some function can accept literally anything: in those cases use -`object` instead. - -When using `Any`, document the reason for using it in a comment. Ideally, -document what types could be used. The `_typeshed` module also provides -a few aliases to `Any` — like `Incomplete` and `MaybeNone` (see below) — -that should be used instead of `Any` in appropriate situations and double -as documentation. - -### Context managers - -When adding type annotations for context manager classes, annotate -the return type of `__exit__` as bool only if the context manager -sometimes suppresses exceptions -- if it sometimes returns `True` -at runtime. If the context manager never suppresses exceptions, -have the return type be either `None` or `bool | None`. If you -are not sure whether exceptions are suppressed or not or if the -context manager is meant to be subclassed, pick `bool | None`. -See https://github.com/python/mypy/issues/7214 for more details. - -`__enter__` methods and other methods that return instances of the -current class should be annotated with `typing_extensions.Self` -([example](https://github.com/python/typeshed/blob/3581846/stdlib/contextlib.pyi#L151)). - -### Naming - -Type variables and aliases you introduce purely for legibility reasons -should be prefixed with an underscore to make it obvious to the reader -they are not part of the stubbed API. - -A few guidelines for protocol names below. In cases that don't fall -into any of those categories, use your best judgement. - -* Use plain names for protocols that represent a clear concept - (e.g. `Iterator`, `Container`). -* Use `SupportsX` for protocols that provide callable methods (e.g. - `SupportsInt`, `SupportsRead`, `SupportsReadSeek`). -* Use `HasX` for protocols that have readable and/or writable attributes - or getter/setter methods (e.g. `HasItems`, `HasFileno`). - -### `@deprecated` - -Typeshed uses the `@typing_extensions.deprecated` decorator -(`@warnings.deprecated` since Python 3.13) to mark deprecated -functionality; see [PEP 702](https://peps.python.org/pep-0702/). - -A few guidelines for how to use it: - -* In the standard library, apply the decorator only in Python versions - where an appropriate replacement for the deprecated functionality - exists. If in doubt, apply the decorator only on versions where the - functionality has been explicitly deprecated, either through runtime - warnings or in the documentation. Use `if sys.version_info` checks to - apply the decorator only to some versions. -* Keep the deprecation message concise, but try to mention the projected - version when the functionality is to be removed, and a suggested - replacement. - -### Incomplete annotations - -When submitting new stubs, it is not necessary to annotate all arguments, -return types, and fields. Such items should either be left unannotated or -use `_typeshed.Incomplete` if this is not possible: - -```python -from _typeshed import Incomplete - -field: Incomplete # unannotated - -def foo(x): ... # unannotated argument and return type -``` - -`Incomplete` can also be used for partially known types: - -```python -def foo(x: Incomplete | None = None) -> list[Incomplete]: ... -``` - -### `Any` vs. `Incomplete` - -While `Incomplete` is a type alias of `Any`, they serve difference purposes: -`Incomplete` is a placeholder where a proper type might be substituted. -It's a "to do" item and should be replaced if possible. `Any` is used when -it's not possible to accurately type an item using the current type system. -It should be used sparingly. - -### "The `Any` trick" - -In cases where a function or method can return `None`, but where forcing the -user to explicitly check for `None` can be detrimental, use -`_typeshed.MaybeNone` (an alias to `Any`), instead of `None`. - -Consider the following (simplified) signature of `re.Match[str].group`: - -```python -class Match: - def group(self, group: str | int, /) -> str | MaybeNone: ... -``` - -This avoid forcing the user to check for `None`: - -```python -match = re.fullmatch(r"\d+_(.*)", some_string) -assert match is not None -name_group = match.group(1) # The user knows that this will never be None -return name_group.uper() # This typo will be flagged by the type checker -``` - -In this case, the user of `match.group()` must be prepared to handle a `str`, -but type checkers are happy with `if name_group is None` checks, because we're -saying it can also be something else than an `str`. - -This is sometimes called "the Any trick". - ## Submitting Changes Even more excellent than a good bug report is a fix for a bug, or the