diff --git a/backend/src/hatchling/dep/core.py b/backend/src/hatchling/dep/core.py index c60437310..b16202254 100644 --- a/backend/src/hatchling/dep/core.py +++ b/backend/src/hatchling/dep/core.py @@ -30,7 +30,7 @@ def __getitem__(self, item: str) -> Distribution | None: return None for distribution in self._resolver: - name = self._canonical_regex.sub('-', distribution.metadata.get('Name')).lower() + name = self._canonical_regex.sub('-', distribution.metadata['Name']).lower() self._distributions[name] = distribution if name == item: return distribution diff --git a/backend/src/hatchling/metadata/classifiers.py b/backend/src/hatchling/metadata/classifiers.py index 32e189c3f..bd3fc050b 100644 --- a/backend/src/hatchling/metadata/classifiers.py +++ b/backend/src/hatchling/metadata/classifiers.py @@ -810,5 +810,5 @@ KNOWN_CLASSIFIERS = set(SORTED_CLASSIFIERS) -def is_private(classifier): +def is_private(classifier: str) -> bool: return classifier.lower().startswith('private ::') diff --git a/backend/src/hatchling/metadata/core.py b/backend/src/hatchling/metadata/core.py index a12504ef0..d3c0a195b 100644 --- a/backend/src/hatchling/metadata/core.py +++ b/backend/src/hatchling/metadata/core.py @@ -3,7 +3,7 @@ import os import sys from copy import deepcopy -from typing import TYPE_CHECKING, Any +from typing import TYPE_CHECKING, Any, cast from hatchling.metadata.utils import get_normalized_dependency, is_valid_project_name, normalize_project_name from hatchling.utils.constants import DEFAULT_CONFIG_FILE @@ -206,7 +206,7 @@ def hatch(self) -> HatchMetadata: return self._hatch - def _get_version(self, core_metadata=None): + def _get_version(self, core_metadata: CoreMetadata | None = None) -> str: if core_metadata is None: core_metadata = self.core @@ -230,7 +230,7 @@ def _get_version(self, core_metadata=None): else: return normalized_version - def validate_fields(self): + def validate_fields(self) -> None: _ = self.version self.core.validate_fields() @@ -274,14 +274,14 @@ def requires_complex(self) -> list[Requirement]: return self._requires_complex @property - def requires(self): + def requires(self) -> list[str]: if self._requires is None: self._requires = [str(r) for r in self.requires_complex] return self._requires @property - def build_backend(self): + def build_backend(self) -> str: if self._build_backend is None: build_backend = self.config.get('build-backend', '') if not isinstance(build_backend, str): @@ -292,7 +292,7 @@ def build_backend(self): return self._build_backend @property - def backend_path(self): + def backend_path(self) -> list[str]: if self._backend_path is None: backend_path = self.config.get('backend-path', []) if not isinstance(backend_path, list): @@ -354,10 +354,10 @@ def __init__( self._dynamic: list[str] | None = None # Indicates that the version has been successfully set dynamically - self._version_set = False + self._version_set: bool = False @property - def raw_name(self): + def raw_name(self) -> str: """ https://peps.python.org/pep-0621/#name """ @@ -385,7 +385,7 @@ def raw_name(self): return self._raw_name @property - def name(self): + def name(self) -> str: """ https://peps.python.org/pep-0621/#name """ @@ -395,10 +395,12 @@ def name(self): return self._name @property - def version(self): + def version(self) -> str: """ https://peps.python.org/pep-0621/#version """ + version: str + if self._version is None: if 'version' not in self.config: if not self._version_set and 'version' not in self.dynamic: @@ -419,10 +421,10 @@ def version(self): self._version = version - return self._version + return cast(str, self._version) @property - def description(self): + def description(self) -> str: """ https://peps.python.org/pep-0621/#description """ @@ -445,10 +447,13 @@ def description(self): return self._description @property - def readme(self): + def readme(self) -> str: """ https://peps.python.org/pep-0621/#readme """ + readme: str | dict[str, str] | None + content_type: str | None + if self._readme is None: if 'readme' in self.config: readme = self.config['readme'] @@ -532,27 +537,27 @@ def readme(self): return self._readme @property - def readme_content_type(self): + def readme_content_type(self) -> str: """ https://peps.python.org/pep-0621/#readme """ if self._readme_content_type is None: _ = self.readme - return self._readme_content_type + return cast(str, self._readme_content_type) @property - def readme_path(self): + def readme_path(self) -> str: """ https://peps.python.org/pep-0621/#readme """ if self._readme_path is None: _ = self.readme - return self._readme_path + return cast(str, self._readme_path) @property - def requires_python(self): + def requires_python(self) -> str: """ https://peps.python.org/pep-0621/#requires-python """ @@ -582,14 +587,14 @@ def requires_python(self): return self._requires_python @property - def python_constraint(self): + def python_constraint(self) -> SpecifierSet: if self._python_constraint is None: _ = self.requires_python return self._python_constraint @property - def license(self): # noqa: A003 + def license(self) -> str: # noqa: A003 """ https://peps.python.org/pep-0621/#license """ @@ -646,17 +651,17 @@ def license(self): # noqa: A003 return self._license @property - def license_expression(self): + def license_expression(self) -> str: """ https://peps.python.org/pep-0639/ """ if self._license_expression is None: _ = self.license - return self._license_expression + return cast(str, self._license_expression) @property - def license_files(self): + def license_files(self) -> list[str]: """ https://peps.python.org/pep-0639/ """ @@ -720,10 +725,13 @@ def license_files(self): return self._license_files @property - def authors(self): + def authors(self) -> list[str]: """ https://peps.python.org/pep-0621/#authors-maintainers """ + authors: list[str] + authors_data: dict[str, list[str]] + if self._authors is None: if 'authors' in self.config: authors = self.config['authors'] @@ -770,20 +778,22 @@ def authors(self): return self._authors @property - def authors_data(self): + def authors_data(self) -> dict[str, list[str]]: """ https://peps.python.org/pep-0621/#authors-maintainers """ if self._authors_data is None: _ = self.authors - return self._authors_data + return cast(dict, self._authors_data) @property - def maintainers(self): + def maintainers(self) -> list[str]: """ https://peps.python.org/pep-0621/#authors-maintainers """ + maintainers: list[str] + if self._maintainers is None: if 'maintainers' in self.config: maintainers = self.config['maintainers'] @@ -801,7 +811,7 @@ def maintainers(self): from email.headerregistry import Address maintainers = deepcopy(maintainers) - maintainers_data = {'name': [], 'email': []} + maintainers_data: dict[str, list[str]] = {'name': [], 'email': []} for i, data in enumerate(maintainers, 1): if not isinstance(data, dict): @@ -832,17 +842,17 @@ def maintainers(self): return self._maintainers @property - def maintainers_data(self): + def maintainers_data(self) -> dict[str, list[str]]: """ https://peps.python.org/pep-0621/#authors-maintainers """ if self._maintainers_data is None: _ = self.maintainers - return self._maintainers_data + return cast(dict, self._maintainers_data) @property - def keywords(self): + def keywords(self) -> list[str]: """ https://peps.python.org/pep-0621/#keywords """ @@ -873,7 +883,7 @@ def keywords(self): return self._keywords @property - def classifiers(self): + def classifiers(self) -> list[str]: """ https://peps.python.org/pep-0621/#classifiers """ @@ -917,7 +927,7 @@ def classifiers(self): return self._classifiers @property - def urls(self): + def urls(self) -> dict[str, str]: """ https://peps.python.org/pep-0621/#urls """ @@ -947,7 +957,7 @@ def urls(self): return self._urls @property - def scripts(self): + def scripts(self) -> dict[str, str]: """ https://peps.python.org/pep-0621/#entry-points """ @@ -978,7 +988,7 @@ def scripts(self): return self._scripts @property - def gui_scripts(self): + def gui_scripts(self) -> dict[str, str]: """ https://peps.python.org/pep-0621/#entry-points """ @@ -1009,7 +1019,7 @@ def gui_scripts(self): return self._gui_scripts @property - def entry_points(self): + def entry_points(self) -> dict[str, dict[str, str]]: """ https://peps.python.org/pep-0621/#entry-points """ @@ -1102,7 +1112,7 @@ def dependencies_complex(self) -> dict[str, Requirement]: return self._dependencies_complex @property - def dependencies(self): + def dependencies(self) -> list[str]: """ https://peps.python.org/pep-0621/#dependencies-optional-dependencies """ @@ -1191,7 +1201,7 @@ def optional_dependencies_complex(self) -> dict[Any, Any]: return self._optional_dependencies_complex @property - def optional_dependencies(self): + def optional_dependencies(self) -> dict[str, list[Requirement]]: """ https://peps.python.org/pep-0621/#dependencies-optional-dependencies """ @@ -1217,10 +1227,10 @@ def dynamic(self) -> list[str]: return self._dynamic - def add_known_classifiers(self, classifiers): + def add_known_classifiers(self, classifiers: str) -> None: self._extra_classifiers.update(classifiers) - def validate_fields(self): + def validate_fields(self) -> None: # Trigger validation for everything for attribute in dir(self): getattr(self, attribute) @@ -1249,7 +1259,7 @@ def metadata(self) -> HatchMetadataSettings: return self._metadata @property - def build_config(self): + def build_config(self) -> dict[str, Any]: if self._build_config is None: build_config = self.config.get('build', {}) if not isinstance(build_config, dict): @@ -1260,9 +1270,9 @@ def build_config(self): return self._build_config @property - def build_targets(self): + def build_targets(self) -> dict[str, Any]: if self._build_targets is None: - build_targets = self.build_config.get('targets', {}) + build_targets: dict = self.build_config.get('targets', {}) if not isinstance(build_targets, dict): raise TypeError('Field `tool.hatch.build.targets` must be a table') @@ -1291,14 +1301,14 @@ def __init__(self, root: Path | str, config: dict[str, Any], plugin_manager: Plu self.config = config self.plugin_manager = plugin_manager - self._cached = None + self._cached: str | None = None self._source_name: str | None = None - self._scheme_name = None + self._scheme_name: str | None = None self._source: RegexSource | None = None - self._scheme = None + self._scheme: str | None = None @property - def cached(self): + def cached(self) -> str: if self._cached is None: try: self._cached = self.source.get_version_data()['version'] @@ -1323,9 +1333,9 @@ def source_name(self) -> str: return self._source_name @property - def scheme_name(self): + def scheme_name(self) -> str: if self._scheme_name is None: - scheme = self.config.get('scheme', 'standard') + scheme: str = self.config.get('scheme', 'standard') if not scheme: raise ValueError( 'The `scheme` option under the `tool.hatch.version` table must not be empty if defined' @@ -1354,7 +1364,7 @@ def source(self) -> RegexSource: return self._source @property - def scheme(self): + def scheme(self) -> str: if self._scheme is None: from copy import deepcopy @@ -1382,7 +1392,7 @@ def __init__(self, root: Path | str, config: dict[Any, Any], plugin_manager: Plu self._hooks: dict[Any, Any] | None = None @property - def allow_direct_references(self): + def allow_direct_references(self) -> bool: if self._allow_direct_references is None: allow_direct_references: bool = self.config.get('allow-direct-references', False) if not isinstance(allow_direct_references, bool): @@ -1393,7 +1403,7 @@ def allow_direct_references(self): return self._allow_direct_references @property - def allow_ambiguous_features(self): + def allow_ambiguous_features(self) -> bool: # TODO: remove in the first minor release after Jan 1, 2024 if self._allow_ambiguous_features is None: allow_ambiguous_features: bool = self.config.get('allow-ambiguous-features', False) diff --git a/backend/src/hatchling/metadata/custom.py b/backend/src/hatchling/metadata/custom.py index 7e17db7f4..1d2773041 100644 --- a/backend/src/hatchling/metadata/custom.py +++ b/backend/src/hatchling/metadata/custom.py @@ -1,14 +1,20 @@ +from __future__ import annotations + import os +from typing import TYPE_CHECKING, Any from hatchling.metadata.plugin.interface import MetadataHookInterface from hatchling.plugin.utils import load_plugin_from_script from hatchling.utils.constants import DEFAULT_BUILD_SCRIPT +if TYPE_CHECKING: + from hatchling.builders.hooks.plugin.interface import BuildHookInterface + class CustomMetadataHook: PLUGIN_NAME = 'custom' - def __new__(cls, root, config, *args, **kwargs): + def __new__(cls, root: str, config: dict[str, Any], *args: Any, **kwargs: Any) -> BuildHookInterface: build_script = config.get('path', DEFAULT_BUILD_SCRIPT) if not isinstance(build_script, str): raise TypeError(f'Option `path` for metadata hook `{cls.PLUGIN_NAME}` must be a string') diff --git a/backend/src/hatchling/metadata/plugin/hooks.py b/backend/src/hatchling/metadata/plugin/hooks.py index 00aa9ccd0..54518284b 100644 --- a/backend/src/hatchling/metadata/plugin/hooks.py +++ b/backend/src/hatchling/metadata/plugin/hooks.py @@ -1,7 +1,9 @@ +from __future__ import annotations + from hatchling.metadata.custom import CustomMetadataHook from hatchling.plugin import hookimpl @hookimpl -def hatch_register_metadata_hook(): +def hatch_register_metadata_hook() -> type[CustomMetadataHook]: return CustomMetadataHook diff --git a/backend/src/hatchling/metadata/plugin/interface.py b/backend/src/hatchling/metadata/plugin/interface.py index 9e62c7913..4ae299ad1 100644 --- a/backend/src/hatchling/metadata/plugin/interface.py +++ b/backend/src/hatchling/metadata/plugin/interface.py @@ -35,19 +35,19 @@ def hatch_register_metadata_hook(): PLUGIN_NAME = '' """The name used for selection.""" - def __init__(self, root, config): + def __init__(self, root: str, config: dict) -> None: self.__root = root self.__config = config @property - def root(self): + def root(self) -> str: """ The root of the project tree. """ return self.__root @property - def config(self): + def config(self) -> dict: """ The hook configuration. @@ -66,7 +66,7 @@ def config(self): return self.__config @abstractmethod - def update(self, metadata: dict): + def update(self, metadata: dict) -> None: """ This updates the metadata mapping of the `project` table in-place. """ diff --git a/backend/src/hatchling/metadata/spec.py b/backend/src/hatchling/metadata/spec.py index fedb71dcf..12aeebc93 100644 --- a/backend/src/hatchling/metadata/spec.py +++ b/backend/src/hatchling/metadata/spec.py @@ -1,7 +1,14 @@ +from __future__ import annotations + +from typing import TYPE_CHECKING, Callable + +if TYPE_CHECKING: + from hatchling.metadata.core import ProjectMetadata + DEFAULT_METADATA_VERSION = '2.1' -def get_core_metadata_constructors(): +def get_core_metadata_constructors() -> dict[str, Callable]: """ https://packaging.python.org/specifications/core-metadata/ """ @@ -13,7 +20,7 @@ def get_core_metadata_constructors(): } -def construct_metadata_file_1_2(metadata, extra_dependencies=()): +def construct_metadata_file_1_2(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str: """ https://peps.python.org/pep-0345/ """ @@ -72,7 +79,7 @@ def construct_metadata_file_1_2(metadata, extra_dependencies=()): return metadata_file -def construct_metadata_file_2_1(metadata, extra_dependencies=()): +def construct_metadata_file_2_1(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str: """ https://peps.python.org/pep-0566/ """ @@ -153,7 +160,7 @@ def construct_metadata_file_2_1(metadata, extra_dependencies=()): return metadata_file -def construct_metadata_file_2_2(metadata, extra_dependencies=()): +def construct_metadata_file_2_2(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str: """ https://peps.python.org/pep-0643/ """ @@ -234,7 +241,7 @@ def construct_metadata_file_2_2(metadata, extra_dependencies=()): return metadata_file -def construct_metadata_file_2_3(metadata, extra_dependencies=()): +def construct_metadata_file_2_3(metadata: ProjectMetadata, extra_dependencies: tuple[str] | None = None) -> str: """ https://peps.python.org/pep-0639/ """ diff --git a/backend/src/hatchling/metadata/utils.py b/backend/src/hatchling/metadata/utils.py index 3a64fdc1b..f1d7b0066 100644 --- a/backend/src/hatchling/metadata/utils.py +++ b/backend/src/hatchling/metadata/utils.py @@ -1,25 +1,27 @@ from __future__ import annotations import re -from typing import TYPE_CHECKING +from typing import TYPE_CHECKING, Any if TYPE_CHECKING: from packaging.requirements import Requirement + from hatchling.metadata.core import ProjectMetadata + # NOTE: this module should rarely be changed because it is likely to be used by other packages like Hatch -def is_valid_project_name(project_name: str): +def is_valid_project_name(project_name: str) -> bool: # https://peps.python.org/pep-0508/#names return re.search('^([A-Z0-9]|[A-Z0-9][A-Z0-9._-]*[A-Z0-9])$', project_name, re.IGNORECASE) is not None -def normalize_project_name(project_name: str): +def normalize_project_name(project_name: str) -> str: # https://peps.python.org/pep-0503/#normalized-names return re.sub(r'[-_.]+', '-', project_name).lower() -def get_normalized_dependency(requirement: Requirement): +def get_normalized_dependency(requirement: Requirement) -> str: # Changes to this function affect reproducibility between versions from packaging.specifiers import SpecifierSet @@ -35,7 +37,7 @@ def get_normalized_dependency(requirement: Requirement): return str(requirement).replace('"', "'") -def resolve_metadata_fields(metadata): +def resolve_metadata_fields(metadata: ProjectMetadata) -> dict[str, Any]: # https://packaging.python.org/en/latest/specifications/declaring-project-metadata/ return { 'name': metadata.core.name, diff --git a/backend/src/hatchling/plugin/manager.py b/backend/src/hatchling/plugin/manager.py index 5a7b9425d..0b147b999 100644 --- a/backend/src/hatchling/plugin/manager.py +++ b/backend/src/hatchling/plugin/manager.py @@ -1,66 +1,72 @@ +from __future__ import annotations + +from typing import Callable + import pluggy class PluginManager: - def __init__(self): + def __init__(self) -> None: self.manager = pluggy.PluginManager('hatch') self.third_party_plugins = ThirdPartyPlugins(self.manager) self.initialized = False - def initialize(self): + def initialize(self) -> None: from hatchling.plugin import specs self.manager.add_hookspecs(specs) - def __getattr__(self, name): + def __getattr__(self, name: str) -> 'ClassRegister': if not self.initialized: self.initialize() self.initialized = True hook_name = f'hatch_register_{name}' - getattr(self, hook_name, None)() + hook = getattr(self, hook_name, None) + if hook: + hook() register = ClassRegister(getattr(self.manager.hook, hook_name), 'PLUGIN_NAME', self.third_party_plugins) setattr(self, name, register) return register - def hatch_register_version_source(self): + def hatch_register_version_source(self) -> None: from hatchling.version.source.plugin import hooks self.manager.register(hooks) - def hatch_register_version_scheme(self): + def hatch_register_version_scheme(self) -> None: from hatchling.version.scheme.plugin import hooks self.manager.register(hooks) - def hatch_register_builder(self): + def hatch_register_builder(self) -> None: from hatchling.builders.plugin import hooks self.manager.register(hooks) - def hatch_register_build_hook(self): + def hatch_register_build_hook(self) -> None: from hatchling.builders.hooks.plugin import hooks self.manager.register(hooks) - def hatch_register_metadata_hook(self): + def hatch_register_metadata_hook(self) -> None: from hatchling.metadata.plugin import hooks self.manager.register(hooks) class ClassRegister: - def __init__(self, registration_method, identifier, third_party_plugins): + def __init__(self, registration_method: Callable, identifier: str, third_party_plugins: ThirdPartyPlugins) -> None: self.registration_method = registration_method self.identifier = identifier self.third_party_plugins = third_party_plugins - def collect(self, *, include_third_party=True): + def collect(self, *, include_third_party: bool = True) -> dict: if include_third_party and not self.third_party_plugins.loaded: self.third_party_plugins.load() - classes = {} + classes: dict[str, type] = {} for registered_classes in self.registration_method(): if not isinstance(registered_classes, list): @@ -80,7 +86,7 @@ def collect(self, *, include_third_party=True): return classes - def get(self, name): + def get(self, name: str) -> type | None: if not self.third_party_plugins.loaded: classes = self.collect(include_third_party=False) if name in classes: @@ -90,10 +96,10 @@ def get(self, name): class ThirdPartyPlugins: - def __init__(self, manager): + def __init__(self, manager: pluggy.PluginManager) -> None: self.manager = manager self.loaded = False - def load(self): + def load(self) -> None: self.manager.load_setuptools_entrypoints('hatch') self.loaded = True diff --git a/backend/src/hatchling/plugin/specs.py b/backend/src/hatchling/plugin/specs.py index 5c534b3f8..0fd12d23e 100644 --- a/backend/src/hatchling/plugin/specs.py +++ b/backend/src/hatchling/plugin/specs.py @@ -4,20 +4,20 @@ @hookspec -def hatch_register_version_source(): +def hatch_register_version_source() -> None: """Register new classes that adhere to the version source interface.""" @hookspec -def hatch_register_builder(): +def hatch_register_builder() -> None: """Register new classes that adhere to the builder interface.""" @hookspec -def hatch_register_build_hook(): +def hatch_register_build_hook() -> None: """Register new classes that adhere to the build hook interface.""" @hookspec -def hatch_register_metadata_hook(): +def hatch_register_metadata_hook() -> None: """Register new classes that adhere to the metadata hook interface.""" diff --git a/backend/src/hatchling/plugin/utils.py b/backend/src/hatchling/plugin/utils.py index bfb523599..940ff335c 100644 --- a/backend/src/hatchling/plugin/utils.py +++ b/backend/src/hatchling/plugin/utils.py @@ -1,8 +1,20 @@ -def load_plugin_from_script(path, script_name, plugin_class, plugin_id): +from __future__ import annotations + +from typing import TYPE_CHECKING, TypeVar + +if TYPE_CHECKING: + from hatchling.builders.hooks.plugin.interface import BuildHookInterface + from hatchling.builders.plugin.interface import BuilderInterface + from hatchling.metadata.plugin.interface import MetadataHookInterface + + T = TypeVar('T', BuilderInterface, BuildHookInterface, MetadataHookInterface) + + +def load_plugin_from_script(path: str, script_name: str, plugin_class: type[T], plugin_id: str) -> type[T]: import importlib - spec = importlib.util.spec_from_file_location(script_name, path) - module = importlib.util.module_from_spec(spec) + spec = importlib.util.spec_from_file_location(script_name, path) # type: ignore + module = importlib.util.module_from_spec(spec) # type: ignore spec.loader.exec_module(module) plugin_finder = f'get_{plugin_id}' diff --git a/pyproject.toml b/pyproject.toml index 9d225b446..000042efb 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -150,15 +150,7 @@ namespace_packages = true [[tool.mypy.overrides]] module = [ - "*.hatchling.build", - "*.hatchling.ouroboros", - "*.hatchling.bridge.*", - "*.hatchling.licenses.*", - "*.hatchling.builders.*", - "*.hatchling.dep.*", - "*.hatchling.utils.*", - "*.hatchling.cli.*", - "*.hatchling.version.*", + "*.hatchling.*", ] disallow_untyped_defs = true disallow_incomplete_defs = true