diff --git a/.gitignore b/.gitignore index 7e6d463c..d5e3ea27 100644 --- a/.gitignore +++ b/.gitignore @@ -14,7 +14,6 @@ _generated *.egg-info .eggs -.pytask.sqlite3 .pytask build diff --git a/docs/source/changes.md b/docs/source/changes.md index 94fdd4c8..8d592050 100644 --- a/docs/source/changes.md +++ b/docs/source/changes.md @@ -20,6 +20,7 @@ releases are available on [PyPI](https://pypi.org/project/pytask) and - {pull}`464` improves pinned dependencies. - {pull}`465` adds test to ensure internal tracebacks are removed by reports. - {pull}`466` implements hashing for files instead of modification timestamps. +- {pull}`470` moves `.pytask.sqlite3` to `.pytask`. - {pull}`472` adds `is_product` to {meth}`PNode.load`. ## 0.4.1 - 2023-10-11 diff --git a/docs/source/how_to_guides/bp_scaling_tasks.md b/docs/source/how_to_guides/bp_scaling_tasks.md index e30ac448..4342d6ef 100644 --- a/docs/source/how_to_guides/bp_scaling_tasks.md +++ b/docs/source/how_to_guides/bp_scaling_tasks.md @@ -42,7 +42,8 @@ my_project │ ├───setup.py │ -├───.pytask.sqlite3 +├───.pytask +│ └────... │ └───bld ``` diff --git a/docs/source/how_to_guides/functional_interface.ipynb b/docs/source/how_to_guides/functional_interface.ipynb index 4377ef18..31337eaa 100644 --- a/docs/source/how_to_guides/functional_interface.ipynb +++ b/docs/source/how_to_guides/functional_interface.ipynb @@ -85,7 +85,7 @@ { "data": { "text/html": [ - "
Platform: linux -- Python 3.11.5, pytask 0.4.0rc3.dev4+gfb5a25d.d20230930, pluggy 1.3.0\n",
+       "
Platform: linux -- Python 3.11.5, pytask 0.4.0, pluggy 1.3.0\n",
        "
\n" ], "text/plain": [ @@ -273,7 +273,7 @@ { "data": { "text/plain": [ - "Session(config={'pm': , 'markers': {'depends_on': 'Add dependencies to a task. See this tutorial for more information: [link https://bit.ly/3JlxylS]https://bit.ly/3JlxylS[/].', 'filterwarnings': 'Add a filter for a warning to a task.', 'persist': 'Prevent execution of a task if all products exist and even if something has changed (dependencies, source file, products). This decorator might be useful for expensive tasks where only the formatting of the file has changed. The state of the files which have changed will also be remembered and another run will skip the task with success.', 'produces': 'Add products to a task. See this tutorial for more information: [link https://bit.ly/3JlxylS]https://bit.ly/3JlxylS[/].', 'skip': 'Skip a task and all its dependent tasks.', 'skip_ancestor_failed': 'Internal decorator applied to tasks if any of its preceding tasks failed.', 'skip_unchanged': 'Internal decorator applied to tasks which have already been executed and have not been changed.', 'skipif': 'Skip a task and all its dependent tasks if a condition is met.', 'task': 'Mark a function as a task regardless of its name. Or mark tasks which are repeated in a loop. See this tutorial for more information: [link https://bit.ly/3DWrXS3]https://bit.ly/3DWrXS3[/].', 'try_first': 'Try to execute a task a early as possible.', 'try_last': 'Try to execute a task a late as possible.'}, 'config': None, 'database_url': sqlite:////home/tobia/git/pytask/.pytask.sqlite3, 'editor_url_scheme': 'file', 'export': <_ExportFormats.NO: 'no'>, 'ignore': ['.codecov.yml', '.gitignore', '.pre-commit-config.yaml', '.readthedocs.yml', '.readthedocs.yaml', 'readthedocs.yml', 'readthedocs.yaml', 'environment.yml', 'pyproject.toml', 'setup.cfg', 'tox.ini', '.git/*', '.venv/*', '*.egg-info/*', '.ipynb_checkpoints/*', '.mypy_cache/*', '.nox/*', '.tox/*', '_build/*', '__pycache__/*', 'build/*', 'dist/*', 'pytest_cache/*'], 'paths': [], 'layout': 'dot', 'output_path': 'dag.pdf', 'rank_direction': <_RankDirection.TB: 'TB'>, 'expression': '', 'marker_expression': '', 'nodes': False, 'strict_markers': False, 'directories': False, 'exclude': [None, '.git/*'], 'mode': <_CleanMode.DRY_RUN: 'dry-run'>, 'quiet': False, 'capture': , 'debug_pytask': False, 'disable_warnings': False, 'dry_run': False, 'force': False, 'max_failures': inf, 'n_entries_in_table': 15, 'pdb': False, 'pdbcls': None, 's': False, 'show_capture': True, 'show_errors_immediately': False, 'show_locals': False, 'show_traceback': True, 'sort_table': True, 'trace': False, 'verbose': 1, 'stop_after_first_failure': False, 'check_casing_of_paths': True, 'pdb_cls': '', 'tasks': [, , at 0x7f3c1b407d80>], 'task_files': ['task_*.py'], 'command': 'build', 'root': PosixPath('/home/tobia/git/pytask'), 'filterwarnings': []}, hook=, collection_reports=[CollectionReport(outcome=, node=TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.0767577, 1696055304.077608)}), exc_info=None), CollectionReport(outcome=, node=TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt')), 'second': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.123595, 1696055304.1244528)}), exc_info=None), CollectionReport(outcome=, node=TaskWithoutPath(name='task_create_second_file', function= at 0x7f3c1b407d80>, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.025182, 1696055304.0267167)}), exc_info=None)], tasks=[TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.0767577, 1696055304.077608)}), TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt')), 'second': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.123595, 1696055304.1244528)}), TaskWithoutPath(name='task_create_second_file', function= at 0x7f3c1b407d80>, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.025182, 1696055304.0267167)})], dag=, resolving_dependencies_report=None, execution_reports=[ExecutionReport(task=TaskWithoutPath(name='task_create_second_file', function= at 0x7f3c1b407d80>, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.025182, 1696055304.0267167)}), outcome=, exc_info=None, sections=[]), ExecutionReport(task=TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.0767577, 1696055304.077608)}), outcome=, exc_info=None, sections=[]), ExecutionReport(task=TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt')), 'second': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.123595, 1696055304.1244528)}), outcome=, exc_info=None, sections=[])], exit_code=, collection_start=1696055303.989013, collection_end=1696055303.9959698, execution_start=1696055304.0121965, execution_end=1696055304.207084, n_tasks_failed=0, scheduler=TopologicalSorter(dag=, priorities={'task_create_first_file': 0, 'task_merge_files': 0, 'task_create_second_file': 0}, _dag_backup=, _is_prepared=True, _nodes_out=set()), should_stop=False, warnings=[])" + "Session(config={'pm': , 'markers': {'depends_on': 'Add dependencies to a task. See this tutorial for more information: [link https://bit.ly/3JlxylS]https://bit.ly/3JlxylS[/].', 'filterwarnings': 'Add a filter for a warning to a task.', 'persist': 'Prevent execution of a task if all products exist and even if something has changed (dependencies, source file, products). This decorator might be useful for expensive tasks where only the formatting of the file has changed. The state of the files which have changed will also be remembered and another run will skip the task with success.', 'produces': 'Add products to a task. See this tutorial for more information: [link https://bit.ly/3JlxylS]https://bit.ly/3JlxylS[/].', 'skip': 'Skip a task and all its dependent tasks.', 'skip_ancestor_failed': 'Internal decorator applied to tasks if any of its preceding tasks failed.', 'skip_unchanged': 'Internal decorator applied to tasks which have already been executed and have not been changed.', 'skipif': 'Skip a task and all its dependent tasks if a condition is met.', 'task': 'Mark a function as a task regardless of its name. Or mark tasks which are repeated in a loop. See this tutorial for more information: [link https://bit.ly/3DWrXS3]https://bit.ly/3DWrXS3[/].', 'try_first': 'Try to execute a task a early as possible.', 'try_last': 'Try to execute a task a late as possible.'}, 'config': None, 'database_url': sqlite:////home/tobia/git/pytask/.pytask/.pytask.sqlite3, 'editor_url_scheme': 'file', 'export': <_ExportFormats.NO: 'no'>, 'ignore': ['.codecov.yml', '.gitignore', '.pre-commit-config.yaml', '.readthedocs.yml', '.readthedocs.yaml', 'readthedocs.yml', 'readthedocs.yaml', 'environment.yml', 'pyproject.toml', 'setup.cfg', 'tox.ini', '.git/*', '.venv/*', '*.egg-info/*', '.ipynb_checkpoints/*', '.mypy_cache/*', '.nox/*', '.tox/*', '_build/*', '__pycache__/*', 'build/*', 'dist/*', 'pytest_cache/*'], 'paths': [], 'layout': 'dot', 'output_path': 'dag.pdf', 'rank_direction': <_RankDirection.TB: 'TB'>, 'expression': '', 'marker_expression': '', 'nodes': False, 'strict_markers': False, 'directories': False, 'exclude': [None, '.git/*'], 'mode': <_CleanMode.DRY_RUN: 'dry-run'>, 'quiet': False, 'capture': , 'debug_pytask': False, 'disable_warnings': False, 'dry_run': False, 'force': False, 'max_failures': inf, 'n_entries_in_table': 15, 'pdb': False, 'pdbcls': None, 's': False, 'show_capture': True, 'show_errors_immediately': False, 'show_locals': False, 'show_traceback': True, 'sort_table': True, 'trace': False, 'verbose': 1, 'stop_after_first_failure': False, 'check_casing_of_paths': True, 'pdb_cls': '', 'tasks': [, , at 0x7f3c1b407d80>], 'task_files': ['task_*.py'], 'command': 'build', 'root': PosixPath('/home/tobia/git/pytask'), 'filterwarnings': []}, hook=, collection_reports=[CollectionReport(outcome=, node=TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.0767577, 1696055304.077608)}), exc_info=None), CollectionReport(outcome=, node=TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt')), 'second': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.123595, 1696055304.1244528)}), exc_info=None), CollectionReport(outcome=, node=TaskWithoutPath(name='task_create_second_file', function= at 0x7f3c1b407d80>, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.025182, 1696055304.0267167)}), exc_info=None)], tasks=[TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.0767577, 1696055304.077608)}), TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt')), 'second': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.123595, 1696055304.1244528)}), TaskWithoutPath(name='task_create_second_file', function= at 0x7f3c1b407d80>, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.025182, 1696055304.0267167)})], dag=, resolving_dependencies_report=None, execution_reports=[ExecutionReport(task=TaskWithoutPath(name='task_create_second_file', function= at 0x7f3c1b407d80>, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.025182, 1696055304.0267167)}), outcome=, exc_info=None, sections=[]), ExecutionReport(task=TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.0767577, 1696055304.077608)}), outcome=, exc_info=None, sections=[]), ExecutionReport(task=TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/first.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/first.txt')), 'second': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/second.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/second.txt'))}, produces={'return': PathNode(name='/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt', path=PosixPath('/home/tobia/git/pytask/docs/source/how_to_guides/hello_world.txt'))}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'duration': (1696055304.123595, 1696055304.1244528)}), outcome=, exc_info=None, sections=[])], exit_code=, collection_start=1696055303.989013, collection_end=1696055303.9959698, execution_start=1696055304.0121965, execution_end=1696055304.207084, n_tasks_failed=0, scheduler=TopologicalSorter(dag=, priorities={'task_create_first_file': 0, 'task_merge_files': 0, 'task_create_second_file': 0}, _dag_backup=, _is_prepared=True, _nodes_out=set()), should_stop=False, warnings=[])" ] }, "execution_count": 4, diff --git a/docs/source/reference_guides/configuration.md b/docs/source/reference_guides/configuration.md index 2799d66a..eecc42ce 100644 --- a/docs/source/reference_guides/configuration.md +++ b/docs/source/reference_guides/configuration.md @@ -46,12 +46,12 @@ are welcome to also support macOS. pytask uses a database to keep track of tasks, products, and dependencies over runs. By default, it will create an SQLite database in the project's root directory called -`.pytask.sqlite3`. If you want to use a different name or a different dialect +`.pytask/pytask.sqlite3`. If you want to use a different name or a different dialect [supported by sqlalchemy](https://docs.sqlalchemy.org/en/latest/core/engines.html#backend-specific-urls), use either {option}`pytask build --database-url` or `database_url` in the config. ```toml -database_url = "sqlite:///.pytask.sqlite3" +database_url = "sqlite:///.pytask/pytask.sqlite3" ``` Relative paths for SQLite databases are interpreted as either relative to the diff --git a/docs/source/tutorials/configuration.md b/docs/source/tutorials/configuration.md index 9a625f5e..68a36c35 100644 --- a/docs/source/tutorials/configuration.md +++ b/docs/source/tutorials/configuration.md @@ -4,7 +4,7 @@ pytask can be configured via the command-line interface or permanently with a `pyproject.toml` file. The file also indicates the root of your project where pytask stores information in a -`.pytask.sqlite3` database. +`.pytask` folder. ## The configuration file diff --git a/docs/source/tutorials/defining_dependencies_products.md b/docs/source/tutorials/defining_dependencies_products.md index 63651d4d..da4d133a 100644 --- a/docs/source/tutorials/defining_dependencies_products.md +++ b/docs/source/tutorials/defining_dependencies_products.md @@ -33,7 +33,8 @@ my_project │ ├───setup.py │ -├───.pytask.sqlite3 +├───.pytask +│ └────... │ └───bld ├────data.pkl diff --git a/docs/source/tutorials/set_up_a_project.md b/docs/source/tutorials/set_up_a_project.md index 0fae0f36..b31f9c41 100644 --- a/docs/source/tutorials/set_up_a_project.md +++ b/docs/source/tutorials/set_up_a_project.md @@ -24,7 +24,8 @@ my_project │ ├───pyproject.toml │ -├───.pytask.sqlite3 +├───.pytask +│ └────... │ └───bld └────... @@ -42,8 +43,8 @@ paths = "src/my_project" You do not have to add configuration values, but you need the `[tool.pytask.ini_options]` header. The header alone will signal pytask that this is the -project's root. pytask will store the information it needs across executions in a -`.pytask.sqlite3` database next to the configuration file. +project's root. pytask will store the information it needs across executions in the +`.pytask` folder. `paths` allows you to set the location of tasks when you do not pass them explicitly via the CLI. diff --git a/docs/source/tutorials/using_a_data_catalog.md b/docs/source/tutorials/using_a_data_catalog.md index b78a26b3..3a9048de 100644 --- a/docs/source/tutorials/using_a_data_catalog.md +++ b/docs/source/tutorials/using_a_data_catalog.md @@ -21,7 +21,6 @@ The project structure is the same as in the previous example with the exception ```text my_project -├───.pytask │ ├───pyproject.toml │ @@ -33,7 +32,8 @@ my_project │ ├───setup.py │ -├───.pytask.sqlite3 +├───.pytask +│ └────... │ └───bld └────plot.png @@ -166,7 +166,6 @@ Let's add `file.csv` to the data catalog. ```text my_project -├───.pytask │ ├───pyproject.toml │ @@ -179,7 +178,8 @@ my_project │ ├───setup.py │ -├───.pytask.sqlite3 +├───.pytask +│ └────... │ └───bld ├────file.pkl diff --git a/docs/source/tutorials/write_a_task.md b/docs/source/tutorials/write_a_task.md index bef620f0..6bc3c135 100644 --- a/docs/source/tutorials/write_a_task.md +++ b/docs/source/tutorials/write_a_task.md @@ -12,6 +12,7 @@ automatically discovers them. ```text my_project +│ ├───pyproject.toml │ ├───src @@ -21,7 +22,8 @@ my_project │ ├───setup.py │ -├───.pytask.sqlite3 +├───.pytask +│ └────... │ └───bld └────data.pkl diff --git a/src/_pytask/build.py b/src/_pytask/build.py index 2e7c45ae..1234bc33 100644 --- a/src/_pytask/build.py +++ b/src/_pytask/build.py @@ -3,6 +3,7 @@ import json import sys +from contextlib import suppress from pathlib import Path from typing import Any from typing import Callable @@ -45,22 +46,19 @@ def pytask_extend_command_line_interface(cli: click.Group) -> None: @hookimpl def pytask_post_parse(config: dict[str, Any]) -> None: """Fill cache of file hashes with stored hashes.""" - try: + with suppress(Exception): path = config["root"] / ".pytask" / "file_hashes.json" cache = json.loads(path.read_text()) - except Exception: # noqa: BLE001 - cache = {} - for key, value in cache.items(): - HashPathCache.add(key, value) + for key, value in cache.items(): + HashPathCache.add(key, value) @hookimpl def pytask_unconfigure(session: Session) -> None: """Save calculated file hashes to file.""" - path = session.config["root"] / ".pytask" - path.mkdir(exist_ok=True, parents=True) - path.joinpath("file_hashes.json").write_text(json.dumps(HashPathCache._cache)) + path = session.config["root"] / ".pytask" / "file_hashes.json" + path.write_text(json.dumps(HashPathCache._cache)) def build( # noqa: C901, PLR0912, PLR0913 diff --git a/src/_pytask/config.py b/src/_pytask/config.py index e4925425..7cb6d9e6 100644 --- a/src/_pytask/config.py +++ b/src/_pytask/config.py @@ -68,7 +68,6 @@ def pytask_configure( config["markers"] = parse_markers(config["markers"]) pm.hook.pytask_parse_config(config=config) - pm.hook.pytask_post_parse(config=config) return config @@ -77,6 +76,8 @@ def pytask_configure( @hookimpl def pytask_parse_config(config: dict[str, Any]) -> None: """Parse the configuration.""" + config["root"].joinpath(".pytask").mkdir(exist_ok=True, parents=True) + config["paths"] = parse_paths(config["paths"]) config["markers"] = { diff --git a/src/_pytask/database.py b/src/_pytask/database.py index ea797e81..eb40fd2b 100644 --- a/src/_pytask/database.py +++ b/src/_pytask/database.py @@ -15,7 +15,7 @@ def pytask_parse_config(config: dict[str, Any]) -> None: # Set default. if not config["database_url"]: config["database_url"] = make_url( - f"sqlite:///{config['root'].as_posix()}/.pytask.sqlite3" + f"sqlite:///{config['root'].joinpath('.pytask').as_posix()}/pytask.sqlite3" ) if ( diff --git a/src/_pytask/parameters.py b/src/_pytask/parameters.py index ec976c99..03575c55 100644 --- a/src/_pytask/parameters.py +++ b/src/_pytask/parameters.py @@ -91,7 +91,7 @@ def _database_url_callback( type=str, help=("Url to the database."), default=None, - show_default="sqlite:///.../.pytask.sqlite3", + show_default="sqlite:///.../.pytask/pytask.sqlite3", callback=_database_url_callback, ) diff --git a/tests/test_clean.py b/tests/test_clean.py index a7db9061..bc73aa9e 100644 --- a/tests/test_clean.py +++ b/tests/test_clean.py @@ -94,7 +94,7 @@ def test_clean_database_ignored(project, runner): text_without_linebreaks = result.output.replace("\n", "") assert "to_be_deleted_file_1.txt" in text_without_linebreaks assert "to_be_deleted_file_2.txt" in text_without_linebreaks - assert ".pytask.sqlite3" not in text_without_linebreaks + assert "pytask.sqlite3" not in text_without_linebreaks @pytest.mark.end_to_end() diff --git a/tests/test_database.py b/tests/test_database.py index 735dcf7d..516ba582 100644 --- a/tests/test_database.py +++ b/tests/test_database.py @@ -33,7 +33,9 @@ def task_write(depends_on, produces): assert result.exit_code == ExitCode.OK create_database( - make_url("sqlite:///" + tmp_path.joinpath(".pytask.sqlite3").as_posix()) + make_url( + "sqlite:///" + tmp_path.joinpath(".pytask", "pytask.sqlite3").as_posix() + ) ) with DatabaseSession() as session: diff --git a/tests/test_persist.py b/tests/test_persist.py index 9d5d714c..05529571 100644 --- a/tests/test_persist.py +++ b/tests/test_persist.py @@ -67,7 +67,9 @@ def task_dummy(depends_on, produces): assert session.execution_reports[0].outcome == TaskOutcome.PERSISTENCE assert isinstance(session.execution_reports[0].exc_info[1], Persisted) - create_database("sqlite:///" + tmp_path.joinpath(".pytask.sqlite3").as_posix()) + create_database( + "sqlite:///" + tmp_path.joinpath(".pytask", "pytask.sqlite3").as_posix() + ) with DatabaseSession() as session: task_id = tmp_path.joinpath("task_module.py").as_posix() + "::task_dummy" diff --git a/tests/test_profile.py b/tests/test_profile.py index a516e0e8..32f8e7c9 100644 --- a/tests/test_profile.py +++ b/tests/test_profile.py @@ -30,7 +30,9 @@ def task_example(): time.sleep(2) duration = task.attributes["duration"] assert duration[1] - duration[0] > 2 - create_database("sqlite:///" + tmp_path.joinpath(".pytask.sqlite3").as_posix()) + create_database( + "sqlite:///" + tmp_path.joinpath(".pytask", "pytask.sqlite3").as_posix() + ) with DatabaseSession() as session: task_name = tmp_path.joinpath("task_example.py").as_posix() + "::task_example"