Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Remove stub content/style section from CONTRIBUTING.md #13332

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
286 changes: 4 additions & 282 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down Expand Up @@ -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"
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think the Any trick is a good example of a "for more info, see also" kind of thing. Most contributors won't need to know about it, so it is IMO a good idea to link to it from CONTRIBUTING.md, but not explain it here.


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
Expand Down