Skip to content

Improve coverage. #481

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

Merged
merged 4 commits into from
Nov 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 0 additions & 6 deletions .coveragerc

This file was deleted.

1 change: 1 addition & 0 deletions docs/source/changes.md
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
- {pull}`479` gives skips a higher precedence as an outcome than ancestor failed.
- {pull}`480` removes the check for missing root nodes from the generation of the DAG.
It is delegated to the check during the execution.
- {pull}`481` improves coverage.

## 0.4.1 - 2023-10-11

Expand Down
8 changes: 8 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -193,3 +193,11 @@ python_version = "3.8"

[tool.check-manifest]
ignore = ["src/_pytask/_version.py"]

[tool.coverage.report]
exclude_also = [
"pragma: no cover",
"if TYPE_CHECKING.*:",
"\\.\\.\\.",
"def __repr__",
]
4 changes: 2 additions & 2 deletions src/_pytask/_hashlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
from typing import Any


if sys.version_info >= (3, 11):
if sys.version_info >= (3, 11): # pragma: no cover
from hashlib import file_digest
else:
else: # pragma: no cover
# This tuple and __get_builtin_constructor() must be modified if a new
# always available algorithm is added.
__always_supported = (
Expand Down
4 changes: 2 additions & 2 deletions src/_pytask/_inspect.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,9 +11,9 @@
__all__ = ["get_annotations"]


if sys.version_info >= (3, 10):
if sys.version_info >= (3, 10): # pragma: no cover
from inspect import get_annotations
else:
else: # pragma: no cover

def get_annotations( # noqa: C901, PLR0912, PLR0915
obj: Callable[..., object] | type[Any] | types.ModuleType,
Expand Down
4 changes: 2 additions & 2 deletions src/_pytask/clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ def clean(**raw_config: Any) -> NoReturn: # noqa: C901, PLR0912
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
session = Session.from_config(config)

except Exception: # noqa: BLE001
except Exception: # noqa: BLE001 # pragma: no cover
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)
console.print(Traceback(sys.exc_info()))

Expand Down Expand Up @@ -160,7 +160,7 @@ def clean(**raw_config: Any) -> NoReturn: # noqa: C901, PLR0912
session.exit_code = ExitCode.COLLECTION_FAILED
console.rule(style="failed")

except Exception: # noqa: BLE001
except Exception: # noqa: BLE001 # pragma: no cover
console.print(Traceback(sys.exc_info()))
console.rule(style="failed")
session.exit_code = ExitCode.FAILED
Expand Down
4 changes: 2 additions & 2 deletions src/_pytask/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -20,9 +20,9 @@
}


if parse_version(click.__version__) < parse_version("8"):
if parse_version(click.__version__) < parse_version("8"): # pragma: no cover
_VERSION_OPTION_KWARGS: dict[str, Any] = {}
else:
else: # pragma: no cover
_VERSION_OPTION_KWARGS = {"package_name": "pytask"}


Expand Down
4 changes: 2 additions & 2 deletions src/_pytask/collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -63,7 +63,7 @@ def pytask_collect(session: Session) -> bool:

try:
session.hook.pytask_collect_modify_tasks(session=session, tasks=session.tasks)
except Exception: # noqa: BLE001
except Exception: # noqa: BLE001 # pragma: no cover
report = CollectionReport.from_exception(
outcome=CollectionOutcome.FAIL, exc_info=sys.exc_info()
)
Expand Down Expand Up @@ -370,7 +370,7 @@ def _raise_error_if_casing_of_path_is_wrong(
path: Path, check_casing_of_paths: bool
) -> None:
"""Raise an error if the path does not have the correct casing."""
if (
if ( # pragma: no cover
not IS_FILE_SYSTEM_CASE_SENSITIVE
and sys.platform == "win32"
and check_casing_of_paths
Expand Down
8 changes: 4 additions & 4 deletions src/_pytask/collect_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ def collect(**raw_config: Any | None) -> NoReturn:
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
session = Session.from_config(config)

except (ConfigurationError, Exception):
except (ConfigurationError, Exception): # pragma: no cover
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)
console.print_exception()

Expand Down Expand Up @@ -90,13 +90,13 @@ def collect(**raw_config: Any | None) -> NoReturn:
console.print()
console.rule(style="neutral")

except CollectionError:
except CollectionError: # pragma: no cover
session.exit_code = ExitCode.COLLECTION_FAILED

except ResolvingDependenciesError:
except ResolvingDependenciesError: # pragma: no cover
session.exit_code = ExitCode.DAG_FAILED

except Exception: # noqa: BLE001
except Exception: # noqa: BLE001 # pragma: no cover
session.exit_code = ExitCode.FAILED
console.print_exception()
console.rule(style="failed")
Expand Down
8 changes: 4 additions & 4 deletions src/_pytask/collect_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@

if sys.version_info >= (3, 9):
from typing import Annotated
else:
else: # pragma: no cover
from typing_extensions import Annotated

if TYPE_CHECKING:
Expand Down Expand Up @@ -598,7 +598,7 @@ def _collect_decorator_node(
collected_node = session.hook.pytask_collect_node(
session=session, path=path, node_info=node_info
)
if collected_node is None:
if collected_node is None: # pragma: no cover
msg = f"{node!r} cannot be parsed as a {kind} for task {name!r} in {path!r}."
raise NodeNotCollectedError(msg)

Expand Down Expand Up @@ -628,7 +628,7 @@ def _collect_dependency(
collected_node = session.hook.pytask_collect_node(
session=session, path=path, node_info=node_info
)
if collected_node is None:
if collected_node is None: # pragma: no cover
msg = (
f"{node!r} cannot be parsed as a dependency for task {name!r} in {path!r}."
)
Expand Down Expand Up @@ -673,7 +673,7 @@ def _collect_product(
session=session, path=path, node_info=node_info
)

if collected_node is None:
if collected_node is None: # pragma: no cover
msg = (
f"{node!r} can't be parsed as a product for task {task_name!r} in {path!r}."
)
Expand Down
6 changes: 3 additions & 3 deletions src/_pytask/config_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
import click
from _pytask.shared import parse_paths

if sys.version_info >= (3, 11):
if sys.version_info >= (3, 11): # pragma: no cover
import tomllib
else:
else: # pragma: no cover
import tomli as tomllib


Expand Down Expand Up @@ -98,7 +98,7 @@ def find_project_root_and_config(
if path.exists():
try:
read_config(path)
except (tomllib.TOMLDecodeError, OSError) as e:
except (tomllib.TOMLDecodeError, OSError) as e: # pragma: no cover
raise click.FileError(
filename=str(path), hint=f"Error reading {path}:\n{e}"
) from None
Expand Down
8 changes: 4 additions & 4 deletions src/_pytask/dag_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,7 @@ def dag(**raw_config: Any) -> int:
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
session = Session.from_config(config)

except (ConfigurationError, Exception):
except (ConfigurationError, Exception): # pragma: no cover
console.print_exception()
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)

Expand All @@ -102,10 +102,10 @@ def dag(**raw_config: Any) -> int:
dag = _refine_dag(session)
_write_graph(dag, session.config["output_path"], session.config["layout"])

except CollectionError:
except CollectionError: # pragma: no cover
session.exit_code = ExitCode.COLLECTION_FAILED

except ResolvingDependenciesError:
except ResolvingDependenciesError: # pragma: no cover
session.exit_code = ExitCode.DAG_FAILED

except Exception: # noqa: BLE001
Expand Down Expand Up @@ -183,7 +183,7 @@ def build_dag(raw_config: dict[str, Any]) -> nx.DiGraph:

session = Session.from_config(config)

except (ConfigurationError, Exception):
except (ConfigurationError, Exception): # pragma: no cover
console.print_exception()
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)

Expand Down
2 changes: 1 addition & 1 deletion src/_pytask/data_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ def add(self, name: str, node: DataCatalog | PNode | None = None) -> None:
arg_name=name, path=(), value=node, task_path=None, task_name=""
),
)
if collected_node is None:
if collected_node is None: # pragma: no cover
msg = f"{node!r} cannot be parsed."
raise NodeNotCollectedError(msg)
self.entries[name] = collected_node
9 changes: 3 additions & 6 deletions src/_pytask/database_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,12 +40,9 @@ class State(BaseTable): # type: ignore[valid-type, misc]

def create_database(url: str) -> None:
"""Create the database."""
try:
engine = create_engine(url)
BaseTable.metadata.create_all(bind=engine)
DatabaseSession.configure(bind=engine)
except Exception:
raise
engine = create_engine(url)
BaseTable.metadata.create_all(bind=engine)
DatabaseSession.configure(bind=engine)


def _create_or_update_state(first_key: str, second_key: str, hash_: str) -> None:
Expand Down
4 changes: 2 additions & 2 deletions src/_pytask/execute.py
Original file line number Diff line number Diff line change
Expand Up @@ -102,7 +102,7 @@ def pytask_execute_task_protocol(session: Session, task: PTask) -> ExecutionRepo
session.hook.pytask_execute_task_setup(session=session, task=task)
session.hook.pytask_execute_task(session=session, task=task)
session.hook.pytask_execute_task_teardown(session=session, task=task)
except KeyboardInterrupt:
except KeyboardInterrupt: # pragma: no cover
short_exc_info = remove_traceback_from_exc_info(sys.exc_info())
report = ExecutionReport.from_task_and_exception(task, short_exc_info)
session.should_stop = True
Expand Down Expand Up @@ -253,7 +253,7 @@ def pytask_execute_task_process_report(
if session.n_tasks_failed >= session.config["max_failures"]:
session.should_stop = True

if report.exc_info and isinstance(report.exc_info[1], Exit):
if report.exc_info and isinstance(report.exc_info[1], Exit): # pragma: no cover
session.should_stop = True

return True
Expand Down
2 changes: 1 addition & 1 deletion src/_pytask/git.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ def get_root(cwd: Path) -> Path | None:
try:
_, stdout, _ = cmd_output("git", "rev-parse", "--show-cdup", cwd=cwd)
root = Path(cwd) / stdout.strip()
except subprocess.CalledProcessError:
except subprocess.CalledProcessError: # pragma: no cover
# User is not in git repo.
root = None
return root
2 changes: 1 addition & 1 deletion src/_pytask/mark/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ def markers(**raw_config: Any) -> NoReturn:
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
session = Session.from_config(config)

except (ConfigurationError, Exception):
except (ConfigurationError, Exception): # pragma: no cover
console.print_exception()
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)

Expand Down
4 changes: 2 additions & 2 deletions src/_pytask/mark/expression.py
Original file line number Diff line number Diff line change
Expand Up @@ -192,10 +192,10 @@ def __init__(self, matcher: Callable[[str], bool]) -> None:
def __getitem__(self, key: str) -> bool:
return self.matcher(key[len(IDENT_PREFIX) :])

def __iter__(self) -> Iterator[str]:
def __iter__(self) -> Iterator[str]: # pragma: no cover
raise NotImplementedError

def __len__(self) -> int:
def __len__(self) -> int: # pragma: no cover
raise NotImplementedError


Expand Down
10 changes: 5 additions & 5 deletions src/_pytask/mark/structures.py
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,7 @@ def normalize_mark_list(mark_list: Iterable[Mark | MarkDecorator]) -> list[Mark]
"""
extracted = [getattr(mark, "mark", mark) for mark in mark_list]
for mark in extracted:
if not isinstance(mark, Mark):
if not isinstance(mark, Mark): # pragma: no cover
msg = f"Got {mark!r} instead of Mark."
raise TypeError(msg)
return [x for x in extracted if isinstance(x, Mark)]
Expand Down Expand Up @@ -202,10 +202,6 @@ def __getattr__(self, name: str) -> MarkDecorator | Any:
# If the name is not in the set of known marks after updating,
# then it really is time to issue a warning or an error.
if self.config is not None and name not in self.config["markers"]:
if self.config["strict_markers"]:
msg = f"Unknown pytask.mark.{name}."
raise ValueError(msg)

if name in ("parametrize", "parameterize", "parametrise", "parameterise"):
msg = (
"@pytask.mark.parametrize has been removed since pytask v0.4. "
Expand All @@ -214,6 +210,10 @@ def __getattr__(self, name: str) -> MarkDecorator | Any:
)
raise NotImplementedError(msg) from None

if self.config["strict_markers"]:
msg = f"Unknown pytask.mark.{name}."
raise ValueError(msg)

warnings.warn(
f"Unknown pytask.mark.{name} - is this a typo? You can register "
"custom marks to avoid this warning.",
Expand Down
14 changes: 7 additions & 7 deletions tests/test_cache.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,21 +10,21 @@ def test_cache():
cache = Cache()

@cache.memoize
def func(a):
return a
def func(a, b):
return a + b

assert func.cache.cache_info.hits == 0
assert func.cache.cache_info.misses == 0

value = func(1)
assert value == 1
value = func(1, b=2)
assert value == 3
assert func.cache.cache_info.hits == 0
assert func.cache.cache_info.misses == 1

assert next(i for i in cache._cache.values()) == 1
assert next(i for i in cache._cache.values()) == 3

value = func(1)
assert value == 1
value = func(1, b=2)
assert value == 3
assert func.cache.cache_info.hits == 1
assert func.cache.cache_info.misses == 1

Expand Down
17 changes: 17 additions & 0 deletions tests/test_collect.py
Original file line number Diff line number Diff line change
Expand Up @@ -555,3 +555,20 @@ def task_example(
result = runner.invoke(cli, [tmp_path.as_posix()])
assert result.exit_code == ExitCode.OK
assert tmp_path.joinpath("subfolder", "out.txt").exists()


@pytest.mark.end_to_end()
def test_error_when_using_kwargs_and_node_in_annotation(runner, tmp_path):
source = """
from pathlib import Path
from pytask import task, Product
from typing_extensions import Annotated

@task(kwargs={"path": Path("file.txt")})
def task_example(path: Annotated[Path, Path("file.txt"), Product]) -> None: ...
"""
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))

result = runner.invoke(cli, [tmp_path.as_posix()])
assert result.exit_code == ExitCode.COLLECTION_FAILED
assert "is defined twice" in result.output
20 changes: 20 additions & 0 deletions tests/test_data_catalog.py
Original file line number Diff line number Diff line change
Expand Up @@ -187,3 +187,23 @@ def task_add_content() -> Annotated[str, data_catalog["new_content"]]:
result = runner.invoke(cli, [tmp_path.as_posix()])
assert result.exit_code == ExitCode.OK
assert len(list(tmp_path.joinpath(".data").iterdir())) == 2


@pytest.mark.unit()
def test_error_when_name_of_node_is_not_string():
data_catalog = DataCatalog()
with pytest.raises(TypeError, match="The name of a catalog entry"):
data_catalog.add(True, Path("file.txt"))


@pytest.mark.unit()
def test_requesting_new_node_with_python_node_as_default():
data_catalog = DataCatalog(default_node=PythonNode)
assert isinstance(data_catalog["node"], PythonNode)


@pytest.mark.unit()
def test_adding_a_python_node():
data_catalog = DataCatalog()
data_catalog.add("node", PythonNode(name="node", value=1))
assert isinstance(data_catalog["node"], PythonNode)
Loading