diff --git a/importlib_metadata/__init__.py b/importlib_metadata/__init__.py index 29ce1175..1ecab312 100644 --- a/importlib_metadata/__init__.py +++ b/importlib_metadata/__init__.py @@ -29,6 +29,8 @@ from importlib import import_module from importlib.abc import MetaPathFinder from itertools import starmap +from pydantic import Field, validate_arguments +from pydantic.typing import Annotated from typing import List, Mapping, Optional, Union @@ -548,7 +550,8 @@ def locate_file(self, path): """ @classmethod - def from_name(cls, name): + @validate_arguments + def from_name(cls, name: Annotated[str, Field(min_length=1)]): """Return the Distribution for the given package name. :param name: The name of the distribution package to search for. diff --git a/pyproject.toml b/pyproject.toml index 60de2424..1ee8e047 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -1,5 +1,11 @@ [build-system] -requires = ["setuptools>=56", "setuptools_scm[toml]>=3.4.1"] +requires = [ + "setuptools>=56", + "setuptools_scm[toml]>=3.4.1", + # temporary workaround for dependency race in setuptools_scm + # https://github.com/python/importlib_metadata/pull/389#issuecomment-1164912426 + 'pydantic; python_version < "3.8"', +] build-backend = "setuptools.build_meta" [tool.black] diff --git a/setup.cfg b/setup.cfg index efd0a36d..75dc6bc2 100644 --- a/setup.cfg +++ b/setup.cfg @@ -19,6 +19,7 @@ python_requires = >=3.7 install_requires = zipp>=0.5 typing-extensions>=3.6.4; python_version < "3.8" + pydantic [options.packages.find] exclude = diff --git a/tests/fixtures.py b/tests/fixtures.py index 08a478ac..6d9a9d2b 100644 --- a/tests/fixtures.py +++ b/tests/fixtures.py @@ -5,6 +5,7 @@ import pathlib import tempfile import textwrap +import functools import contextlib from .py39compat import FS_NONASCII @@ -294,3 +295,18 @@ def setUp(self): # Add self.zip_name to the front of sys.path. self.resources = contextlib.ExitStack() self.addCleanup(self.resources.close) + + +def parameterize(*args_set): + """Run test method with a series of parameters.""" + + def wrapper(func): + @functools.wraps(func) + def _inner(self): + for args in args_set: + with self.subTest(**args): + func(self, **args) + + return _inner + + return wrapper diff --git a/tests/test_main.py b/tests/test_main.py index 215662dd..921f5d9c 100644 --- a/tests/test_main.py +++ b/tests/test_main.py @@ -50,6 +50,14 @@ def test_new_style_classes(self): self.assertIsInstance(Distribution, type) self.assertIsInstance(MetadataPathFinder, type) + @fixtures.parameterize( + dict(name=None), + dict(name=''), + ) + def test_invalid_inputs_to_from_name(self, name): + with self.assertRaises(Exception): + Distribution.from_name(name) + class ImportTests(fixtures.DistInfoPkg, unittest.TestCase): def test_import_nonexistent_module(self):