Skip to content
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
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ repos:
- id: check-yaml
exclude: meta.yaml
- id: debug-statements
exclude: (debugging\.py|build\.py)
exclude: (debugging\.py|build\.py|clean\.py|mark/__init__\.py)
- id: end-of-file-fixer
- repo: https://github.com/asottile/pyupgrade
rev: v2.7.2
Expand Down
1 change: 1 addition & 0 deletions docs/changes.rst
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ all releases are available on `Anaconda.org <https://anaconda.org/pytask/pytask>
------------------

- :gh:`25` allows to customize the names of the task files.
- :gh:`26` makes commands return the correct exit codes.
- :gh:`27` implements the ``pytask_collect_task_teardown`` hook specification to perform
checks after a task is collected.

Expand Down
10 changes: 5 additions & 5 deletions src/_pytask/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@

import click
from _pytask.config import hookimpl
from _pytask.database import create_database
from _pytask.enums import ExitCode
from _pytask.exceptions import CollectionError
from _pytask.exceptions import ConfigurationError
from _pytask.exceptions import ExecutionError
from _pytask.exceptions import ResolvingDependenciesError
from _pytask.pluginmanager import get_plugin_manager
Expand Down Expand Up @@ -49,16 +49,16 @@ def main(config_from_cli):

config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli)

create_database(**config["database"])

session = Session.from_config(config)
session.exit_code = ExitCode.OK

except Exception:
except (ConfigurationError, Exception):
traceback.print_exception(*sys.exc_info())
session = Session({}, None)
session.exit_code = ExitCode.CONFIGURATION_FAILED

if config_from_cli.get("pdb"):
pdb.post_mortem()

else:
try:
session.hook.pytask_log_session_header(session=session)
Expand Down
8 changes: 5 additions & 3 deletions src/_pytask/clean.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Add a command to clean the project from files unknown to pytask."""
import itertools
import pdb
import shutil
import sys
import traceback
Expand Down Expand Up @@ -71,15 +72,16 @@ def clean(**config_from_cli):
pm.hook.pytask_add_hooks(pm=pm)

config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli)

session = Session.from_config(config)
session.exit_code = ExitCode.OK

except Exception:
traceback.print_exception(*sys.exc_info())
session = Session({}, None)
session.exit_code = ExitCode.CONFIGURATION_FAILED

if config_from_cli.get("pdb"):
pdb.post_mortem()

else:
try:
session.hook.pytask_log_session_header(session=session)
Expand Down Expand Up @@ -119,7 +121,7 @@ def clean(**config_from_cli):
traceback.print_exception(*sys.exc_info())
session.exit_code = ExitCode.FAILED

return session
sys.exit(session.exit_code)


def _collect_all_paths_known_to_pytask(session):
Expand Down
3 changes: 2 additions & 1 deletion src/_pytask/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

import click
import pluggy
from _pytask.shared import convert_truthy_or_falsy_to_bool
from _pytask.shared import get_first_non_none_value
from _pytask.shared import parse_paths
from _pytask.shared import parse_value_or_multiline_option
Expand Down Expand Up @@ -112,7 +113,7 @@ def pytask_parse_config(config, config_from_cli, config_from_file):
config_from_file,
key="debug_pytask",
default=False,
callback=bool,
callback=convert_truthy_or_falsy_to_bool,
)
if config["debug_pytask"]:
config["pm"].trace.root.setwriter(click.echo)
Expand Down
5 changes: 5 additions & 0 deletions src/_pytask/database.py
Original file line number Diff line number Diff line change
Expand Up @@ -120,3 +120,8 @@ def pytask_parse_config(config, config_from_cli, config_from_file):
"create_db": config["database_create_db"],
"create_tables": config["database_create_tables"],
}


@hookimpl
def pytask_post_parse(config):
create_database(**config["database"])
1 change: 1 addition & 0 deletions src/_pytask/enums.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Enumerations for pytask."""
import enum


Expand Down
4 changes: 4 additions & 0 deletions src/_pytask/exceptions.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,10 @@ class NodeNotCollectedError(PytaskError):
"""Exception for nodes which could not be collected."""


class ConfigurationError(PytaskError):
"""Exception during the configuration."""


class CollectionError(PytaskError):
"""Exception during collection."""

Expand Down
8 changes: 5 additions & 3 deletions src/_pytask/mark/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import pdb
import sys
import textwrap
import traceback
Expand Down Expand Up @@ -50,9 +51,10 @@ def markers(**config_from_cli):
pm.hook.pytask_add_hooks(pm=pm)

config = pm.hook.pytask_configure(pm=pm, config_from_cli=config_from_cli)

session = Session.from_config(config)
session.exit_code = ExitCode.OK

if config_from_cli.get("pdb"):
pdb.post_mortem()

except Exception:
traceback.print_exception(*sys.exc_info())
Expand All @@ -68,7 +70,7 @@ def markers(**config_from_cli):
)
click.echo("")

return session
sys.exit(session.exit_code)


@hookimpl
Expand Down
2 changes: 2 additions & 0 deletions src/_pytask/session.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import attr
from _pytask.enums import ExitCode


@attr.s
Expand Down Expand Up @@ -27,6 +28,7 @@ class Session:
"""
execution_reports = attr.ib(factory=list)
"""Optional[List[pytask.report.ExecutionReport]]: Reports for executed tasks."""
exit_code = attr.ib(default=ExitCode.OK)

@classmethod
def from_config(cls, config):
Expand Down
7 changes: 3 additions & 4 deletions src/_pytask/shared.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
"""Functions which are used across various modules."""
import glob
from collections.abc import Iterable
from pathlib import Path
Expand Down Expand Up @@ -71,13 +72,10 @@ def get_first_non_none_value(*configs, key, default=None, callback=None):
--------
>>> get_first_non_none_value({"a": None}, {"a": 1}, key="a")
1

>>> get_first_non_none_value({"a": None}, {"a": None}, key="a", default="default")
'default'

>>> get_first_non_none_value({}, {}, key="a", default="default")
'default'

>>> get_first_non_none_value({"a": None}, {"a": "b"}, key="a")
'b'

Expand All @@ -88,6 +86,7 @@ def get_first_non_none_value(*configs, key, default=None, callback=None):


def parse_value_or_multiline_option(value):
"""Parse option which can hold a single value or values separated by new lines."""
if value in ["none", "None", None, ""]:
value = None
elif isinstance(value, str) and "\n" in value:
Expand All @@ -107,6 +106,6 @@ def convert_truthy_or_falsy_to_bool(x):
out = None
else:
raise ValueError(
f"Input {x} is neither truthy (True, true, 1) or falsy (False, false, 0)."
f"Input '{x}' is neither truthy (True, true, 1) or falsy (False, false, 0)."
)
return out
54 changes: 54 additions & 0 deletions tests/test_build.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,54 @@
import textwrap

import pytest
from pytask import cli


@pytest.mark.end_to_end
def test_execution_failed(runner, tmp_path):
source = """
def task_dummy():
raise Exception
"""
tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source))

result = runner.invoke(cli, [tmp_path.as_posix()])
assert result.exit_code == 1


@pytest.mark.end_to_end
def test_configuration_failed(runner, tmp_path):
result = runner.invoke(cli, [tmp_path.joinpath("non_existent_path").as_posix()])
assert result.exit_code == 2


@pytest.mark.end_to_end
def test_collection_failed(runner, tmp_path):
source = """
raise Exception
"""
tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source))

result = runner.invoke(cli, [tmp_path.as_posix()])
assert result.exit_code == 3


@pytest.mark.end_to_end
def test_resolving_dependencies_failed(runner, tmp_path):
source = """
import pytask

@pytask.mark.depends_on("in.txt")
@pytask.mark.produces("out.txt")
def task_dummy_1():
pass

@pytask.mark.depends_on("out.txt")
@pytask.mark.produces("in.txt")
def task_dummy_2():
pass
"""
tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source))

result = runner.invoke(cli, [tmp_path.as_posix()])
assert result.exit_code == 4
19 changes: 19 additions & 0 deletions tests/test_clean.py
Original file line number Diff line number Diff line change
Expand Up @@ -107,3 +107,22 @@ def test_clean_interactive_w_directories(sample_project_path, runner):
assert "to_be_deleted_file_2.txt" not in result.output
assert "to_be_deleted_folder_1" in result.output
assert not sample_project_path.joinpath("to_be_deleted_folder_1").exists()


@pytest.mark.end_to_end
def test_configuration_failed(runner, tmp_path):
result = runner.invoke(
cli, ["clean", tmp_path.joinpath("non_existent_path").as_posix()]
)
assert result.exit_code == 2


@pytest.mark.end_to_end
def test_collection_failed(runner, tmp_path):
source = """
raise Exception
"""
tmp_path.joinpath("task_dummy.py").write_text(textwrap.dedent(source))

result = runner.invoke(cli, ["clean", tmp_path.as_posix()])
assert result.exit_code == 3
8 changes: 8 additions & 0 deletions tests/test_mark.py
Original file line number Diff line number Diff line change
Expand Up @@ -272,3 +272,11 @@ def task_func(arg):

err = capsys.readouterr().err
assert expected_error in err


@pytest.mark.end_to_end
def test_configuration_failed(runner, tmp_path):
result = runner.invoke(
cli, ["markers", "-c", tmp_path.joinpath("non_existent_path").as_posix()]
)
assert result.exit_code == 2