diff --git a/CHANGES b/CHANGES index 7689a105..956b527b 100644 --- a/CHANGES +++ b/CHANGES @@ -21,6 +21,17 @@ $ pipx install --suffix=@next 'vcspull' --pip-args '\--pre' --force +### Development + +#### chore: Implement PEP 563 deferred annotation resolution (#459) + +- Add `from __future__ import annotations` to defer annotation resolution and reduce unnecessary runtime computations during type checking. +- Enable Ruff checks for PEP-compliant annotations: + - [non-pep585-annotation (UP006)](https://docs.astral.sh/ruff/rules/non-pep585-annotation/) + - [non-pep604-annotation (UP007)](https://docs.astral.sh/ruff/rules/non-pep604-annotation/) + +For more details on PEP 563, see: https://peps.python.org/pep-0563/ + ## vcspull v1.33.0 (2024-11-23) _Maintenance only, no bug fixes, or new features_ diff --git a/conftest.py b/conftest.py index ffd28691..36e8cd3e 100644 --- a/conftest.py +++ b/conftest.py @@ -8,12 +8,16 @@ https://docs.pytest.org/en/stable/deprecations.html """ -import pathlib +from __future__ import annotations + import shutil import typing as t import pytest +if t.TYPE_CHECKING: + import pathlib + @pytest.fixture(autouse=True) def add_doctest_fixtures( diff --git a/docs/conf.py b/docs/conf.py index 6ebed909..981f34bd 100644 --- a/docs/conf.py +++ b/docs/conf.py @@ -1,6 +1,8 @@ """Sphinx configuration for vcspull documentation.""" # flake8: noqa: E501 +from __future__ import annotations + import inspect import pathlib import sys @@ -146,7 +148,7 @@ } -def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]: +def linkcode_resolve(domain: str, info: dict[str, str]) -> None | str: """ Determine the URL corresponding to Python object. @@ -216,13 +218,13 @@ def linkcode_resolve(domain: str, info: dict[str, str]) -> t.Union[None, str]: ) -def remove_tabs_js(app: "Sphinx", exc: Exception) -> None: +def remove_tabs_js(app: Sphinx, exc: Exception) -> None: """Fix for sphinx-inline-tabs#18.""" if app.builder.format == "html" and not exc: tabs_js = pathlib.Path(app.builder.outdir) / "_static" / "tabs.js" tabs_js.unlink(missing_ok=True) -def setup(app: "Sphinx") -> None: +def setup(app: Sphinx) -> None: """Sphinx setup hook.""" app.connect("build-finished", remove_tabs_js) diff --git a/pyproject.toml b/pyproject.toml index 54f55cbe..6463849c 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -178,6 +178,7 @@ exclude_lines = [ "if TYPE_CHECKING:", "if t.TYPE_CHECKING:", "@overload( |$)", + "from __future__ import annotations", ] [tool.ruff] @@ -201,10 +202,16 @@ select = [ "PERF", # Perflint "RUF", # Ruff-specific rules "D", # pydocstyle + "FA100", # future annotations ] ignore = [ "COM812", # missing trailing comma, ruff format conflict ] +extend-safe-fixes = [ + "UP006", + "UP007", +] +pyupgrade.keep-runtime-typing = false [tool.ruff.lint.pydocstyle] convention = "numpy" @@ -214,6 +221,9 @@ known-first-party = [ "vcspull", ] combine-as-imports = true +required-imports = [ + "from __future__ import annotations", +] [tool.ruff.lint.per-file-ignores] "*/__init__.py" = ["F401"] diff --git a/scripts/generate_gitlab.py b/scripts/generate_gitlab.py index d1b90a65..4452f610 100755 --- a/scripts/generate_gitlab.py +++ b/scripts/generate_gitlab.py @@ -1,18 +1,23 @@ #!/usr/bin/env python """Example script for export gitlab organization to vcspull config file.""" +from __future__ import annotations + import argparse import logging import os import pathlib import sys +import typing as t import requests import yaml from libvcs.sync.git import GitRemote from vcspull.cli.sync import CouldNotGuessVCSFromURL, guess_vcs -from vcspull.types import RawConfig + +if t.TYPE_CHECKING: + from vcspull.types import RawConfig log = logging.getLogger(__name__) logging.basicConfig(level=logging.INFO, format="%(message)s") diff --git a/src/vcspull/__about__.py b/src/vcspull/__about__.py index 056fbf0f..ff6e4314 100644 --- a/src/vcspull/__about__.py +++ b/src/vcspull/__about__.py @@ -1,5 +1,7 @@ """Metadata for vcspull.""" +from __future__ import annotations + __title__ = "vcspull" __package_name__ = "vcspull" __description__ = "Manage and sync multiple git, mercurial, and svn repos" diff --git a/src/vcspull/__init__.py b/src/vcspull/__init__.py index 303d0ed9..5c9da904 100644 --- a/src/vcspull/__init__.py +++ b/src/vcspull/__init__.py @@ -6,6 +6,8 @@ """ # Set default logging handler to avoid "No handler found" warnings. +from __future__ import annotations + import logging from logging import NullHandler diff --git a/src/vcspull/_internal/config_reader.py b/src/vcspull/_internal/config_reader.py index f51dbc8c..cc9f55e3 100644 --- a/src/vcspull/_internal/config_reader.py +++ b/src/vcspull/_internal/config_reader.py @@ -1,3 +1,5 @@ +from __future__ import annotations + import json import pathlib import typing as t @@ -22,11 +24,11 @@ class ConfigReader: '{\n "session_name": "my session"\n}' """ - def __init__(self, content: "RawConfigData") -> None: + def __init__(self, content: RawConfigData) -> None: self.content = content @staticmethod - def _load(fmt: "FormatLiteral", content: str) -> dict[str, t.Any]: + def _load(fmt: FormatLiteral, content: str) -> dict[str, t.Any]: """Load raw config data and directly return it. >>> ConfigReader._load("json", '{ "session_name": "my session" }') @@ -49,7 +51,7 @@ def _load(fmt: "FormatLiteral", content: str) -> dict[str, t.Any]: raise NotImplementedError(msg) @classmethod - def load(cls, fmt: "FormatLiteral", content: str) -> "ConfigReader": + def load(cls, fmt: FormatLiteral, content: str) -> ConfigReader: """Load raw config data into a ConfigReader instance (to dump later). >>> cfg = ConfigReader.load("json", '{ "session_name": "my session" }') @@ -118,7 +120,7 @@ def _from_file(cls, path: pathlib.Path) -> dict[str, t.Any]: ) @classmethod - def from_file(cls, path: pathlib.Path) -> "ConfigReader": + def from_file(cls, path: pathlib.Path) -> ConfigReader: r"""Load data from file path. **YAML file** @@ -159,8 +161,8 @@ def from_file(cls, path: pathlib.Path) -> "ConfigReader": @staticmethod def _dump( - fmt: "FormatLiteral", - content: "RawConfigData", + fmt: FormatLiteral, + content: RawConfigData, indent: int = 2, **kwargs: t.Any, ) -> str: @@ -187,7 +189,7 @@ def _dump( msg = f"{fmt} not supported in config" raise NotImplementedError(msg) - def dump(self, fmt: "FormatLiteral", indent: int = 2, **kwargs: t.Any) -> str: + def dump(self, fmt: FormatLiteral, indent: int = 2, **kwargs: t.Any) -> str: r"""Dump via ConfigReader instance. >>> cfg = ConfigReader({ "session_name": "my session" }) diff --git a/src/vcspull/cli/__init__.py b/src/vcspull/cli/__init__.py index cf9f9dfb..a4d2d303 100644 --- a/src/vcspull/cli/__init__.py +++ b/src/vcspull/cli/__init__.py @@ -1,5 +1,7 @@ """CLI utilities for vcspull.""" +from __future__ import annotations + import argparse import logging import textwrap @@ -41,7 +43,7 @@ def create_parser(return_subparsers: t.Literal[False]) -> argparse.ArgumentParse def create_parser( return_subparsers: bool = False, -) -> t.Union[argparse.ArgumentParser, tuple[argparse.ArgumentParser, t.Any]]: +) -> argparse.ArgumentParser | tuple[argparse.ArgumentParser, t.Any]: """Create CLI argument parser for vcspull.""" parser = argparse.ArgumentParser( prog="vcspull", @@ -76,7 +78,7 @@ def create_parser( return parser -def cli(_args: t.Optional[list[str]] = None) -> None: +def cli(_args: list[str] | None = None) -> None: """CLI entry point for vcspull.""" parser, sync_parser = create_parser(return_subparsers=True) args = parser.parse_args(_args) diff --git a/src/vcspull/cli/sync.py b/src/vcspull/cli/sync.py index affdcfd2..e38d2f94 100644 --- a/src/vcspull/cli/sync.py +++ b/src/vcspull/cli/sync.py @@ -1,21 +1,26 @@ """Synchronization functionality for vcspull.""" -import argparse +from __future__ import annotations + import logging -import pathlib import sys import typing as t from copy import deepcopy -from datetime import datetime from libvcs._internal.shortcuts import create_project -from libvcs._internal.types import VCSLiteral -from libvcs.sync.git import GitSync from libvcs.url import registry as url_tools from vcspull import exc from vcspull.config import filter_repos, find_config_files, load_configs +if t.TYPE_CHECKING: + import argparse + import pathlib + from datetime import datetime + + from libvcs._internal.types import VCSLiteral + from libvcs.sync.git import GitSync + log = logging.getLogger(__name__) @@ -63,9 +68,8 @@ def sync( repo_patterns: list[str], config: pathlib.Path, exit_on_error: bool, - parser: t.Optional[ - argparse.ArgumentParser - ] = None, # optional so sync can be unit tested + parser: argparse.ArgumentParser + | None = None, # optional so sync can be unit tested ) -> None: """Entry point for ``vcspull sync``.""" if isinstance(repo_patterns, list) and len(repo_patterns) == 0: @@ -117,7 +121,7 @@ def progress_cb(output: str, timestamp: datetime) -> None: sys.stdout.flush() -def guess_vcs(url: str) -> t.Optional[VCSLiteral]: +def guess_vcs(url: str) -> VCSLiteral | None: """Guess the VCS from a URL.""" vcs_matches = url_tools.registry.match(url=url, is_explicit=True) diff --git a/src/vcspull/config.py b/src/vcspull/config.py index 18a35cc5..74d4f60b 100644 --- a/src/vcspull/config.py +++ b/src/vcspull/config.py @@ -1,11 +1,12 @@ """Configuration functionality for vcspull.""" +from __future__ import annotations + import fnmatch import logging import os import pathlib import typing as t -from collections.abc import Callable from libvcs.sync.git import GitRemote @@ -18,6 +19,8 @@ log = logging.getLogger(__name__) if t.TYPE_CHECKING: + from collections.abc import Callable + from typing_extensions import TypeGuard from .types import ConfigDict, RawConfigDict @@ -25,7 +28,7 @@ def expand_dir( dir_: pathlib.Path, - cwd: t.Union[pathlib.Path, Callable[[], pathlib.Path]] = pathlib.Path.cwd, + cwd: pathlib.Path | Callable[[], pathlib.Path] = pathlib.Path.cwd, ) -> pathlib.Path: """Return path with environmental variables and tilde ~ expanded. @@ -52,9 +55,9 @@ def expand_dir( def extract_repos( - config: "RawConfigDict", - cwd: t.Union[pathlib.Path, Callable[[], pathlib.Path]] = pathlib.Path.cwd, -) -> list["ConfigDict"]: + config: RawConfigDict, + cwd: pathlib.Path | Callable[[], pathlib.Path] = pathlib.Path.cwd, +) -> list[ConfigDict]: """Return expanded configuration. end-user configuration permit inline configuration shortcuts, expand to @@ -130,7 +133,7 @@ def extract_repos( **url, ) - def is_valid_config_dict(val: t.Any) -> "TypeGuard[ConfigDict]": + def is_valid_config_dict(val: t.Any) -> TypeGuard[ConfigDict]: assert isinstance(val, dict) return True @@ -142,7 +145,7 @@ def is_valid_config_dict(val: t.Any) -> "TypeGuard[ConfigDict]": def find_home_config_files( - filetype: t.Optional[list[str]] = None, + filetype: list[str] | None = None, ) -> list[pathlib.Path]: """Return configs of ``.vcspull.{yaml,json}`` in user's home directory.""" if filetype is None: @@ -172,11 +175,11 @@ def find_home_config_files( def find_config_files( - path: t.Optional[t.Union[list[pathlib.Path], pathlib.Path]] = None, - match: t.Optional[t.Union[list[str], str]] = None, - filetype: t.Optional[ - t.Union[t.Literal["json", "yaml", "*"], list[t.Literal["json", "yaml", "*"]]] - ] = None, + path: list[pathlib.Path] | pathlib.Path | None = None, + match: list[str] | str | None = None, + filetype: t.Literal["json", "yaml", "*"] + | list[t.Literal["json", "yaml", "*"]] + | None = None, include_home: bool = False, ) -> list[pathlib.Path]: """Return repos from a directory and match. Not recursive. @@ -234,8 +237,8 @@ def find_config_files( def load_configs( files: list[pathlib.Path], - cwd: t.Union[pathlib.Path, Callable[[], pathlib.Path]] = pathlib.Path.cwd, -) -> list["ConfigDict"]: + cwd: pathlib.Path | Callable[[], pathlib.Path] = pathlib.Path.cwd, +) -> list[ConfigDict]: """Return repos from a list of files. Parameters @@ -284,8 +287,8 @@ def load_configs( def detect_duplicate_repos( - config1: list["ConfigDict"], - config2: list["ConfigDict"], + config1: list[ConfigDict], + config2: list[ConfigDict], ) -> list[ConfigDictTuple]: """Return duplicate repos dict if repo_dir same and vcs different. @@ -320,8 +323,8 @@ def detect_duplicate_repos( def in_dir( - config_dir: t.Optional[pathlib.Path] = None, - extensions: t.Optional[list[str]] = None, + config_dir: pathlib.Path | None = None, + extensions: list[str] | None = None, ) -> list[str]: """Return a list of configs in ``config_dir``. @@ -349,11 +352,11 @@ def in_dir( def filter_repos( - config: list["ConfigDict"], - path: t.Union[pathlib.Path, t.Literal["*"], str, None] = None, - vcs_url: t.Union[str, None] = None, - name: t.Union[str, None] = None, -) -> list["ConfigDict"]: + config: list[ConfigDict], + path: pathlib.Path | t.Literal["*"] | str | None = None, + vcs_url: str | None = None, + name: str | None = None, +) -> list[ConfigDict]: """Return a :py:obj:`list` list of repos from (expanded) config file. path, vcs_url and name all support fnmatch. @@ -402,7 +405,7 @@ def filter_repos( def is_config_file( filename: str, - extensions: t.Optional[t.Union[list[str], str]] = None, + extensions: list[str] | str | None = None, ) -> bool: """Return True if file has a valid config file type. diff --git a/src/vcspull/exc.py b/src/vcspull/exc.py index 4c858950..af8d936c 100644 --- a/src/vcspull/exc.py +++ b/src/vcspull/exc.py @@ -1,5 +1,7 @@ """Exceptions for vcspull.""" +from __future__ import annotations + class VCSPullException(Exception): """Standard exception raised by vcspull.""" diff --git a/src/vcspull/log.py b/src/vcspull/log.py index f35c0642..10e671f7 100644 --- a/src/vcspull/log.py +++ b/src/vcspull/log.py @@ -7,6 +7,8 @@ provided. """ +from __future__ import annotations + import logging import time import typing as t @@ -23,7 +25,7 @@ def setup_logger( - log: t.Optional[logging.Logger] = None, + log: logging.Logger | None = None, level: t.Literal["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"] = "INFO", ) -> None: """Configure vcspull logger for CLI use. diff --git a/src/vcspull/types.py b/src/vcspull/types.py index cb95d1c0..29217d34 100644 --- a/src/vcspull/types.py +++ b/src/vcspull/types.py @@ -1,12 +1,17 @@ """Typings for vcspull.""" -import pathlib +from __future__ import annotations + import typing as t -from libvcs._internal.types import StrPath, VCSLiteral -from libvcs.sync.git import GitSyncRemoteDict from typing_extensions import NotRequired, TypedDict +if t.TYPE_CHECKING: + import pathlib + + from libvcs._internal.types import StrPath, VCSLiteral + from libvcs.sync.git import GitSyncRemoteDict + class RawConfigDict(t.TypedDict): """Configuration dictionary without any type marshalling or variable resolution.""" @@ -25,12 +30,12 @@ class RawConfigDict(t.TypedDict): class ConfigDict(TypedDict): """Configuration map for vcspull after shorthands and variables resolved.""" - vcs: t.Optional[VCSLiteral] + vcs: VCSLiteral | None name: str path: pathlib.Path url: str - remotes: NotRequired[t.Optional[GitSyncRemoteDict]] - shell_command_after: NotRequired[t.Optional[list[str]]] + remotes: NotRequired[GitSyncRemoteDict | None] + shell_command_after: NotRequired[list[str] | None] ConfigDir = dict[str, ConfigDict] diff --git a/src/vcspull/util.py b/src/vcspull/util.py index bed87845..b755144c 100644 --- a/src/vcspull/util.py +++ b/src/vcspull/util.py @@ -1,5 +1,7 @@ """Utility functions for vcspull.""" +from __future__ import annotations + import os import pathlib import typing as t diff --git a/src/vcspull/validator.py b/src/vcspull/validator.py index 93af6b14..7e40366f 100644 --- a/src/vcspull/validator.py +++ b/src/vcspull/validator.py @@ -1,5 +1,7 @@ """Validation of vcspull configuration file.""" +from __future__ import annotations + import pathlib import typing as t @@ -9,7 +11,7 @@ from vcspull.types import RawConfigDict -def is_valid_config(config: dict[str, t.Any]) -> "TypeGuard[RawConfigDict]": +def is_valid_config(config: dict[str, t.Any]) -> TypeGuard[RawConfigDict]: """Return true and upcast if vcspull configuration file is valid.""" if not isinstance(config, dict): return False diff --git a/tests/__init__.py b/tests/__init__.py index 3884ee1f..af29a56c 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -1,3 +1,5 @@ """Tests for vcspull package.""" +from __future__ import annotations + from . import fixtures diff --git a/tests/fixtures/example.py b/tests/fixtures/example.py index 7f3742f6..e84d19ce 100644 --- a/tests/fixtures/example.py +++ b/tests/fixtures/example.py @@ -1,5 +1,7 @@ """Example fixture data for vcspull tests.""" +from __future__ import annotations + import pathlib import typing as t @@ -38,7 +40,7 @@ }, } -config_dict_expanded: list["ConfigDict"] = [ +config_dict_expanded: list[ConfigDict] = [ { "vcs": "git", "name": "linux", diff --git a/tests/helpers.py b/tests/helpers.py index a238efdc..b88b727d 100644 --- a/tests/helpers.py +++ b/tests/helpers.py @@ -1,11 +1,17 @@ """Helpers for vcspull.""" +from __future__ import annotations + import os -import pathlib import typing as t +from typing_extensions import Self + from vcspull._internal.config_reader import ConfigReader +if t.TYPE_CHECKING: + import pathlib + class EnvironmentVarGuard: """Class to help protect the environment variable properly. @@ -34,7 +40,7 @@ def unset(self, envvar: str) -> None: self._reset[envvar] = self._environ[envvar] del self._environ[envvar] - def __enter__(self) -> "EnvironmentVarGuard": + def __enter__(self) -> Self: """Context manager entry for setting and resetting environmental variable.""" return self diff --git a/tests/test_cli.py b/tests/test_cli.py index 29d47cc0..43c02d17 100644 --- a/tests/test_cli.py +++ b/tests/test_cli.py @@ -1,19 +1,22 @@ """Test CLI entry point for for vcspull.""" +from __future__ import annotations + import contextlib -import pathlib import shutil import typing as t import pytest import yaml -from libvcs.sync.git import GitSync from vcspull.__about__ import __version__ from vcspull.cli import cli from vcspull.cli.sync import EXIT_ON_ERROR_MSG, NO_REPOS_FOR_TERM_MSG if t.TYPE_CHECKING: + import pathlib + + from libvcs.sync.git import GitSync from typing_extensions import TypeAlias ExpectedOutput: TypeAlias = t.Optional[t.Union[str, list[str]]] @@ -28,10 +31,10 @@ class SyncCLINonExistentRepo(t.NamedTuple): # test parameters sync_args: list[str] expected_exit_code: int - expected_in_out: "ExpectedOutput" = None - expected_not_in_out: "ExpectedOutput" = None - expected_in_err: "ExpectedOutput" = None - expected_not_in_err: "ExpectedOutput" = None + expected_in_out: ExpectedOutput = None + expected_not_in_out: ExpectedOutput = None + expected_in_err: ExpectedOutput = None + expected_not_in_err: ExpectedOutput = None SYNC_CLI_EXISTENT_REPO_FIXTURES: list[SyncCLINonExistentRepo] = [ @@ -77,10 +80,10 @@ def test_sync_cli_filter_non_existent( test_id: str, sync_args: list[str], expected_exit_code: int, - expected_in_out: "ExpectedOutput", - expected_not_in_out: "ExpectedOutput", - expected_in_err: "ExpectedOutput", - expected_not_in_err: "ExpectedOutput", + expected_in_out: ExpectedOutput, + expected_not_in_out: ExpectedOutput, + expected_in_err: ExpectedOutput, + expected_not_in_err: ExpectedOutput, ) -> None: """Tests vcspull syncing when repo does not exist.""" config = { @@ -124,10 +127,10 @@ class SyncFixture(t.NamedTuple): # test params sync_args: list[str] expected_exit_code: int - expected_in_out: "ExpectedOutput" = None - expected_not_in_out: "ExpectedOutput" = None - expected_in_err: "ExpectedOutput" = None - expected_not_in_err: "ExpectedOutput" = None + expected_in_out: ExpectedOutput = None + expected_not_in_out: ExpectedOutput = None + expected_in_err: ExpectedOutput = None + expected_not_in_err: ExpectedOutput = None SYNC_REPO_FIXTURES: list[SyncFixture] = [ @@ -211,10 +214,10 @@ def test_sync( test_id: str, sync_args: list[str], expected_exit_code: int, - expected_in_out: "ExpectedOutput", - expected_not_in_out: "ExpectedOutput", - expected_in_err: "ExpectedOutput", - expected_not_in_err: "ExpectedOutput", + expected_in_out: ExpectedOutput, + expected_not_in_out: ExpectedOutput, + expected_in_err: ExpectedOutput, + expected_not_in_err: ExpectedOutput, ) -> None: """Tests for vcspull sync.""" config = { @@ -262,10 +265,10 @@ class SyncBrokenFixture(t.NamedTuple): # test params sync_args: list[str] expected_exit_code: int - expected_in_out: "ExpectedOutput" = None - expected_not_in_out: "ExpectedOutput" = None - expected_in_err: "ExpectedOutput" = None - expected_not_in_err: "ExpectedOutput" = None + expected_in_out: ExpectedOutput = None + expected_not_in_out: ExpectedOutput = None + expected_in_err: ExpectedOutput = None + expected_not_in_err: ExpectedOutput = None SYNC_BROKEN_REPO_FIXTURES: list[SyncBrokenFixture] = [ @@ -347,10 +350,10 @@ def test_sync_broken( test_id: str, sync_args: list[str], expected_exit_code: int, - expected_in_out: "ExpectedOutput", - expected_not_in_out: "ExpectedOutput", - expected_in_err: "ExpectedOutput", - expected_not_in_err: "ExpectedOutput", + expected_in_out: ExpectedOutput, + expected_not_in_out: ExpectedOutput, + expected_in_err: ExpectedOutput, + expected_not_in_err: ExpectedOutput, ) -> None: """Tests for syncing in vcspull when unexpected error occurs.""" github_projects = user_path / "github_projects" diff --git a/tests/test_config.py b/tests/test_config.py index 8dfea4d3..9baaea13 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -1,6 +1,7 @@ """Tests for vcspull configuration format.""" -import pathlib +from __future__ import annotations + import typing as t import pytest @@ -8,6 +9,8 @@ from vcspull import config if t.TYPE_CHECKING: + import pathlib + from vcspull.types import ConfigDict @@ -19,7 +22,7 @@ def __call__( content: str, path: str = "randomdir", filename: str = "randomfilename.yaml", - ) -> tuple[pathlib.Path, list[t.Union[t.Any, pathlib.Path]], list["ConfigDict"]]: + ) -> tuple[pathlib.Path, list[t.Any | pathlib.Path], list[ConfigDict]]: """Callable function type signature for load_yaml pytest fixture.""" ... @@ -32,7 +35,7 @@ def fn( content: str, path: str = "randomdir", filename: str = "randomfilename.yaml", - ) -> tuple[pathlib.Path, list[pathlib.Path], list["ConfigDict"]]: + ) -> tuple[pathlib.Path, list[pathlib.Path], list[ConfigDict]]: """Return vcspull configurations and write out config to temp directory.""" dir_ = tmp_path / path dir_.mkdir() diff --git a/tests/test_config_file.py b/tests/test_config_file.py index b273f6df..ed59ca3f 100644 --- a/tests/test_config_file.py +++ b/tests/test_config_file.py @@ -1,5 +1,7 @@ """Tests for vcspull configuration files.""" +from __future__ import annotations + import os import pathlib import textwrap diff --git a/tests/test_repo.py b/tests/test_repo.py index 079116fb..f6ccd49a 100644 --- a/tests/test_repo.py +++ b/tests/test_repo.py @@ -1,6 +1,8 @@ """Tests for placing config dicts into :py:class:`Project` objects.""" -import pathlib +from __future__ import annotations + +import typing as t from libvcs import BaseSync, GitSync, HgSync, SvnSync from libvcs._internal.shortcuts import create_project @@ -9,6 +11,9 @@ from .fixtures import example as fixtures +if t.TYPE_CHECKING: + import pathlib + def test_filter_dir() -> None: """`filter_repos` filter by dir.""" diff --git a/tests/test_sync.py b/tests/test_sync.py index 8777b912..e7a379ed 100644 --- a/tests/test_sync.py +++ b/tests/test_sync.py @@ -1,12 +1,12 @@ """Tests for sync functionality of vcspull.""" -import pathlib +from __future__ import annotations + import textwrap import typing as t import pytest from libvcs._internal.shortcuts import create_project -from libvcs.pytest_plugin import CreateRepoPytestFixtureFn from libvcs.sync.git import GitRemote, GitSync from vcspull._internal.config_reader import ConfigReader @@ -17,6 +17,10 @@ from .helpers import write_config if t.TYPE_CHECKING: + import pathlib + + from libvcs.pytest_plugin import CreateRepoPytestFixtureFn + from vcspull.types import ConfigDict diff --git a/tests/test_utils.py b/tests/test_utils.py index b4799fe9..f1875b98 100644 --- a/tests/test_utils.py +++ b/tests/test_utils.py @@ -1,11 +1,16 @@ """Tests for vcspull utilities.""" -import pathlib +from __future__ import annotations -import pytest +import typing as t from vcspull.util import get_config_dir +if t.TYPE_CHECKING: + import pathlib + + import pytest + def test_vcspull_configdir_env_var( tmp_path: pathlib.Path,