-
Notifications
You must be signed in to change notification settings - Fork 16
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
#27 - mypy fixup, adds to #24 - unblocks #31 #32
Changes from 1 commit
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -9,35 +9,50 @@ | |
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], | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I don't think you need to duplicate these for |
||
_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, | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This type hint is quite complicated. Maybe it would be better replaced with something using functools.singledispatch: https://docs.python.org/3/library/functools.html#functools.singledispatch. That's probably a lot for this PR though. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. But I don't know if it's worth making an alias for this, since it's only used once. The purpose of this function is to take various inputs and standardise them as a callable version. |
||
) -> typing.Callable[[_pathlib.Path], bool]: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Probably worth making an alias for this output type, since it's reused. |
||
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] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Maybe it's better to just return the right function directly here and then handle the iterable input separately.
|
||
else: | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I would remove the extra blank line here: it's unnecessary. |
||
criterion_collection = list(criterion) # type: ignore[arg-type] | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. For anyone that's interested, this needs to be converted into a list here from a generic iterable since we need to be able to restart the iterator. |
||
|
||
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): | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I should revert this to I ended up giving up here - see the ignore instruction. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think, ideally, this logic would all be encapsulated with classes. But I think this was just a simple version that works. But making something that's not confusing for mypy shouldn't be the first objective. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we leave the There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Perhaps it needs a type hint for the |
||
if c(path): | ||
return True | ||
return False | ||
|
||
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. | ||
""" | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -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: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think it's better to write |
||
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,15 +55,18 @@ 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. | ||
|
||
Recursively search parents of start path for directory | ||
matching root criterion. | ||
""" | ||
try: | ||
root, _ = find_root_with_reason(criterion, start=start, **kwargs) | ||
root, _ = find_root_with_reason(criterion, start=start) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I'd prefer it if the extra |
||
except RuntimeError as ex: | ||
raise ex | ||
else: | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think (but I'm not sure) that "PathLike" includes "str". Can you check this?