Visitar URL original
Add PEP 484 -- Type Hints · Issue #368 · python-ldap/python-ldap · GitHub
Skip to content

Add PEP 484 -- Type Hints #368

Open
Open

Description

@pmhahn

Python supports type hints to improve static analysis:
https://www.python.org/dev/peps/pep-0484/
This helps to identify cases, were wrong types are passed into functions et al by using type checkers likehttp://mypy-lang.org/ or https://github.com/google/pytype/.

python-ldap does not yet seem to contain those annotations nor am I aware of any project doing that.
Those annotation can either we added directly in the library like

def func(arg: str, val: int = 0) -> Tuple[int, str]

since Python 3.5 or in a Python-2 backward compatible way as

def func(arg, val=0): # type: (str, int) -> Tuple[int, str]

As an alternative the annotations can be put into so-called "stub files" names "*.pyi", which are collected by https://github.com/python/typeshed, but inline annotations are preferred as they are easier to keep in sync.

I started working on that, but other projects have preempted that
effort. My current work is at
univention/typeshed@8fc5f79,
but it not much more then generating the stubs using stubgen.
If someone wants to join that effort that is much appreciated.

This was proposed to the Mailing List.
@tiran proposed to start the work only after the Python 2 legacy code is removed:

we are planning to drop support for Python 3.5 and earlier in the next
version of python-ldap. This would make it simpler to add signatures for
type hints directly to the code base. Patches are welcome, but please
wait until we have finalized Python 2 cleanup in master.

Activity

  1. encukou commented on Jul 1, 2020

    @encukou
    Member

    I would prefer not using .pyi. Inline type hints are much more useful, especially for documentation.
    Unfortunately, stubgen doesn't seem too successful: neither annotating things with Any, not specifying None return values, nor determinig that constants are str or int, is very useful.

    IMO, type hints are useful for documentation purposes.
    I would love to use Sphinx's autofunction to generate function signatures in the reference docs; maybe that can be done along with the type annotations?

  2. pmhahn commented on Jul 2, 2020

    @pmhahn
    ContributorAuthor

    I would prefer not using .pyi. Inline type hints are much more useful, especially for documentation.

    See my quote from @tiran - type hints will be added directly into the Python code .py files after the old Python 2 code has been purged.
    For now I'm using the .pyi files as my project is still Python 2.

    Unfortunately, stubgen doesn't seem too successful: neither annotating things with Any, not specifying None return values, nor determinig that constants are str or int, is very useful.

    Yes, it's a lot of work as stubgen only provides a very basic template. There are other tools which can generate better stubs by using runtime information - it's on my todo list to try them out.

    IMO, type hints are useful for documentation purposes.
    I would love to use Sphinx's autofunction to generate function signatures in the reference docs; maybe that can be done along with the type annotations?

    Yes, I'm already using sphinx-autodoc-typehints, which re-uses the Python type annotation for Sphinx documentation, too.

  3. Alphix commented on May 16, 2023

    @Alphix
    Contributor

    @pmhahn see PR #522 for a first attempt at adding type hints

  4. added this to the 3.5.0 milestone on Aug 28, 2026
  5. spaceone commented on Sep 15, 2026

    @spaceone
    Contributor

    @mistotebe great to see that this is merged. I have several problems with the type annotations. Would you overwork a little further? For some of them I provide the PRs linked here.

    SyncreplConsumer currently uses a conditional base class to make the methods inherited from SimpleLDAPObject available to type checkers without changing the runtime inheritance hierarchy:

    if TYPE_CHECKING:
        _Base = SimpleLDAPObject
    else:
        _Base = object
    
    
    class SyncreplConsumer(_Base):
        ...

    This causes problems when generating stubs with stubgen. The generated stub retains _Base as the base class of SyncreplConsumer, while the conditional import/assignment defining _Base is omitted, resulting in an undefined name:

    stubs/ldap/syncrepl.pyi:78:24: undefined-name: Undefined name `_Base`
    

    Would it make sense to model it using a Protocol instead?
    That could be provided in ldap._types and also re-used in stubs/ldap/controls/openldap.pyi and stubs/ldap/resiter.pyi which have the same problem for their mixin classes.

  6. spaceone commented on Sep 15, 2026

    @spaceone
    Contributor

    git 4277c52 migrated ldap.schema.models.Entry from UserDict to MutableMapping. The methods update(), items() and keys() have type-ignore comments and wrong type annotations not compatible with the upstream class.

    stubs/ldap/schema/models.pyi:67:9: error[invalid-method-override] Invalid override of method `update`: Definition is incompatible with `MutableMapping.update`
    stubs/ldap/schema/models.pyi:74:9: error[invalid-method-override] Invalid override of method `keys`: Definition is incompatible with `Mapping.keys`
    stubs/ldap/schema/models.pyi:75:9: error[invalid-method-override] Invalid override of method `items`: Definition is incompatible with `Mapping.items` 
    

    Why is the update method necessary? Can it be removed and just use the upstream method?
    Same goes for keys() and probably items() if __iter__() is implemented correctly. Or are there performance reasons speaking against it?

    Alternative is something like:

    +++ Lib/ldap/schema/models.py
    @@ -7,8 +7,10 @@ See https://www.python-ldap.org/ for details.
     from __future__ import annotations
     import sys
     
    -from typing import ClassVar, TYPE_CHECKING
    -from collections.abc import Iterator, MutableMapping
    +from typing import ClassVar, TYPE_CHECKING, cast, overload
    +from collections.abc import (
    +    Iterable, ItemsView, Iterator, KeysView, Mapping, MutableMapping,
    +)
     if TYPE_CHECKING:
         from typing_extensions import Self
     
    @@ -24,6 +26,9 @@ from ldap.schema.tokenizer import (
     
     EntryBase = MutableMapping[str, list[bytes]]
     
    +if TYPE_CHECKING:
    +    from _typeshed import SupportsKeysAndGetItem
    +
     
     NOT_HUMAN_READABLE_LDAP_SYNTAXES = {
       '1.3.6.1.4.1.1466.115.121.1.4',  # Audio
    @@ -728,8 +733,42 @@ class Entry(EntryBase):
           self._attrtype2keytuple[nameoroid] = t
           return t
     
    -  def update(self, dict: MutableMapping[str, list[bytes]]) -> None:  # type: ignore
    -    for key, value in dict.items():
    +  @overload
    +  def update(
    +    self, mapping: SupportsKeysAndGetItem[str, list[bytes]], /,
    +  ) -> None: ...
    +
    +  @overload
    +  def update(
    +    self, mapping: SupportsKeysAndGetItem[str, list[bytes]], /,
    +    **kwargs: list[bytes],
    +  ) -> None: ...
    +
    +  @overload
    +  def update(
    +    self, iterable: Iterable[tuple[str, list[bytes]]], /,
    +  ) -> None: ...
    +
    +  @overload
    +  def update(
    +    self, iterable: Iterable[tuple[str, list[bytes]]], /,
    +    **kwargs: list[bytes],
    +  ) -> None: ...
    +
    +  @overload
    +  def update(self, /, **kwargs: list[bytes]) -> None: ...
    +
    +  def update(self, other: object = None, **kwargs: list[bytes]) -> None:
    +    if other is not None:
    +      if hasattr(other, 'keys'):
    +        mapping = cast(Mapping[str, list[bytes]], other)
    +        for key in mapping.keys():
    +          self[key] = mapping[key]
    +      else:
    +        iterable = cast(Iterable[tuple[str, list[bytes]]], other)
    +        for key, value in iterable:
    +          self[key] = value
    +    for key, value in kwargs.items():
           self[key] = value
  7. 58 remaining items

  8. spaceone commented on Oct 25, 2026

    @spaceone
    Contributor

    → #690 for the mixin class stubgen problem

  9. spaceone commented on Oct 25, 2026

    @spaceone
    Contributor

    Another problem with stubgen is that it does not recognize re-exported TypeAliases - see python/mypy#22294

    for Python 3.9 support we have a conditional TypeAlias in ldap._types and use it like:

    from ldap._types import TypeAlias
    
    LDAPTokenDictValue: TypeAlias = Union[tuple[()], tuple[str, ...]]

    stubgen doesn't recognize the imported TypeAlias as a type alias declaration and generates:

    LDAPTokenDictValue: TypeAlias = ...

    → this loses the actual type definition so that the generated stub is not usable

    importing TypeAlias directly from typing works - but I don't want this 3 case code in all the places.
    I'll test what happens if we move all TypeAlias declarations into ldap._types.

  10. spaceone commented on Oct 25, 2026

    @spaceone
    Contributor

    → #691 (at least for these - doesn't work for ldapurl module)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions