python / python/typing

Augmenting third-party packages

Ouverte
#1,936 4 commentaires 1 réaction 0 personnes assignées Voir sur GitHub

Personne n'a encore pris cette issue.

topic: feature
Langage dominant
Python
Étoiles
1.8k
Forks
302
Merge moyen
23 h
PR mergées (30 j)
8

Description

Motivation

Some framework packages (like pytest, polars, xarray, …) have APIs that allow plugin packages to define attributes on their classes/singletons using some registration function, e.g.:

import pytest

def pytest_configure(config: pytest.Config) -> None:
    config.addinivalue_line("markers", "mychoice(select, skip): choose which stuff to test")

@pytest.mark.mychoice(x=1)  # I want to make this into a type error by defining the signature somewhere
def test_thing(): ...

These plugins should have a way to specify the type of the new attribute. In our example, pytest itself has this definition for the type of the pytest.mark object:

@final
class MarkGenerator:
    if TYPE_CHECKING:
        skip: _SkipMarkDecorator
        skipif: _SkipifMarkDecorator
        xfail: _XfailMarkDecorator
        parametrize: _ParametrizeMarkDecorator
        usefixtures: _UsefixturesMarkDecorator
        filterwarnings: _FilterwarningsMarkDecorator

    # untyped marks:
    def __getattr__(self, name: str) -> MarkDecorator: ...

which allows its own defined marks to be typed:

@pytest.mark.skipif(x=None)  # this *is* a type error
def test_something(): ...

A plugin needs to have a way to add a new typed attribute to MarkGenerator.

Design considerations

The current way of shipping types has no good way of having potentially multiple stubs that can be merged into one: even if it’s possible to ship pytest/__init__.pyi in one plugin and have it merged with the actual pytest package, only one plugin could do that, and there would be no indication that this .pyi is intended to be an augmentation instead of a replacement for all of pytest’s types.

So we’d need a new way to locate augmentation stubs, I think.

As for how these stubs look like, I think typing.Protocol could do a good job:
A Protocol in an augmentation stub could be interpreted as an augmentation protocol, e.g.

$PYTHONPATH/my-pytest-plugin/typeshedding-location-for-augments/pytest/__init__.pyi or
$PYTHONPATH/typeshedding-location-for-augments/my-pytest-plugin/pytest/__init__.pyi

from typing import Protocol
import pytest

class MarkGenerator(Protocol):
    mychoice: _MyChoiceMarkDecorator

class _MyChoiceMarkDecorator(pytest.MarkDecorator):
    def __call__(  # type: ignore[override]
            self,
            select: list[str] = ...,
            skip: list[str] = ...,
        ) -> MarkDecorator: ...

Prior art

Guide de contribution

Aucun guide de contribution indexé pour ce dépôt

Par où commencer

  1. Lisez l'issue en entier, puis le guide de contribution du projet.
  2. Signalez en commentaire que vous la prenez — cela évite que deux personnes fassent le même travail.
  3. Forkez le dépôt et travaillez sur une branche.
  4. Ouvrez une pull request qui référence le numéro de l'issue.

Piste de recherche

Aucun fichier du dépôt ni aucun test n’est mentionné. Commencez par examiner PEP 561, la section référencée de PEP 484 sur la distribution des stubs et les précédents de declaration merging de TypeScript ; comparez les emplacements d’augmentation proposés et la forme de Protocol. Le travail est terminé lorsqu’un design convenu pour localiser, fusionner et interpréter les stubs d’augmentation tiers a été produit.

Rédigé par le modèle d'indexation à partir du texte de l'issue.

Évaluation

Stack technique
python
Domaine
tooling
Type d'issue
Fonctionnalité
Difficulté
5/5
Temps estimé
Plus d'une semaine
Activité
Calme
Clarté
À clarifier
Accessibilité débutants
30/100

Recevez les nouvelles issues par e-mail

Un résumé court des issues GitHub adaptées aux débutants.