From 1a5a3552316bbebce66be8cf506f205bfc11a3f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Achim=20Ga=CC=88dke?= Date: Sat, 19 Nov 2022 21:39:01 +1300 Subject: [PATCH 1/3] use matrix build/test gh action --- .github/workflows/python-build-test.yml | 37 +++++++++++++++++++ ...da.yml => python-package-conda.ignore-yml} | 0 2 files changed, 37 insertions(+) create mode 100644 .github/workflows/python-build-test.yml rename .github/workflows/{python-package-conda.yml => python-package-conda.ignore-yml} (100%) diff --git a/.github/workflows/python-build-test.yml b/.github/workflows/python-build-test.yml new file mode 100644 index 0000000..bcfbde1 --- /dev/null +++ b/.github/workflows/python-build-test.yml @@ -0,0 +1,37 @@ +name: Python package + +on: [push] + +jobs: + build: + + runs-on: ubuntu-latest + strategy: + matrix: + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + + steps: + - uses: actions/checkout@v3 + + - name: Set up Python ${{ matrix.python-version }} + uses: actions/setup-python@v4 + + with: + python-version: ${{ matrix.python-version }} + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install flake8 pytest mypy black + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with flake8 + run: | + flake8 src/pyprojroot tests + - name: MyPy (type) checking + run: | + mypy --strict src/pyprojroot + - name: Format with black + run: | + black --check --diff src/pyprojroot tests + - name: Test with pytest + run: | + PYTHONPATH=src pytest diff --git a/.github/workflows/python-package-conda.yml b/.github/workflows/python-package-conda.ignore-yml similarity index 100% rename from .github/workflows/python-package-conda.yml rename to .github/workflows/python-package-conda.ignore-yml From 17df82bf9b53537bbbe31ec2c42f3d2b65ab73c5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Achim=20Ga=CC=88dke?= Date: Sat, 19 Nov 2022 21:39:23 +1300 Subject: [PATCH 2/3] make flake happy --- src/pyprojroot/__init__.py | 11 ++++++++++- src/pyprojroot/here.py | 2 +- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/src/pyprojroot/__init__.py b/src/pyprojroot/__init__.py index b17b019..62242aa 100644 --- a/src/pyprojroot/__init__.py +++ b/src/pyprojroot/__init__.py @@ -1,3 +1,12 @@ -from .criterion import * +from .criterion import as_root_criterion, has_dir, has_file from .root import find_root, find_root_with_reason from .here import here + +__all__ = [ + "as_root_criterion", + "find_root_with_reason", + "find_root", + "has_dir", + "has_file", + "here", +] diff --git a/src/pyprojroot/here.py b/src/pyprojroot/here.py index 4578aac..dda30c6 100644 --- a/src/pyprojroot/here.py +++ b/src/pyprojroot/here.py @@ -10,7 +10,7 @@ from os import PathLike as _PathLike from . import criterion -from .root import find_root, find_root_with_reason +from .root import find_root_with_reason CRITERIA = [ criterion.has_file(".here"), From 2f88ae8f7ca6e75c0d419888ae307663a947d823 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Achim=20Ga=CC=88dke?= Date: Sat, 19 Nov 2022 23:16:24 +1300 Subject: [PATCH 3/3] fixed mypy (mostly) --- src/pyprojroot/criterion.py | 31 +++++++++++++++++++++++-------- src/pyprojroot/here.py | 9 ++++++--- src/pyprojroot/root.py | 19 +++++++++++++------ 3 files changed, 42 insertions(+), 17 deletions(-) diff --git a/src/pyprojroot/criterion.py b/src/pyprojroot/criterion.py index a90094d..0d5a554 100644 --- a/src/pyprojroot/criterion.py +++ b/src/pyprojroot/criterion.py @@ -9,27 +9,42 @@ import typing from os import PathLike as _PathLike + +_PathType = typing.Union[_PathLike, str] +_CriterionType = typing.Union[ + typing.Callable[[_PathType], bool], + typing.Callable[[_pathlib.Path], bool], + _PathType, + _pathlib.Path, + typing.Iterable[typing.Callable[[_PathType], bool]], + typing.Iterable[typing.Callable[[_pathlib.Path], bool]], +] + # TODO: It would be nice to have a class that encapsulates these checks, # so that we can implement methods like |, !, &, ^ operators # TODO: Refactor in a way that allows creation of reasons -def as_root_criterion(criterion) -> typing.Callable: +def as_root_criterion( + criterion: _CriterionType, +) -> typing.Callable[[_pathlib.Path], bool]: if callable(criterion): return criterion # criterion must be a Collection, rather than just Iterable if isinstance(criterion, _PathLike): - criterion = [criterion] - criterion = list(criterion) + criterion_collection = [criterion] + else: + + criterion_collection = list(criterion) # type: ignore[arg-type] def f(path: _pathlib.Path) -> bool: - for c in criterion: + for c in criterion_collection: if isinstance(c, _PathLike): if (path / c).exists(): return True - else: + if callable(c): if c(path): return True return False @@ -37,7 +52,7 @@ def f(path: _pathlib.Path) -> bool: return f -def has_file(file: _PathLike) -> typing.Callable: +def has_file(file: _PathType) -> typing.Callable[[_pathlib.Path], bool]: """ Check that specified file exists in path. @@ -50,7 +65,7 @@ def f(path: _pathlib.Path) -> bool: return f -def has_dir(file: _PathLike) -> typing.Callable: +def has_dir(file: _PathType) -> typing.Callable[[_pathlib.Path], bool]: """ Check that specified directory exists. @@ -63,7 +78,7 @@ def f(path: _pathlib.Path) -> bool: return f -def matches_glob(pat: str) -> typing.Callable: +def matches_glob(pat: str) -> typing.Callable[[_pathlib.Path], bool]: """ Check that glob has at least one match. """ diff --git a/src/pyprojroot/here.py b/src/pyprojroot/here.py index dda30c6..c47cd2c 100644 --- a/src/pyprojroot/here.py +++ b/src/pyprojroot/here.py @@ -7,11 +7,12 @@ import pathlib as _pathlib import warnings as _warnings -from os import PathLike as _PathLike +import typing from . import criterion from .root import find_root_with_reason + CRITERIA = [ criterion.has_file(".here"), criterion.has_dir(".git"), @@ -26,7 +27,7 @@ ] -def get_here(): +def get_here() -> typing.Tuple[_pathlib.Path, str]: # TODO: This should only find_root once per session start = _pathlib.Path.cwd() path, reason = find_root_with_reason(CRITERIA, start=start) @@ -36,7 +37,9 @@ def get_here(): # TODO: Implement set_here -def here(relative_project_path: _PathLike = "", warn_missing=False) -> _pathlib.Path: +def here( + relative_project_path: criterion._PathType = "", warn_missing: bool = False +) -> _pathlib.Path: """ Returns the path relative to the projects root directory. :param relative_project_path: relative path from project root diff --git a/src/pyprojroot/root.py b/src/pyprojroot/root.py index b8ef64a..d0cac6a 100644 --- a/src/pyprojroot/root.py +++ b/src/pyprojroot/root.py @@ -7,12 +7,15 @@ import pathlib as _pathlib import typing as _typing -from os import PathLike as _PathLike -from .criterion import as_root_criterion as _as_root_criterion +from .criterion import ( + as_root_criterion as _as_root_criterion, + _CriterionType, + _PathType, +) -def as_start_path(start: _PathLike) -> _pathlib.Path: +def as_start_path(start: _typing.Union[None, _PathType]) -> _pathlib.Path: if start is None: return _pathlib.Path.cwd() if not isinstance(start, _pathlib.Path): @@ -22,7 +25,8 @@ def as_start_path(start: _PathLike) -> _pathlib.Path: def find_root_with_reason( - criterion, start: _PathLike = None + criterion: _CriterionType, + start: _typing.Union[None, _PathType] = None, ) -> _typing.Tuple[_pathlib.Path, str]: """ Find directory matching root criterion with reason. @@ -51,7 +55,10 @@ def find_root_with_reason( raise RuntimeError("Project root not found.") -def find_root(criterion, start: _PathLike = None, **kwargs) -> _pathlib.Path: +def find_root( + criterion: _CriterionType, + start: _typing.Union[None, _PathType] = None, +) -> _pathlib.Path: """ Find directory matching root criterion. @@ -59,7 +66,7 @@ def find_root(criterion, start: _PathLike = None, **kwargs) -> _pathlib.Path: matching root criterion. """ try: - root, _ = find_root_with_reason(criterion, start=start, **kwargs) + root, _ = find_root_with_reason(criterion, start=start) except RuntimeError as ex: raise ex else: