diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 41192f1c..b9ccf5ca 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -1,3 +1,6 @@ +ci: + skip: [type-checking] + repos: - repo: https://github.com/pre-commit/pre-commit-hooks rev: v5.0.0 @@ -64,3 +67,10 @@ repos: - id: check-hooks-apply - id: check-useless-excludes # - id: identity # Prints all files passed to pre-commits. Debugging. +- repo: local + hooks: + - id: type-checking + name: type-checking + entry: uv run --group typing mypy + language: system + pass_filenames: false diff --git a/docs/source/changes.md b/docs/source/changes.md index d53d7d24..0e2aa95a 100644 --- a/docs/source/changes.md +++ b/docs/source/changes.md @@ -5,6 +5,12 @@ chronological order. Releases follow [semantic versioning](https://semver.org/) releases are available on [PyPI](https://pypi.org/project/pytask) and [Anaconda.org](https://anaconda.org/conda-forge/pytask). +## 0.5.4 - 2025-xx-xx + +- {pull}`676` ensures compatibility with click >8.2.0. +- {pull}`680` uses uv everywhere. +- {pull}`684` adds tests for lowest and highest dependency resolutions. + ## 0.5.3 - 2025-05-16 - {pull}`650` allows to identify from which data catalog a node is coming from. Thanks diff --git a/docs/source/how_to_guides/functional_interface.ipynb b/docs/source/how_to_guides/functional_interface.ipynb index d4b657ef..76b6eedc 100644 --- a/docs/source/how_to_guides/functional_interface.ipynb +++ b/docs/source/how_to_guides/functional_interface.ipynb @@ -72,11 +72,11 @@ { "data": { "text/html": [ - "
────────────────────────────────────────────── Start pytask session ───────────────────────────────────────────────\n",
+       "
────────────────────────────────────────────── Start pytask session ───────────────────────────────────────────────\n",
        "
\n" ], "text/plain": [ - "────────────────────────────────────────────── Start pytask session ───────────────────────────────────────────────\n" + "\u001b[39m────────────────────────────────────────────── \u001b[0mStart pytask session\u001b[39m ───────────────────────────────────────────────\u001b[0m\n" ] }, "metadata": {}, @@ -85,11 +85,11 @@ { "data": { "text/html": [ - "
Platform: linux -- Python 3.11.5, pytask 0.4.0, pluggy 1.3.0\n",
+       "
Platform: darwin -- Python 3.12.2, pytask 0.5.4.dev16+g8ed43db.d20250607, pluggy 1.6.0\n",
        "
\n" ], "text/plain": [ - "Platform: linux -- Python 3.11.5, pytask 0.4.0rc3.dev4+gfb5a25d.d20230930, pluggy 1.3.0\n" + "Platform: darwin -- Python \u001b[1;36m3.12\u001b[0m.\u001b[1;36m2\u001b[0m, pytask \u001b[1;36m0.5\u001b[0m.\u001b[1;36m4.\u001b[0mdev16+g8ed43db.d20250607, pluggy \u001b[1;36m1.6\u001b[0m.\u001b[1;36m0\u001b[0m\n" ] }, "metadata": {}, @@ -98,11 +98,11 @@ { "data": { "text/html": [ - "
Root: /home/tobia/git/pytask\n",
+       "
Root: /Users/tobiasr/git/pytask\n",
        "
\n" ], "text/plain": [ - "Root: /home/tobia/git/pytask\n" + "Root: \u001b[35m/Users/tobiasr/git/\u001b[0m\u001b[95mpytask\u001b[0m\n" ] }, "metadata": {}, @@ -125,7 +125,7 @@ "
\n" ], "text/plain": [ - "Collected 3 tasks.\n" + "Collected \u001b[1;36m3\u001b[0m tasks.\n" ] }, "metadata": {}, @@ -147,7 +147,7 @@ { "data": { "application/vnd.jupyter.widget-view+json": { - "model_id": "d684c1a12c55404f8acd8c563bd99577", + "model_id": "59c1696ceef94890b5833506da638d2d", "version_major": 2, "version_minor": 0 }, @@ -174,19 +174,19 @@ "
╭─────────────────────────┬─────────╮\n",
        "│ Task                     Outcome │\n",
        "├─────────────────────────┼─────────┤\n",
-       "│ task_create_first_file .       │\n",
-       "│ task_create_second_file.       │\n",
-       "│ task_merge_files       .       │\n",
+       "│ task_create_first_file .       │\n",
+       "│ task_create_second_file.       │\n",
+       "│ task_merge_files       .       │\n",
        "╰─────────────────────────┴─────────╯\n",
        "
\n" ], "text/plain": [ "╭─────────────────────────┬─────────╮\n", - "│ Task │ Outcome │\n", + "│\u001b[1m \u001b[0m\u001b[1mTask \u001b[0m\u001b[1m \u001b[0m│\u001b[1m \u001b[0m\u001b[1mOutcome\u001b[0m\u001b[1m \u001b[0m│\n", "├─────────────────────────┼─────────┤\n", - "│ task_create_first_file │ . │\n", - "│ task_create_second_file │ . │\n", - "│ task_merge_files │ . │\n", + "│ \u001b]8;id=761467;file:///None\u001b\\task_create_first_file \u001b]8;;\u001b\\ │ \u001b[38;2;19;124;57m. \u001b[0m │\n", + "│ \u001b]8;id=184911;file:///None\u001b\\task_create_second_file\u001b]8;;\u001b\\ │ \u001b[38;2;19;124;57m. \u001b[0m │\n", + "│ \u001b]8;id=880445;file:///None\u001b\\task_merge_files \u001b]8;;\u001b\\ │ \u001b[38;2;19;124;57m. \u001b[0m │\n", "╰─────────────────────────┴─────────╯\n" ] }, @@ -213,7 +213,7 @@ "
\n" ], "text/plain": [ - "───────────────────────────────────────────────────────────────────────────────────────────────────────────────────\n" + "\u001b[2m───────────────────────────────────────────────────────────────────────────────────────────────────────────────────\u001b[0m\n" ] }, "metadata": {}, @@ -222,17 +222,17 @@ { "data": { "text/html": [ - "
╭─────────── Summary ────────────╮\n",
+       "
╭─────────── Summary ────────────╮\n",
        "  3  Collected tasks            \n",
        "  3  Succeeded        (100.0%)  \n",
        "╰────────────────────────────────╯\n",
        "
\n" ], "text/plain": [ - "╭─────────── Summary ────────────╮\n", - "│ 3 Collected tasks │\n", - "│ 3 Succeeded (100.0%) │\n", - "╰────────────────────────────────╯\n" + "\u001b[38;2;19;124;57m╭─\u001b[0m\u001b[38;2;19;124;57m──────────\u001b[0m\u001b[1;38;2;242;242;242m Summary \u001b[0m\u001b[38;2;19;124;57m───────────\u001b[0m\u001b[38;2;19;124;57m─╮\u001b[0m\n", + "\u001b[38;2;19;124;57m│\u001b[0m \u001b[38;2;242;242;242m \u001b[0m\u001b[38;2;242;242;242m3\u001b[0m\u001b[38;2;242;242;242m \u001b[0m\u001b[38;2;242;242;242m \u001b[0m\u001b[38;2;242;242;242mCollected tasks\u001b[0m\u001b[38;2;242;242;242m \u001b[0m\u001b[38;2;242;242;242m \u001b[0m\u001b[38;2;242;242;242m \u001b[0m\u001b[38;2;242;242;242m \u001b[0m \u001b[38;2;19;124;57m│\u001b[0m\n", + "\u001b[38;2;19;124;57m│\u001b[0m \u001b[38;2;242;242;242;48;2;19;124;57m \u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57m3\u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57m \u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57m \u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57mSucceeded \u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57m \u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57m \u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57m(100.0%)\u001b[0m\u001b[38;2;242;242;242;48;2;19;124;57m \u001b[0m \u001b[38;2;19;124;57m│\u001b[0m\n", + "\u001b[38;2;19;124;57m╰────────────────────────────────╯\u001b[0m\n" ] }, "metadata": {}, @@ -241,11 +241,11 @@ { "data": { "text/html": [ - "
──────────────────────────────────────────── Succeeded in 0.19 seconds ────────────────────────────────────────────\n",
+       "
──────────────────────────────────────────── Succeeded in 0.1 seconds ─────────────────────────────────────────────\n",
        "
\n" ], "text/plain": [ - "──────────────────────────────────────────── Succeeded in 0.19 seconds ────────────────────────────────────────────\n" + "\u001b[38;2;19;124;57m──────────────────────────────────────────── \u001b[0m\u001b[38;2;19;124;57mSucceeded in 0.1 seconds\u001b[0m\u001b[38;2;19;124;57m ─────────────────────────────────────────────\u001b[0m\n" ] }, "metadata": {}, @@ -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/.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', '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': {'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.', '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:////Users/tobiasr/git/pytask/.pytask/pytask.sqlite3, 'editor_url_scheme': 'file', 'export': <_ExportFormats.NO: 'no'>, 'hook_module': None, '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/*', '.pixi/*', '*.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/*', '/Users/tobiasr/git/pytask/.pytask/*'], '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': , '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 0x115ac0a40>], 'task_files': ('task_*.py',), 'command': 'build', 'root': PosixPath('/Users/tobiasr/git/pytask'), 'filterwarnings': []}, collection_reports=[CollectionReport(outcome=, node=TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/first.txt'), name='pytask/docs/source/how_to_guides/first.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('768f2f6b-85b9-4ed4-8b08-22e66ec68143'), 'after': [], 'is_generator': False, 'duration': (1749282510.968756, 1749282510.969585)}), exc_info=None), CollectionReport(outcome=, node=TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/first.txt'), name='pytask/docs/source/how_to_guides/first.txt', attributes={}), 'second': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/second.txt'), name='pytask/docs/source/how_to_guides/second.txt', attributes={})}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/hello_world.txt'), name='pytask/docs/source/how_to_guides/hello_world.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('2fe578c8-ab38-4d0d-bb34-e12df5b5975b'), 'after': [], 'is_generator': False, 'duration': (1749282511.055661, 1749282511.056195)}), exc_info=None), CollectionReport(outcome=, node=TaskWithoutPath(name='task_create_second_file', function= at 0x115ac0a40>, depends_on={}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/second.txt'), name='pytask/docs/source/how_to_guides/second.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('d096742c-b6fd-4b68-9b43-ae5d3c775001'), 'after': [], 'is_generator': False, 'duration': (1749282511.046389, 1749282511.046774)}), exc_info=None)], dag=, hook=, tasks=[TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/first.txt'), name='pytask/docs/source/how_to_guides/first.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('768f2f6b-85b9-4ed4-8b08-22e66ec68143'), 'after': [], 'is_generator': False, 'duration': (1749282510.968756, 1749282510.969585)}), TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/first.txt'), name='pytask/docs/source/how_to_guides/first.txt', attributes={}), 'second': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/second.txt'), name='pytask/docs/source/how_to_guides/second.txt', attributes={})}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/hello_world.txt'), name='pytask/docs/source/how_to_guides/hello_world.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('2fe578c8-ab38-4d0d-bb34-e12df5b5975b'), 'after': [], 'is_generator': False, 'duration': (1749282511.055661, 1749282511.056195)}), TaskWithoutPath(name='task_create_second_file', function= at 0x115ac0a40>, depends_on={}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/second.txt'), name='pytask/docs/source/how_to_guides/second.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('d096742c-b6fd-4b68-9b43-ae5d3c775001'), 'after': [], 'is_generator': False, 'duration': (1749282511.046389, 1749282511.046774)})], dag_report=None, execution_reports=[ExecutionReport(task=TaskWithoutPath(name='task_create_first_file', function=, depends_on={}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/first.txt'), name='pytask/docs/source/how_to_guides/first.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('768f2f6b-85b9-4ed4-8b08-22e66ec68143'), 'after': [], 'is_generator': False, 'duration': (1749282510.968756, 1749282510.969585)}), outcome=, exc_info=None, sections=[]), ExecutionReport(task=TaskWithoutPath(name='task_create_second_file', function= at 0x115ac0a40>, depends_on={}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/second.txt'), name='pytask/docs/source/how_to_guides/second.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('d096742c-b6fd-4b68-9b43-ae5d3c775001'), 'after': [], 'is_generator': False, 'duration': (1749282511.046389, 1749282511.046774)}), outcome=, exc_info=None, sections=[]), ExecutionReport(task=TaskWithoutPath(name='task_merge_files', function=, depends_on={'first': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/first.txt'), name='pytask/docs/source/how_to_guides/first.txt', attributes={}), 'second': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/second.txt'), name='pytask/docs/source/how_to_guides/second.txt', attributes={})}, produces={'return': PathNode(path=PosixPath('/Users/tobiasr/git/pytask/docs/source/how_to_guides/hello_world.txt'), name='pytask/docs/source/how_to_guides/hello_world.txt', attributes={})}, markers=[Mark(name='task', args=(), kwargs={})], report_sections=[], attributes={'collection_id': UUID('2fe578c8-ab38-4d0d-bb34-e12df5b5975b'), 'after': [], 'is_generator': False, 'duration': (1749282511.055661, 1749282511.056195)}), outcome=, exc_info=None, sections=[])], exit_code=, collection_start=1749282510.959347, collection_end=1749282510.9617531, execution_start=1749282510.962504, execution_end=1749282511.06384, n_tasks_failed=0, scheduler=TopologicalSorter(dag=, priorities={'82d6a7ce01a2a50d5d4bd5081d662df92b8c500fbc172f94fb026c9d1d4ebc4a': 0, '45a637ca3cc7aa973d4b315cc1bef02217b79918357fd35c6fa61f4e2d2f9948': 0, '2a06f358fc8e621754c133af76f5ac1b3e8ad5172b5803823cb264b30ea5d829': 0}, _nodes_processing=set(), _nodes_done={'2a06f358fc8e621754c133af76f5ac1b3e8ad5172b5803823cb264b30ea5d829', '82d6a7ce01a2a50d5d4bd5081d662df92b8c500fbc172f94fb026c9d1d4ebc4a', '45a637ca3cc7aa973d4b315cc1bef02217b79918357fd35c6fa61f4e2d2f9948'}), should_stop=False, warnings=[])" ] }, "execution_count": 4, @@ -303,41 +303,41 @@ "name": "stdout", "output_type": "stream", "text": [ - "\u001b[0;31mSignature:\u001b[0m\n", - "\u001b[0mpytask\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mbuild\u001b[0m\u001b[0;34m(\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0;34m*\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcapture\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m\"Literal['fd', 'no', 'sys', 'tee-sys'] | CaptureMethod\"\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m<\u001b[0m\u001b[0mCaptureMethod\u001b[0m\u001b[0;34m.\u001b[0m\u001b[0mNO\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'no'\u001b[0m\u001b[0;34m>\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mcheck_casing_of_paths\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mconfig\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Path | None'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mNone\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdatabase_url\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdebug_pytask\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdisable_warnings\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mdry_run\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0meditor_url_scheme\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m\"Literal['no_link', 'file', 'vscode', 'pycharm'] | str\"\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'file'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mexpression\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mforce\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mignore\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Iterable[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mmarker_expression\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mmax_failures\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'float'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0minf\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mn_entries_in_table\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'int'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m15\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mpaths\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | Path | Iterable[str | Path]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mpdb\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mpdb_cls\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m''\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0ms\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mshow_capture\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mshow_errors_immediately\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mshow_locals\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mshow_traceback\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0msort_table\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mTrue\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstop_after_first_failure\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mstrict_markers\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtasks\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Callable[..., Any] | PTask | Iterable[Callable[..., Any] | PTask]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m(\u001b[0m\u001b[0;34m)\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtask_files\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'str | Iterable[str]'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;34m'task_*.py'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mtrace\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'bool'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;32mFalse\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0mverbose\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'int'\u001b[0m \u001b[0;34m=\u001b[0m \u001b[0;36m1\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m \u001b[0;34m**\u001b[0m\u001b[0mkwargs\u001b[0m\u001b[0;34m:\u001b[0m \u001b[0;34m'Any'\u001b[0m\u001b[0;34m,\u001b[0m\u001b[0;34m\u001b[0m\n", - "\u001b[0;34m\u001b[0m\u001b[0;34m)\u001b[0m \u001b[0;34m->\u001b[0m \u001b[0;34m'Session'\u001b[0m\u001b[0;34m\u001b[0m\u001b[0;34m\u001b[0m\u001b[0m\n", - "\u001b[0;31mDocstring:\u001b[0m\n", + "\u001b[31mSignature:\u001b[39m\n", + "pytask.build(\n", + " *,\n", + " capture: \u001b[33m\"Literal['fd', 'no', 'sys', 'tee-sys'] | CaptureMethod\"\u001b[39m = ,\n", + " check_casing_of_paths: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mTrue\u001b[39;00m,\n", + " config: \u001b[33m'Path | None'\u001b[39m = \u001b[38;5;28;01mNone\u001b[39;00m,\n", + " database_url: \u001b[33m'str'\u001b[39m = \u001b[33m''\u001b[39m,\n", + " debug_pytask: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " disable_warnings: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " dry_run: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " editor_url_scheme: \u001b[33m\"Literal['no_link', 'file', 'vscode', 'pycharm'] | str\"\u001b[39m = \u001b[33m'file'\u001b[39m,\n", + " expression: \u001b[33m'str'\u001b[39m = \u001b[33m''\u001b[39m,\n", + " force: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " ignore: \u001b[33m'Iterable[str]'\u001b[39m = (),\n", + " marker_expression: \u001b[33m'str'\u001b[39m = \u001b[33m''\u001b[39m,\n", + " max_failures: \u001b[33m'float'\u001b[39m = inf,\n", + " n_entries_in_table: \u001b[33m'int'\u001b[39m = \u001b[32m15\u001b[39m,\n", + " paths: \u001b[33m'Path | Iterable[Path]'\u001b[39m = (),\n", + " pdb: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " pdb_cls: \u001b[33m'str'\u001b[39m = \u001b[33m''\u001b[39m,\n", + " s: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " show_capture: \u001b[33m\"Literal['no', 'stdout', 'stderr', 'all'] | ShowCapture\"\u001b[39m = ,\n", + " show_errors_immediately: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " show_locals: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " show_traceback: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mTrue\u001b[39;00m,\n", + " sort_table: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mTrue\u001b[39;00m,\n", + " stop_after_first_failure: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " strict_markers: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " tasks: \u001b[33m'Callable[..., Any] | PTask | Iterable[Callable[..., Any] | PTask]'\u001b[39m = (),\n", + " task_files: \u001b[33m'Iterable[str]'\u001b[39m = (\u001b[33m'task_*.py'\u001b[39m,),\n", + " trace: \u001b[33m'bool'\u001b[39m = \u001b[38;5;28;01mFalse\u001b[39;00m,\n", + " verbose: \u001b[33m'int'\u001b[39m = \u001b[32m1\u001b[39m,\n", + " **kwargs: \u001b[33m'Any'\u001b[39m,\n", + ") -> \u001b[33m'Session'\u001b[39m\n", + "\u001b[31mDocstring:\u001b[39m\n", "Run pytask.\n", "\n", "This is the main command to run pytask which usually receives kwargs from the\n", @@ -362,14 +362,14 @@ " Whether a dry-run should be performed that shows which tasks need to be rerun.\n", "editor_url_scheme\n", " An url scheme that allows to click on task names, node names and filenames and\n", - " jump right into you preferred edior to the right line.\n", + " jump right into you preferred editor to the right line.\n", "expression\n", " Same as ``-k`` on the command line. Select tasks via expressions on task ids.\n", "force\n", " Run tasks even though they would be skipped since nothing has changed.\n", "ignore\n", - " A pattern to ignore files or directories. Refer to ``pathlib.Path.match``\n", - " for more info.\n", + " A pattern to ignore files or directories. Refer to ``pathlib.Path.match`` for\n", + " more info.\n", "marker_expression\n", " Same as ``-m`` on the command line. Select tasks via marker expressions.\n", "max_failures\n", @@ -386,7 +386,7 @@ " Start a custom debugger on errors. For example:\n", " ``--pdbcls=IPython.terminal.debugger:TerminalPdb``\n", "s\n", - " Shortcut for ``pytask.build(capture\"no\")``.\n", + " Shortcut for ``capture=\"no\"``.\n", "show_capture\n", " Choose which captured output should be shown for failed tasks.\n", "show_errors_immediately\n", @@ -402,7 +402,8 @@ "strict_markers\n", " Raise errors for unknown markers.\n", "tasks\n", - " A task or a collection of tasks that is passed to ``pytask.build(tasks=...)``.\n", + " A task or a collection of tasks which can be callables or instances following\n", + " {class}`~pytask.PTask`.\n", "task_files\n", " A pattern to describe modules that contain tasks.\n", "trace\n", @@ -414,8 +415,8 @@ "-------\n", "session : pytask.Session\n", " The session captures all the information of the current run.\n", - "\u001b[0;31mFile:\u001b[0m ~/git/pytask/src/_pytask/build.py\n", - "\u001b[0;31mType:\u001b[0m function" + "\u001b[31mFile:\u001b[39m ~/git/pytask/src/_pytask/build.py\n", + "\u001b[31mType:\u001b[39m function" ] } ], @@ -437,7 +438,7 @@ ], "metadata": { "kernelspec": { - "display_name": "pytask", + "display_name": ".venv", "language": "python", "name": "python3" }, @@ -451,7 +452,7 @@ "name": "python", "nbconvert_exporter": "python", "pygments_lexer": "ipython3", - "version": "3.11.5" + "version": "3.12.2" } }, "nbformat": 4, diff --git a/pyproject.toml b/pyproject.toml index 10e4e536..042f00bd 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,7 @@ classifiers = [ dynamic = ["version"] dependencies = [ "attrs>=21.3.0", - "click>=8.1.8,<8.2.0", + "click>=8.1.8,!=8.2.0", "click-default-group>=1.2.4", "networkx>=2.4.0", "optree>=0.9.0", @@ -51,6 +51,7 @@ dev = ["pygraphviz>=1.11;platform_system=='Linux'"] docs = [ "furo>=2024.8.6", "ipython>=8.13.2", + "ipywidgets>=8.1.6", "matplotlib>=3.5.0", "myst-parser>=3.0.0", "myst-nb>=1.2.0", diff --git a/src/_pytask/cli.py b/src/_pytask/cli.py index 13046646..49ab6464 100644 --- a/src/_pytask/cli.py +++ b/src/_pytask/cli.py @@ -2,10 +2,10 @@ from __future__ import annotations +import importlib.metadata from typing import Any import click -from packaging.version import parse as parse_version from _pytask.click import ColoredGroup from _pytask.pluginmanager import storage @@ -16,7 +16,7 @@ } -if parse_version(click.__version__) >= parse_version("8"): # pragma: no cover +if importlib.metadata.version("click") >= "8": # pragma: no cover _VERSION_OPTION_KWARGS = {"package_name": "pytask"} else: # pragma: no cover _VERSION_OPTION_KWARGS = {} diff --git a/src/_pytask/click.py b/src/_pytask/click.py index 9b19eb93..7da27f08 100644 --- a/src/_pytask/click.py +++ b/src/_pytask/click.py @@ -2,6 +2,7 @@ from __future__ import annotations +import importlib.metadata import inspect from enum import Enum from gettext import gettext as _ @@ -9,15 +10,16 @@ from typing import TYPE_CHECKING from typing import Any from typing import ClassVar +from typing import TypeVar import click from click import Choice from click import Command from click import Context from click import Parameter -from click.parser import split_opt from click_default_group import DefaultGroup from rich.highlighter import RegexHighlighter +from rich.padding import Padding from rich.panel import Panel from rich.table import Table from rich.text import Text @@ -27,40 +29,59 @@ from _pytask.console import create_panel_title if TYPE_CHECKING: + from collections.abc import Iterable from collections.abc import Sequence __all__ = ["ColoredCommand", "ColoredGroup", "EnumChoice"] -class EnumChoice(Choice): - """An enum-based choice type. +if importlib.metadata.version("click") < "8.2": + from click.parser import split_opt - The implementation is copied from https://github.com/pallets/click/pull/2210 and - related discussion can be found in https://github.com/pallets/click/issues/605. + class EnumChoice(Choice): + """An enum-based choice type. - In contrast to using :class:`click.Choice`, using this type ensures that the error - message does not show the enum members. + The implementation is copied from https://github.com/pallets/click/pull/2210 and + related discussion can be found in https://github.com/pallets/click/issues/605. - In contrast to the proposed implementation in the PR, this implementation does not - use the members than rather the values of the enum. + In contrast to using :class:`click.Choice`, using this type ensures that the + error message does not show the enum members. - """ + In contrast to the proposed implementation in the PR, this implementation does + not use the members than rather the values of the enum. - def __init__(self, enum_type: type[Enum], case_sensitive: bool = True) -> None: - super().__init__( - choices=[element.value for element in enum_type], - case_sensitive=case_sensitive, - ) - self.enum_type = enum_type + """ + + def __init__(self, enum_type: type[Enum], case_sensitive: bool = True) -> None: + super().__init__( + choices=[element.value for element in enum_type], + case_sensitive=case_sensitive, + ) + self.enum_type = enum_type + + def convert( + self, value: Any, param: Parameter | None, ctx: Context | None + ) -> Any: + if isinstance(value, Enum): + value = value.value + value = super().convert(value=value, param=param, ctx=ctx) + if value is None: + return None + return self.enum_type(value) + +else: + from click.parser import ( # type: ignore[attr-defined, no-redef] + _split_opt as split_opt, + ) + + ParamTypeValue = TypeVar("ParamTypeValue") - def convert(self, value: Any, param: Parameter | None, ctx: Context | None) -> Any: - if isinstance(value, Enum): - value = value.value - value = super().convert(value=value, param=param, ctx=ctx) - if value is None: - return None - return self.enum_type(value) + class EnumChoice(Choice): # type: ignore[no-redef] + def __init__( + self, choices: Iterable[ParamTypeValue], case_sensitive: bool = False + ) -> None: + super().__init__(choices=choices, case_sensitive=case_sensitive) # type: ignore[arg-type] class _OptionHighlighter(RegexHighlighter): @@ -119,7 +140,10 @@ def format_help( _print_options(self, ctx) console.print( - "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", + Padding( + "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", + (0, 3, 0, 0), + ), justify="right", ) @@ -197,7 +221,10 @@ def format_help( _print_options(self, ctx) console.print( - "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", + Padding( + "[bold #FF0000]♥[/] [#f2f2f2]https://pytask-dev.readthedocs.io[/]", + (0, 3, 0, 0), + ), justify="right", ) diff --git a/uv.lock b/uv.lock index 5591300d..86aff736 100644 --- a/uv.lock +++ b/uv.lock @@ -3444,6 +3444,7 @@ docs = [ { name = "ipython", version = "8.18.1", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "ipython", version = "8.37.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version == '3.10.*'" }, { name = "ipython", version = "9.3.0", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.11'" }, + { name = "ipywidgets" }, { name = "matplotlib", version = "3.9.4", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version < '3.10'" }, { name = "matplotlib", version = "3.10.3", source = { registry = "https://pypi.org/simple" }, marker = "python_full_version >= '3.10'" }, { name = "myst-nb" }, @@ -3470,6 +3471,7 @@ test = [ { name = "deepdiff" }, { name = "nbmake" }, { name = "pexpect" }, + { name = "pygments" }, { name = "pytest" }, { name = "pytest-cov" }, { name = "pytest-xdist" }, @@ -3483,16 +3485,16 @@ typing = [ [package.metadata] requires-dist = [ { name = "attrs", specifier = ">=21.3.0" }, - { name = "click", specifier = ">=8.1.8,<8.2.0" }, + { name = "click", specifier = ">=8.1.8,!=8.2.0" }, { name = "click-default-group", specifier = ">=1.2.4" }, - { name = "networkx", specifier = ">=3.0.0" }, + { name = "networkx", specifier = ">=2.4.0" }, { name = "optree", specifier = ">=0.9.0" }, { name = "packaging", specifier = ">=23.0.0" }, { name = "pluggy", specifier = ">=1.3.0" }, { name = "rich", specifier = ">=13.8.0" }, - { name = "sqlalchemy", specifier = ">=2.0.0" }, + { name = "sqlalchemy", specifier = ">=2.0.31" }, { name = "tomli", marker = "python_full_version < '3.11'", specifier = ">=1" }, - { name = "typing-extensions", marker = "python_full_version < '3.11'", specifier = ">=4.5.0" }, + { name = "typing-extensions", marker = "python_full_version < '3.11'", specifier = ">=4.8.0" }, { name = "universal-pathlib", specifier = ">=0.2.2" }, ] @@ -3501,30 +3503,32 @@ dev = [{ name = "pygraphviz", marker = "sys_platform == 'linux'", specifier = "> docs = [ { name = "furo", specifier = ">=2024.8.6" }, { name = "ipython", specifier = ">=8.13.2" }, + { name = "ipywidgets", specifier = ">=8.1.6" }, { name = "matplotlib", specifier = ">=3.5.0" }, - { name = "myst-nb" }, - { name = "myst-parser" }, + { name = "myst-nb", specifier = ">=1.2.0" }, + { name = "myst-parser", specifier = ">=3.0.0" }, { name = "sphinx", specifier = ">=7.0.0" }, - { name = "sphinx-click" }, - { name = "sphinx-copybutton" }, + { name = "sphinx-click", specifier = ">=6.0.0" }, + { name = "sphinx-copybutton", specifier = ">=0.5.2" }, { name = "sphinx-design", specifier = ">=0.3" }, - { name = "sphinx-toolbox" }, - { name = "sphinxext-opengraph" }, + { name = "sphinx-toolbox", specifier = ">=4.0.0" }, + { name = "sphinxext-opengraph", specifier = ">=0.10.0" }, ] plugin-list = [ - { name = "httpx" }, + { name = "httpx", specifier = ">=0.27.0" }, { name = "tabulate", extras = ["widechars"], specifier = ">=0.9.0" }, - { name = "tqdm" }, + { name = "tqdm", specifier = ">=4.66.3" }, ] test = [ { name = "aiohttp", specifier = ">=3.11.0" }, { name = "cloudpickle", specifier = ">=3.0.0" }, { name = "coiled", specifier = ">=1.42.0" }, - { name = "deepdiff" }, - { name = "nbmake" }, - { name = "pexpect" }, + { name = "deepdiff", specifier = ">=7.0.0" }, + { name = "nbmake", specifier = ">=1.5.5" }, + { name = "pexpect", specifier = ">=4.9.0" }, + { name = "pygments", specifier = ">=2.18.0" }, { name = "pytest", specifier = ">=8.4.0" }, - { name = "pytest-cov" }, + { name = "pytest-cov", specifier = ">=5.0.0" }, { name = "pytest-xdist", specifier = ">=3.6.1" }, { name = "syrupy", specifier = ">=4.5.0" }, ]