Skip to content

Commit

Permalink
Merge pull request #194 from ariebovenberg/py312
Browse files Browse the repository at this point in the history
add Python 3.12 support
  • Loading branch information
ariebovenberg authored Nov 3, 2023
2 parents 69b2792 + 37e76b7 commit f5238c7
Show file tree
Hide file tree
Showing 15 changed files with 316 additions and 214 deletions.
4 changes: 2 additions & 2 deletions .github/workflows/build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: ["3.8", "3.9", "3.10", "3.11"]
python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"]

steps:
- uses: actions/checkout@v1
Expand All @@ -25,7 +25,7 @@ jobs:
- name: Install dependencies
run: |
python -m pip install --upgrade pip
pip install 'tox<5' tox-gh-actions 'poetry>=1.5,<1.6'
pip install 'tox<5' tox-gh-actions 'poetry>=1.6,<1.7'
- name: Test with tox
run: tox

Expand Down
7 changes: 7 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,6 +1,13 @@
Changelog
=========

0.17.1 (2023-11-03)
-------------------

- Fixed ``Generic`` false positive in Python 3.12+ (#201).
- Remove ``pkgutil`` deprecation warning on Python 3.12+.
- Official Python 3.12 support.

0.17.0 (2023-07-31)
-------------------

Expand Down
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ format-check:
fix: isort format

lint:
flake8 --exclude=.tox,build
flake8 src tests --exclude=.tox,build

type-check:
mypy --pretty src tests/src
Expand Down
2 changes: 1 addition & 1 deletion docs/advanced.rst
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ Use the following configuration:
repos:
- repo: https://github.com/ariebovenberg/slotscheck
rev: v0.17.0
rev: v0.17.1
hooks:
- id: slotscheck
# If your Python files are not importable from the project root,
Expand Down
9 changes: 4 additions & 5 deletions mypy.ini
Original file line number Diff line number Diff line change
@@ -1,14 +1,13 @@
[mypy]
disallow_untyped_defs = True
strict = True
warn_redundant_casts = True
warn_unused_ignores = True
warn_unreachable = True
enable_error_code = redundant-expr
exclude= ^tests/examples/.*$

[mypy-tests.*]
check_untyped_defs = True
disable_error_code =
no-untyped-def
disallow_untyped_defs = False
warn_unreachable = True

[mypy-module_misc.*,module_singular]
ignore_errors = True
390 changes: 241 additions & 149 deletions poetry.lock

Large diffs are not rendered by default.

5 changes: 3 additions & 2 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tool.poetry]
name = "slotscheck"
version = "0.17.0"
version = "0.17.1"
description = "Ensure your __slots__ are working properly."
authors = ["Arie Bovenberg <[email protected]>"]
license = "MIT"
Expand All @@ -12,6 +12,7 @@ classifiers = [
"Programming Language :: Python :: 3.9",
"Programming Language :: Python :: 3.10",
"Programming Language :: Python :: 3.11",
"Programming Language :: Python :: 3.12",
]
packages = [
{ include = "slotscheck", from = "src" },
Expand All @@ -36,7 +37,7 @@ pytest-cov = "^4.1.0"
pytest-mock = "^3.12.0"
typing_extensions = ">=4.1,<5"
# Only actually needed as an example library whose __init__ is an extension.
pydantic = "1.9.2"
pydantic = "<3"

[tool.poetry.scripts]
slotscheck = "slotscheck.cli:root"
Expand Down
7 changes: 5 additions & 2 deletions src/slotscheck/checks.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"Slots-related checks and inspection tools"
import platform
import sys
from typing import Collection, Iterator, Optional
from typing import Collection, Generic, Iterator, Optional

from .common import is_typeddict

Expand All @@ -16,14 +16,17 @@ def slots(c: type) -> Optional[Collection[str]]:
elif isinstance(slots_raw, Iterator):
raise NotImplementedError("Iterator __slots__ not supported. See #22")
else:
return slots_raw
# We know it's a collection of strings now, since class creation
# would have failed otherwise
return slots_raw # type: ignore[no-any-return]


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 c is Generic # type: ignore[comparison-overlap]
)


Expand Down
15 changes: 14 additions & 1 deletion src/slotscheck/common.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,16 @@
TypeVar,
)

__all__ = [
"both",
"compose",
"groupby",
"is_protocol",
"is_typeddict",
"map_optional",
"unique",
]

flatten = chain.from_iterable


Expand Down Expand Up @@ -92,7 +102,10 @@ def is_typeddict(tp: object) -> bool:
# Note that typing.is_protocol is not available yet (CPython PR 104878)
# The implementation below is derived from it.
def is_protocol(t: type) -> bool: # pragma: no cover
return getattr(t, "_is_protocol", False) and t != Protocol
return (
getattr(t, "_is_protocol", False)
and t != Protocol # type: ignore[comparison-overlap]
)


try:
Expand Down
10 changes: 6 additions & 4 deletions src/slotscheck/discovery.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,11 +4,12 @@
import pkgutil
from dataclasses import dataclass, field, replace
from functools import partial, reduce
from importlib._bootstrap_external import ( # type: ignore[import]
from importlib._bootstrap_external import ( # type: ignore[import-not-found]
_NamespaceLoader,
)
from importlib.abc import FileLoader
from importlib.machinery import ExtensionFileLoader
from importlib.util import find_spec
from inspect import isclass
from itertools import chain, takewhile
from pathlib import Path
Expand Down Expand Up @@ -164,9 +165,12 @@ def module_tree(
) -> Union[ModuleTree, FailedImport]:
"""May raise ModuleNotFoundError or UnexpectedImportLocation"""
try:
loader = pkgutil.get_loader(module)
spec = find_spec(module)
except BaseException as e:
return FailedImport(module, e)
if spec is None:
raise ModuleNotFoundError(f"No module named '{module}'", name=module)
loader = spec.loader
*namespaces, name = module.split(".")
location: AbsPath
tree: ModuleTree
Expand All @@ -182,8 +186,6 @@ def module_tree(
assert len(loader._path._path) == 1
location = Path(loader._path._path[0])
tree = _package(name, location)
elif loader is None:
raise ModuleNotFoundError(f"No module named '{module}'", name=module)
elif module == "builtins":
return Module(module)
else:
Expand Down
9 changes: 8 additions & 1 deletion tests/examples/module_not_ok/foo.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
from typing import Generic, Protocol, TypeVar

from typing import Protocol
from typing_extensions import Protocol as TypingExtProtocol


Expand Down Expand Up @@ -115,3 +115,10 @@ class MyOtherProto(TypingExtProtocol):

class Zb(MyProto):
__slots__ = ()


Tvar = TypeVar("Tvar")


class Zc(Generic[Tvar]):
__slots__ = ()
2 changes: 1 addition & 1 deletion tests/src/test_checks.py
Original file line number Diff line number Diff line change
Expand Up @@ -58,7 +58,7 @@ class OneStringSlot(HasSlots):
__slots__ = "baz"


class ArrayInherit(array):
class ArrayInherit(array): # type: ignore[type-arg]
__slots__ = ()


Expand Down
26 changes: 13 additions & 13 deletions tests/src/test_cli.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import os
import pkgutil
from importlib.util import find_spec
from pathlib import Path

import pytest
Expand Down Expand Up @@ -207,7 +207,7 @@ def test_errors_with_default_settings(runner: CliRunner):
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
ERROR: 'module_not_ok.foo:Zb' has slots but superclass does not.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand All @@ -234,7 +234,7 @@ def test_errors_require_slots_subclass(runner: CliRunner):
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
ERROR: 'module_not_ok.foo:Zb' has slots but superclass does not.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand All @@ -259,7 +259,7 @@ def test_errors_disallow_nonslot_inherit(runner: CliRunner):
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
ERROR: 'module_not_ok.foo:Zb' has slots but superclass does not.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand All @@ -279,7 +279,7 @@ def test_errors_no_require_superclass(runner: CliRunner):
ERROR: 'module_not_ok.foo:Z' has duplicate slots.
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand All @@ -302,7 +302,7 @@ def test_errors_with_exclude_classes(runner: CliRunner):
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
ERROR: 'module_not_ok.foo:Zb' has slots but superclass does not.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand All @@ -322,7 +322,7 @@ def test_errors_with_include_classes(runner: CliRunner):
ERROR: 'module_not_ok.foo:W' defines overlapping slots.
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand Down Expand Up @@ -417,8 +417,8 @@ def test_module_not_ok_verbose(runner: CliRunner):
excluded: 0
skipped: 0
classes: 31
has slots: 22
classes: 32
has slots: 23
no slots: 9
n/a: 0
"""
Expand Down Expand Up @@ -519,7 +519,7 @@ def test_finds_config(runner: CliRunner, mocker, tmpdir):
ERROR: 'module_not_ok.foo:Z' has duplicate slots.
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand Down Expand Up @@ -548,7 +548,7 @@ def test_given_config(runner: CliRunner, tmpdir):
ERROR: 'module_not_ok.foo:Z' has duplicate slots.
ERROR: 'module_not_ok.foo:Za' defines overlapping slots.
Oh no, found some problems!
Scanned 4 module(s), 31 class(es).
Scanned 4 module(s), 32 class(es).
"""
)

Expand Down Expand Up @@ -578,9 +578,9 @@ def test_ambiguous_import(runner: CliRunner):
See slotscheck.rtfd.io/en/latest/discovery.html
for more information on why this happens and how to resolve it.
""".format(
pkgutil.get_loader(
find_spec(
"module_misc.a.b.c"
).path, # type: ignore[union-attr]
).loader.path, # type: ignore[union-attr]
EXAMPLES_DIR / "other/module_misc/a/b/c.py",
)
)
Expand Down
37 changes: 7 additions & 30 deletions tests/src/test_discovery.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from typing import List
from typing import FrozenSet, List, TypeVar
from unittest import mock

import pytest
Expand All @@ -18,8 +18,10 @@

from .conftest import EXAMPLES_DIR

T = TypeVar("T")

def fset(*args) -> frozenset:

def fset(*args: T) -> FrozenSet[T]:
return frozenset(args)


Expand Down Expand Up @@ -253,34 +255,9 @@ def test_import_error(self, mocker):
) == FailedImport("broken.submodule", mocker.ANY)

def test_extension_package(self):
assert module_tree("pydantic", None) == make_pkg(
"pydantic",
Module("annotated_types"),
Module("types"),
Module("schema"),
Module("version"),
Module("utils"),
Module("parse"),
Module("env_settings"),
Module("datetime_parse"),
Module("json"),
Module("decorator"),
Module("class_validators"),
Module("generics"),
Module("validators"),
Module("main"),
Module("errors"),
Module("_hypothesis_plugin"),
Module("dataclasses"),
Module("mypy"),
Module("typing"),
Module("networks"),
Module("error_wrappers"),
Module("config"),
Module("fields"),
Module("tools"),
Module("color"),
)
tree = module_tree("pydantic", None)
assert isinstance(tree, Package)
assert len(tree.content) > 20

def test_module(self):
assert module_tree(
Expand Down
5 changes: 3 additions & 2 deletions tox.ini
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[tox]
isolated_build = true
envlist = py{38,39,310,311},lint,docs,mypy,isort,slots
envlist = py{38,39,310,311,312},lint,docs,mypy,isort,slots

[testenv]
allowlist_externals =
Expand Down Expand Up @@ -56,4 +56,5 @@ python =
3.8: py38
3.9: py39
3.10: py310
3.11: py311, docs, mypy, lint, isort, slots
3.11: py311, docs, lint, isort, slots
3.12: py312, mypy

0 comments on commit f5238c7

Please sign in to comment.