Skip to content

Commit 4e39b68

Browse files
authored
Improve coverage. (#481)
1 parent 7d20db2 commit 4e39b68

24 files changed

+122
-57
lines changed

.coveragerc

-6
This file was deleted.

docs/source/changes.md

+1
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and
2828
- {pull}`479` gives skips a higher precedence as an outcome than ancestor failed.
2929
- {pull}`480` removes the check for missing root nodes from the generation of the DAG.
3030
It is delegated to the check during the execution.
31+
- {pull}`481` improves coverage.
3132

3233
## 0.4.1 - 2023-10-11
3334

pyproject.toml

+8
Original file line numberDiff line numberDiff line change
@@ -193,3 +193,11 @@ python_version = "3.8"
193193

194194
[tool.check-manifest]
195195
ignore = ["src/_pytask/_version.py"]
196+
197+
[tool.coverage.report]
198+
exclude_also = [
199+
"pragma: no cover",
200+
"if TYPE_CHECKING.*:",
201+
"\\.\\.\\.",
202+
"def __repr__",
203+
]

src/_pytask/_hashlib.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,9 +7,9 @@
77
from typing import Any
88

99

10-
if sys.version_info >= (3, 11):
10+
if sys.version_info >= (3, 11): # pragma: no cover
1111
from hashlib import file_digest
12-
else:
12+
else: # pragma: no cover
1313
# This tuple and __get_builtin_constructor() must be modified if a new
1414
# always available algorithm is added.
1515
__always_supported = (

src/_pytask/_inspect.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -11,9 +11,9 @@
1111
__all__ = ["get_annotations"]
1212

1313

14-
if sys.version_info >= (3, 10):
14+
if sys.version_info >= (3, 10): # pragma: no cover
1515
from inspect import get_annotations
16-
else:
16+
else: # pragma: no cover
1717

1818
def get_annotations( # noqa: C901, PLR0912, PLR0915
1919
obj: Callable[..., object] | type[Any] | types.ModuleType,

src/_pytask/clean.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -106,7 +106,7 @@ def clean(**raw_config: Any) -> NoReturn: # noqa: C901, PLR0912
106106
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
107107
session = Session.from_config(config)
108108

109-
except Exception: # noqa: BLE001
109+
except Exception: # noqa: BLE001 # pragma: no cover
110110
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)
111111
console.print(Traceback(sys.exc_info()))
112112

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

163-
except Exception: # noqa: BLE001
163+
except Exception: # noqa: BLE001 # pragma: no cover
164164
console.print(Traceback(sys.exc_info()))
165165
console.rule(style="failed")
166166
session.exit_code = ExitCode.FAILED

src/_pytask/cli.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -20,9 +20,9 @@
2020
}
2121

2222

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

2828

src/_pytask/collect.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -63,7 +63,7 @@ def pytask_collect(session: Session) -> bool:
6363

6464
try:
6565
session.hook.pytask_collect_modify_tasks(session=session, tasks=session.tasks)
66-
except Exception: # noqa: BLE001
66+
except Exception: # noqa: BLE001 # pragma: no cover
6767
report = CollectionReport.from_exception(
6868
outcome=CollectionOutcome.FAIL, exc_info=sys.exc_info()
6969
)
@@ -370,7 +370,7 @@ def _raise_error_if_casing_of_path_is_wrong(
370370
path: Path, check_casing_of_paths: bool
371371
) -> None:
372372
"""Raise an error if the path does not have the correct casing."""
373-
if (
373+
if ( # pragma: no cover
374374
not IS_FILE_SYSTEM_CASE_SENSITIVE
375375
and sys.platform == "win32"
376376
and check_casing_of_paths

src/_pytask/collect_command.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def collect(**raw_config: Any | None) -> NoReturn:
6262
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
6363
session = Session.from_config(config)
6464

65-
except (ConfigurationError, Exception):
65+
except (ConfigurationError, Exception): # pragma: no cover
6666
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)
6767
console.print_exception()
6868

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

93-
except CollectionError:
93+
except CollectionError: # pragma: no cover
9494
session.exit_code = ExitCode.COLLECTION_FAILED
9595

96-
except ResolvingDependenciesError:
96+
except ResolvingDependenciesError: # pragma: no cover
9797
session.exit_code = ExitCode.DAG_FAILED
9898

99-
except Exception: # noqa: BLE001
99+
except Exception: # noqa: BLE001 # pragma: no cover
100100
session.exit_code = ExitCode.FAILED
101101
console.print_exception()
102102
console.rule(style="failed")

src/_pytask/collect_utils.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@
3434

3535
if sys.version_info >= (3, 9):
3636
from typing import Annotated
37-
else:
37+
else: # pragma: no cover
3838
from typing_extensions import Annotated
3939

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

@@ -628,7 +628,7 @@ def _collect_dependency(
628628
collected_node = session.hook.pytask_collect_node(
629629
session=session, path=path, node_info=node_info
630630
)
631-
if collected_node is None:
631+
if collected_node is None: # pragma: no cover
632632
msg = (
633633
f"{node!r} cannot be parsed as a dependency for task {name!r} in {path!r}."
634634
)
@@ -673,7 +673,7 @@ def _collect_product(
673673
session=session, path=path, node_info=node_info
674674
)
675675

676-
if collected_node is None:
676+
if collected_node is None: # pragma: no cover
677677
msg = (
678678
f"{node!r} can't be parsed as a product for task {task_name!r} in {path!r}."
679679
)

src/_pytask/config_utils.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,9 @@
1010
import click
1111
from _pytask.shared import parse_paths
1212

13-
if sys.version_info >= (3, 11):
13+
if sys.version_info >= (3, 11): # pragma: no cover
1414
import tomllib
15-
else:
15+
else: # pragma: no cover
1616
import tomli as tomllib
1717

1818

@@ -98,7 +98,7 @@ def find_project_root_and_config(
9898
if path.exists():
9999
try:
100100
read_config(path)
101-
except (tomllib.TOMLDecodeError, OSError) as e:
101+
except (tomllib.TOMLDecodeError, OSError) as e: # pragma: no cover
102102
raise click.FileError(
103103
filename=str(path), hint=f"Error reading {path}:\n{e}"
104104
) from None

src/_pytask/dag_command.py

+4-4
Original file line numberDiff line numberDiff line change
@@ -84,7 +84,7 @@ def dag(**raw_config: Any) -> int:
8484
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
8585
session = Session.from_config(config)
8686

87-
except (ConfigurationError, Exception):
87+
except (ConfigurationError, Exception): # pragma: no cover
8888
console.print_exception()
8989
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)
9090

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

105-
except CollectionError:
105+
except CollectionError: # pragma: no cover
106106
session.exit_code = ExitCode.COLLECTION_FAILED
107107

108-
except ResolvingDependenciesError:
108+
except ResolvingDependenciesError: # pragma: no cover
109109
session.exit_code = ExitCode.DAG_FAILED
110110

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

184184
session = Session.from_config(config)
185185

186-
except (ConfigurationError, Exception):
186+
except (ConfigurationError, Exception): # pragma: no cover
187187
console.print_exception()
188188
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)
189189

src/_pytask/data_catalog.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -122,7 +122,7 @@ def add(self, name: str, node: DataCatalog | PNode | None = None) -> None:
122122
arg_name=name, path=(), value=node, task_path=None, task_name=""
123123
),
124124
)
125-
if collected_node is None:
125+
if collected_node is None: # pragma: no cover
126126
msg = f"{node!r} cannot be parsed."
127127
raise NodeNotCollectedError(msg)
128128
self.entries[name] = collected_node

src/_pytask/database_utils.py

+3-6
Original file line numberDiff line numberDiff line change
@@ -40,12 +40,9 @@ class State(BaseTable): # type: ignore[valid-type, misc]
4040

4141
def create_database(url: str) -> None:
4242
"""Create the database."""
43-
try:
44-
engine = create_engine(url)
45-
BaseTable.metadata.create_all(bind=engine)
46-
DatabaseSession.configure(bind=engine)
47-
except Exception:
48-
raise
43+
engine = create_engine(url)
44+
BaseTable.metadata.create_all(bind=engine)
45+
DatabaseSession.configure(bind=engine)
4946

5047

5148
def _create_or_update_state(first_key: str, second_key: str, hash_: str) -> None:

src/_pytask/execute.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,7 @@ def pytask_execute_task_protocol(session: Session, task: PTask) -> ExecutionRepo
102102
session.hook.pytask_execute_task_setup(session=session, task=task)
103103
session.hook.pytask_execute_task(session=session, task=task)
104104
session.hook.pytask_execute_task_teardown(session=session, task=task)
105-
except KeyboardInterrupt:
105+
except KeyboardInterrupt: # pragma: no cover
106106
short_exc_info = remove_traceback_from_exc_info(sys.exc_info())
107107
report = ExecutionReport.from_task_and_exception(task, short_exc_info)
108108
session.should_stop = True
@@ -253,7 +253,7 @@ def pytask_execute_task_process_report(
253253
if session.n_tasks_failed >= session.config["max_failures"]:
254254
session.should_stop = True
255255

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

259259
return True

src/_pytask/git.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,7 @@ def get_root(cwd: Path) -> Path | None:
5050
try:
5151
_, stdout, _ = cmd_output("git", "rev-parse", "--show-cdup", cwd=cwd)
5252
root = Path(cwd) / stdout.strip()
53-
except subprocess.CalledProcessError:
53+
except subprocess.CalledProcessError: # pragma: no cover
5454
# User is not in git repo.
5555
root = None
5656
return root

src/_pytask/mark/__init__.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@ def markers(**raw_config: Any) -> NoReturn:
5454
config = pm.hook.pytask_configure(pm=pm, raw_config=raw_config)
5555
session = Session.from_config(config)
5656

57-
except (ConfigurationError, Exception):
57+
except (ConfigurationError, Exception): # pragma: no cover
5858
console.print_exception()
5959
session = Session(exit_code=ExitCode.CONFIGURATION_FAILED)
6060

src/_pytask/mark/expression.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -192,10 +192,10 @@ def __init__(self, matcher: Callable[[str], bool]) -> None:
192192
def __getitem__(self, key: str) -> bool:
193193
return self.matcher(key[len(IDENT_PREFIX) :])
194194

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

198-
def __len__(self) -> int:
198+
def __len__(self) -> int: # pragma: no cover
199199
raise NotImplementedError
200200

201201

src/_pytask/mark/structures.py

+5-5
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@ def normalize_mark_list(mark_list: Iterable[Mark | MarkDecorator]) -> list[Mark]
140140
"""
141141
extracted = [getattr(mark, "mark", mark) for mark in mark_list]
142142
for mark in extracted:
143-
if not isinstance(mark, Mark):
143+
if not isinstance(mark, Mark): # pragma: no cover
144144
msg = f"Got {mark!r} instead of Mark."
145145
raise TypeError(msg)
146146
return [x for x in extracted if isinstance(x, Mark)]
@@ -202,10 +202,6 @@ def __getattr__(self, name: str) -> MarkDecorator | Any:
202202
# If the name is not in the set of known marks after updating,
203203
# then it really is time to issue a warning or an error.
204204
if self.config is not None and name not in self.config["markers"]:
205-
if self.config["strict_markers"]:
206-
msg = f"Unknown pytask.mark.{name}."
207-
raise ValueError(msg)
208-
209205
if name in ("parametrize", "parameterize", "parametrise", "parameterise"):
210206
msg = (
211207
"@pytask.mark.parametrize has been removed since pytask v0.4. "
@@ -214,6 +210,10 @@ def __getattr__(self, name: str) -> MarkDecorator | Any:
214210
)
215211
raise NotImplementedError(msg) from None
216212

213+
if self.config["strict_markers"]:
214+
msg = f"Unknown pytask.mark.{name}."
215+
raise ValueError(msg)
216+
217217
warnings.warn(
218218
f"Unknown pytask.mark.{name} - is this a typo? You can register "
219219
"custom marks to avoid this warning.",

tests/test_cache.py

+7-7
Original file line numberDiff line numberDiff line change
@@ -10,21 +10,21 @@ def test_cache():
1010
cache = Cache()
1111

1212
@cache.memoize
13-
def func(a):
14-
return a
13+
def func(a, b):
14+
return a + b
1515

1616
assert func.cache.cache_info.hits == 0
1717
assert func.cache.cache_info.misses == 0
1818

19-
value = func(1)
20-
assert value == 1
19+
value = func(1, b=2)
20+
assert value == 3
2121
assert func.cache.cache_info.hits == 0
2222
assert func.cache.cache_info.misses == 1
2323

24-
assert next(i for i in cache._cache.values()) == 1
24+
assert next(i for i in cache._cache.values()) == 3
2525

26-
value = func(1)
27-
assert value == 1
26+
value = func(1, b=2)
27+
assert value == 3
2828
assert func.cache.cache_info.hits == 1
2929
assert func.cache.cache_info.misses == 1
3030

tests/test_collect.py

+17
Original file line numberDiff line numberDiff line change
@@ -555,3 +555,20 @@ def task_example(
555555
result = runner.invoke(cli, [tmp_path.as_posix()])
556556
assert result.exit_code == ExitCode.OK
557557
assert tmp_path.joinpath("subfolder", "out.txt").exists()
558+
559+
560+
@pytest.mark.end_to_end()
561+
def test_error_when_using_kwargs_and_node_in_annotation(runner, tmp_path):
562+
source = """
563+
from pathlib import Path
564+
from pytask import task, Product
565+
from typing_extensions import Annotated
566+
567+
@task(kwargs={"path": Path("file.txt")})
568+
def task_example(path: Annotated[Path, Path("file.txt"), Product]) -> None: ...
569+
"""
570+
tmp_path.joinpath("task_module.py").write_text(textwrap.dedent(source))
571+
572+
result = runner.invoke(cli, [tmp_path.as_posix()])
573+
assert result.exit_code == ExitCode.COLLECTION_FAILED
574+
assert "is defined twice" in result.output

tests/test_data_catalog.py

+20
Original file line numberDiff line numberDiff line change
@@ -187,3 +187,23 @@ def task_add_content() -> Annotated[str, data_catalog["new_content"]]:
187187
result = runner.invoke(cli, [tmp_path.as_posix()])
188188
assert result.exit_code == ExitCode.OK
189189
assert len(list(tmp_path.joinpath(".data").iterdir())) == 2
190+
191+
192+
@pytest.mark.unit()
193+
def test_error_when_name_of_node_is_not_string():
194+
data_catalog = DataCatalog()
195+
with pytest.raises(TypeError, match="The name of a catalog entry"):
196+
data_catalog.add(True, Path("file.txt"))
197+
198+
199+
@pytest.mark.unit()
200+
def test_requesting_new_node_with_python_node_as_default():
201+
data_catalog = DataCatalog(default_node=PythonNode)
202+
assert isinstance(data_catalog["node"], PythonNode)
203+
204+
205+
@pytest.mark.unit()
206+
def test_adding_a_python_node():
207+
data_catalog = DataCatalog()
208+
data_catalog.add("node", PythonNode(name="node", value=1))
209+
assert isinstance(data_catalog["node"], PythonNode)

0 commit comments

Comments
 (0)