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

fix #918: remove the need for importlib_metadata in general #929

Merged
Show file tree
Hide file tree
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
6 changes: 6 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
v8.0.3
======

* fix #918 for good - remove external importlib-metadata to avoid source only loop
* fix #926: ensure mypy on python3.8 works with the version file

v8.0.2
======

Expand Down
3 changes: 0 additions & 3 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,9 @@
[build-system]
build-backend = "_own_version_helper"
requires = [
'importlib-metadata>=4.6; python_version < "3.10"',
"rich",
"setuptools>=61",
'tomli; python_version < "3.11"',
'typing_extensions; python_version < "3.8"',
]
backend-path = [
".",
Expand Down Expand Up @@ -42,7 +40,6 @@ dynamic = [
"version",
]
dependencies = [
'importlib-metadata>=4.6; python_version < "3.10"',
"packaging>=20",
"setuptools",
'tomli>=1; python_version < "3.11"',
Expand Down
56 changes: 28 additions & 28 deletions src/setuptools_scm/_entrypoints.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@
from typing import cast
from typing import Iterator
from typing import overload
from typing import Protocol
from typing import TYPE_CHECKING

from . import _log
Expand All @@ -17,15 +16,32 @@
from ._config import Configuration, ParseFunction


log = _log.log.getChild("entrypoints")
from importlib.metadata import EntryPoint as EntryPoint
Copy link
Contributor

Choose a reason for hiding this comment

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

Should this not be removed? It's outside the version condition.

Copy link
Contributor

@QuLogic QuLogic Sep 22, 2023

Choose a reason for hiding this comment

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

Oh, never mind me, I see it's available in Python 3.8 already, and I misread it as the plural.



if sys.version_info[:2] < (3, 10):
from importlib.metadata import entry_points as legacy_entry_points

class EntryPoints:
_groupdata: list[EntryPoint]

def __init__(self, groupdata: list[EntryPoint]) -> None:
self._groupdata = groupdata

def select(self, name: str) -> EntryPoints:
return EntryPoints([x for x in self._groupdata if x.name == name])
RonnyPfannschmidt marked this conversation as resolved.
Show resolved Hide resolved

def __iter__(self) -> Iterator[EntryPoint]:
return iter(self._groupdata)

class EntrypointProtocol(Protocol):
name: str
value: str
def entry_points(group: str) -> EntryPoints:
return EntryPoints(legacy_entry_points()[group])

def load(self) -> Any:
pass
else:
from importlib.metadata import entry_points, EntryPoints


log = _log.log.getChild("entrypoints")


def version_from_entrypoint(
Expand All @@ -43,27 +59,11 @@ def version_from_entrypoint(
return None


if sys.version_info[:2] < (3, 10):
from importlib_metadata import entry_points
from importlib_metadata import EntryPoint
else:
from importlib.metadata import entry_points
from importlib.metadata import EntryPoint


def iter_entry_points(
group: str, name: str | None = None
) -> Iterator[EntrypointProtocol]:
eps = entry_points(group=group)
res = (
eps
if name is None
else eps.select( # type: ignore [no-untyped-call]
name=name,
)
)
def iter_entry_points(group: str, name: str | None = None) -> Iterator[EntryPoint]:
eps: EntryPoints = entry_points(group=group)
res = eps if name is None else eps.select(name=name)
RonnyPfannschmidt marked this conversation as resolved.
Show resolved Hide resolved

return cast(Iterator[EntrypointProtocol], iter(res))
return iter(res)


def _get_ep(group: str, name: str) -> Any | None:
Expand All @@ -76,7 +76,7 @@ def _get_ep(group: str, name: str) -> Any | None:

def _get_from_object_reference_str(path: str, group: str) -> Any | None:
# todo: remove for importlib native spelling
ep: EntrypointProtocol = EntryPoint(path, path, group)
ep = EntryPoint(path, path, group)
try:
return ep.load()
except (AttributeError, ModuleNotFoundError):
Expand Down
6 changes: 4 additions & 2 deletions src/setuptools_scm/_integration/dump_version.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,12 @@
".py": """\
# file generated by setuptools_scm
# don't change, don't track in version control
from __future__ import annotations
TYPE_CHECKING = False
Copy link

Choose a reason for hiding this comment

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

This looks weird. Doesn't this effectively always disable all type checking for this file?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Type checkers pretend such variables are true

Copy link

Choose a reason for hiding this comment

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

Well, this is not normal way to do this. Normally you import typing and check typing.TYPE_CHECKING. It is normally never set by user.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

this is a trick specifically for the type checkers

they use the variable name as indication, that way one does not have to do a runtime import

as importing typing can be expensive , its deemed that the version file ought not to impose that cost

so this really is just a dirty trick to avoid runtime imports in type check able files

Copy link

Choose a reason for hiding this comment

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

Are you sure this really works? Have you tested type checkers fail here if you provide wrong input? https://docs.python.org/3/library/typing.html#typing.TYPE_CHECKING says this is specifically a constant in typing module. Mypy documentation agrees https://mypy.readthedocs.io/en/stable/runtime_troubles.html#import-cycles

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The unit test is there specifically to check this

If necessary ill add a test with type assertions

if TYPE_CHECKING:
from typing import Tuple

__version__ = version = {version!r} # type: str
__version_tuple__ = version_tuple = {version_tuple!r} # type: tuple[int | str, ...]
__version_tuple__ = version_tuple = {version_tuple!r} # type: Tuple[int | str, ...]
""",
".txt": "{version}",
}
Expand Down
2 changes: 1 addition & 1 deletion src/setuptools_scm/discover.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def match_entrypoint(root: _t.PathT, name: str) -> bool:

def iter_matching_entrypoints(
root: _t.PathT, entrypoint: str, config: Configuration
) -> Iterable[_entrypoints.EntrypointProtocol]:
) -> Iterable[_entrypoints.EntryPoint]:
"""
Consider different entry-points in ``root`` and optionally its parents.
:param root: File path.
Expand Down
2 changes: 1 addition & 1 deletion testing/test_basic_api.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,7 +182,7 @@ def read(name: str) -> str:
assert lines[-2:] == [
"__version__ = version = '1.0.dev42' # type: str",
"__version_tuple__ = version_tuple = (1, 0, 'dev42')"
" # type: tuple[int | str, ...]",
" # type: Tuple[int | str, ...]",
]

version = "1.0.1+g4ac9d2c"
Expand Down
6 changes: 5 additions & 1 deletion testing/test_functions.py
Original file line number Diff line number Diff line change
Expand Up @@ -137,7 +137,11 @@ def test_dump_version_mypy(tmp_path: Path) -> None:
if mypy is None:
pytest.skip("mypy not found")
dump_a_version(tmp_path)
subprocess.run([mypy, "--strict", "VERSION.py"], cwd=tmp_path, check=True)
subprocess.run(
[mypy, "--python-version=3.8", "--strict", "VERSION.py"],
cwd=tmp_path,
check=True,
)


def test_dump_version_flake8(tmp_path: Path) -> None:
Expand Down