Skip to content

Commit

Permalink
Fix bug with TypedDict for typing_extensions specifically (#120)
Browse files Browse the repository at this point in the history
  • Loading branch information
ariebovenberg committed Mar 1, 2023
1 parent 8cf6121 commit 87eac5c
Show file tree
Hide file tree
Showing 7 changed files with 123 additions and 195 deletions.
2 changes: 1 addition & 1 deletion .github/workflows/build.yml
Expand Up @@ -25,7 +25,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install tox tox-gh-actions poetry==1.2.2
pip install 'tox<5' tox-gh-actions 'poetry<1.5'
- name: Test with tox
run: tox

Expand Down
6 changes: 6 additions & 0 deletions CHANGELOG.rst
@@ -1,6 +1,12 @@
Changelog
=========

0.16.5 (2023-03-01)
-------------------

- Don't flag ``TypedDict`` from ``typing_extensions`` in Python versions
where ``typing`` has ``TypedDict`` itself.

0.16.4 (2023-01-13)
-------------------

Expand Down
257 changes: 72 additions & 185 deletions poetry.lock

Large diffs are not rendered by default.

7 changes: 3 additions & 4 deletions pyproject.toml
@@ -1,6 +1,6 @@
[tool.poetry]
name = "slotscheck"
version = "0.16.4"
version = "0.16.5"
description = "Ensure your __slots__ are working properly."
authors = ["Arie Bovenberg <a.c.bovenberg@gmail.com>"]
license = "MIT"
Expand Down Expand Up @@ -34,12 +34,11 @@ flake8 = "^5.0.4"
isort = "^5.11.5"
mypy = "^1.0"
pytest = "^7.2.1"
tox = "^3.27.1"
black = "^23.1"
pytest-cov = "^4.0.0"
pytest-mock = "^3.6.1"
types-dataclasses = "^0.6.6"
# Not actually used, but a rare library whose __init__ is an extension.
typing_extensions = ">=4.1,<5"
# Only actually needed as an example library whose __init__ is an extension.
pydantic = "1.9.2"

[tool.poetry.scripts]
Expand Down
19 changes: 15 additions & 4 deletions src/slotscheck/checks.py
@@ -1,12 +1,23 @@
"Slots-related checks and inspection tools"
import platform
import sys
from typing import Collection, Iterator, Optional
from typing import Callable, Collection, Iterator, Optional

from .common import either

is_typeddict: Callable[[type], bool]

try:
from typing import is_typeddict
except ImportError: # pragma: no cover
from typing_extensions import is_typeddict

try:
from typing_extensions import is_typeddict as _is_typing_ext_typeddict
except ModuleNotFoundError:
pass
else:
is_typeddict = either(is_typeddict, _is_typing_ext_typeddict)
except ModuleNotFoundError: # pragma: no cover
from typing_extensions import is_typeddict as is_any_typeddict


def slots(c: type) -> Optional[Collection[str]]:
Expand All @@ -26,7 +37,7 @@ def has_slots(c: type) -> bool:
return (
"__slots__" in c.__dict__
or not (issubclass(c, BaseException) or is_pure_python(c))
or is_typeddict(c)
or is_any_typeddict(c)
)


Expand Down
17 changes: 17 additions & 0 deletions src/slotscheck/common.py
Expand Up @@ -12,6 +12,7 @@
Set,
Tuple,
TypeVar,
overload,
)

flatten = chain.from_iterable
Expand Down Expand Up @@ -69,6 +70,22 @@ def both(__a: Predicate[_T1], __b: Predicate[_T1]) -> Predicate[_T1]:
return lambda x: __a(x) and __b(x)


@overload
def either(
__a: Callable[[_T1], bool], __b: Callable[[_T1], bool]
) -> Callable[[_T1], bool]:
...


@overload
def either(__a: Predicate[_T1], __b: Predicate[_T1]) -> Predicate[_T1]:
...


def either(__a: Any, __b: Any) -> Any:
return lambda x: __a(x) or __b(x)


def map_optional(
f: Callable[[_T1], Optional[_T2]], it: Iterable[_T1]
) -> Iterator[_T2]:
Expand Down
10 changes: 9 additions & 1 deletion tests/src/test_checks.py
Expand Up @@ -7,13 +7,14 @@
from xml.etree.ElementTree import Element

import pytest
from typing_extensions import TypedDict as TypingExtensionsTypedDict

from slotscheck.checks import has_slotless_base, has_slots, slots_overlap

try:
from typing import TypedDict
except ImportError:
from typing_extensions import TypedDict
TypedDict = TypingExtensionsTypedDict


class HasSlots:
Expand Down Expand Up @@ -73,6 +74,10 @@ class MyDict(TypedDict):
foo: str


class MyTypingExtensionsTypedDict(TypingExtensionsTypedDict):
bla: int


class TestHasSlots:
@pytest.mark.parametrize(
"klass",
Expand All @@ -84,6 +89,9 @@ def test_not_purepython(self, klass):
def test_typeddict(self):
assert has_slots(MyDict)

def test_typing_extensions_typeddict(self):
assert has_slots(MyTypingExtensionsTypedDict)

@pytest.mark.parametrize(
"klass",
[
Expand Down

0 comments on commit 87eac5c

Please sign in to comment.