mirror of
https://github.com/davidhalter/typeshed.git
synced 2025-12-09 21:46:42 +08:00
Remove stub content/style section from CONTRIBUTING.md (#13332)
This commit is contained in:
333
CONTRIBUTING.md
333
CONTRIBUTING.md
@@ -240,19 +240,60 @@ 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.
|
||||
Each Python module is represented by a .pyi "stub file". This is a syntactically valid Python file, where all methods are empty and [type annotations](https://typing.readthedocs.io/en/latest/spec/annotations.html) are used to describe function signatures and variable types.
|
||||
|
||||
Python function annotations ([PEP 3107](https://www.python.org/dev/peps/pep-3107/))
|
||||
are used to describe the signature of each function or method.
|
||||
Typeshed follows the standard type system guidelines for [stub content](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#stub-content) and [coding style](https://typing.readthedocs.io/en/latest/guides/writing_stubs.html#style-guide).
|
||||
|
||||
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.
|
||||
### 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``.
|
||||
|
||||
### 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]: ...
|
||||
```
|
||||
|
||||
### 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.
|
||||
|
||||
### Docstrings
|
||||
|
||||
Typeshed stubs should not include duplicated docstrings from the source code.
|
||||
|
||||
### Auto-generating stub files
|
||||
|
||||
@@ -323,278 +364,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
|
||||
|
||||
Reference in New Issue
Block a user