diff --git a/Makefile b/Makefile index 48bfd5ef0..189450632 100644 --- a/Makefile +++ b/Makefile @@ -31,7 +31,7 @@ lint: ## Run a `lint` process on Hera and report problems .PHONY: test test: ## Run tests for Hera - @poetry run python -m pytest -v + @poetry run python -m pytest .PHONY: workflows-models workflows-models: ## Generate the Workflows models portion of Argo Workflows diff --git a/docs/examples/workflows/global_config.md b/docs/examples/workflows/global_config.md index a48e5fb3f..721162467 100644 --- a/docs/examples/workflows/global_config.md +++ b/docs/examples/workflows/global_config.md @@ -15,6 +15,7 @@ global_config.namespace = "argo-namespace" global_config.service_account_name = "argo-account" global_config.image = "image-say" + global_config.script_command = ["python3"] @script() @@ -43,14 +44,19 @@ command: - cowsay image: docker/whalesay:latest - - name: 'say' + - name: say script: - command: ['python'] - image: 'image-say' - source: | - import os + command: + - python3 + image: image-say + source: 'import os + import sys + sys.path.append(os.getcwd()) + print("hello") + + ' ``` diff --git a/examples/workflows/global-config.yaml b/examples/workflows/global-config.yaml index dd7f52e5e..7a3dfed37 100644 --- a/examples/workflows/global-config.yaml +++ b/examples/workflows/global-config.yaml @@ -11,12 +11,17 @@ spec: command: - cowsay image: docker/whalesay:latest - - name: 'say' + - name: say script: - command: ['python'] - image: 'image-say' - source: | - import os + command: + - python3 + image: image-say + source: 'import os + import sys + sys.path.append(os.getcwd()) + print("hello") + + ' diff --git a/examples/workflows/global_config.py b/examples/workflows/global_config.py index 5d60eff61..6ede4a240 100644 --- a/examples/workflows/global_config.py +++ b/examples/workflows/global_config.py @@ -5,6 +5,7 @@ global_config.namespace = "argo-namespace" global_config.service_account_name = "argo-account" global_config.image = "image-say" +global_config.script_command = ["python3"] @script() diff --git a/poetry.lock b/poetry.lock index 29d181b0a..c1df6d384 100644 --- a/poetry.lock +++ b/poetry.lock @@ -1,4 +1,4 @@ -# This file is automatically @generated by Poetry 1.4.0 and should not be changed by hand. +# This file is automatically @generated by Poetry 1.4.1 and should not be changed by hand. [[package]] name = "anyio" @@ -715,6 +715,31 @@ files = [ {file = "lazy_object_proxy-1.9.0-cp39-cp39-win_amd64.whl", hash = "sha256:db1c1722726f47e10e0b5fdbf15ac3b8adb58c091d12b3ab713965795036985f"}, ] +[[package]] +name = "markdown-it-py" +version = "2.2.0" +description = "Python port of markdown-it. Markdown parsing, done right!" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "markdown-it-py-2.2.0.tar.gz", hash = "sha256:7c9a5e412688bc771c67432cbfebcdd686c93ce6484913dccf06cb5a0bea35a1"}, + {file = "markdown_it_py-2.2.0-py3-none-any.whl", hash = "sha256:5a35f8d1870171d9acc47b99612dc146129b631baf04970128b568f190d0cc30"}, +] + +[package.dependencies] +mdurl = ">=0.1,<1.0" + +[package.extras] +benchmarking = ["psutil", "pytest", "pytest-benchmark"] +code-style = ["pre-commit (>=3.0,<4.0)"] +compare = ["commonmark (>=0.9,<1.0)", "markdown (>=3.4,<4.0)", "mistletoe (>=1.0,<2.0)", "mistune (>=2.0,<3.0)", "panflute (>=2.3,<3.0)"] +linkify = ["linkify-it-py (>=1,<3)"] +plugins = ["mdit-py-plugins"] +profiling = ["gprof2dot"] +rtd = ["attrs", "myst-parser", "pyyaml", "sphinx", "sphinx-copybutton", "sphinx-design", "sphinx_book_theme"] +testing = ["coverage", "pytest", "pytest-cov", "pytest-regressions"] + [[package]] name = "markupsafe" version = "2.1.2" @@ -775,6 +800,18 @@ files = [ {file = "MarkupSafe-2.1.2.tar.gz", hash = "sha256:abcabc8c2b26036d62d4c746381a6f7cf60aafcc653198ad678306986b09450d"}, ] +[[package]] +name = "mdurl" +version = "0.1.2" +description = "Markdown URL utilities" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "mdurl-0.1.2-py3-none-any.whl", hash = "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8"}, + {file = "mdurl-0.1.2.tar.gz", hash = "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba"}, +] + [[package]] name = "mypy" version = "1.0.1" @@ -970,6 +1007,18 @@ files = [ {file = "ply-3.11.tar.gz", hash = "sha256:00c7c1aaa88358b9c765b6d3000c6eec0ba42abca5351b095321aef446081da3"}, ] +[[package]] +name = "pprintpp" +version = "0.4.0" +description = "A drop-in replacement for pprint that's actually pretty" +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pprintpp-0.4.0-py2.py3-none-any.whl", hash = "sha256:b6b4dcdd0c0c0d75e4d7b2f21a9e933e5b2ce62b26e1a54537f9651ae5a5c01d"}, + {file = "pprintpp-0.4.0.tar.gz", hash = "sha256:ea826108e2c7f49dc6d66c752973c3fc9749142a798d6b254e1e301cfdbc6403"}, +] + [[package]] name = "prance" version = "0.21.8.0" @@ -1051,6 +1100,21 @@ typing-extensions = ">=4.2.0" dotenv = ["python-dotenv (>=0.10.4)"] email = ["email-validator (>=1.0.3)"] +[[package]] +name = "pygments" +version = "2.14.0" +description = "Pygments is a syntax highlighting package written in Python." +category = "dev" +optional = false +python-versions = ">=3.6" +files = [ + {file = "Pygments-2.14.0-py3-none-any.whl", hash = "sha256:fa7bd7bd2771287c0de303af8bfdfc731f51bd2c6a47ab69d117138893b82717"}, + {file = "Pygments-2.14.0.tar.gz", hash = "sha256:b3ed06a9e8ac9a9aae5a6f5dbe78a8a58655d17b43b93c078f094ddc476ae297"}, +] + +[package.extras] +plugins = ["importlib-metadata"] + [[package]] name = "pyproject-hooks" version = "1.0.0" @@ -1142,6 +1206,22 @@ tomli = {version = ">=1.0.0", markers = "python_version < \"3.11\""} [package.extras] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "pygments (>=2.7.2)", "requests", "xmlschema"] +[[package]] +name = "pytest-clarity" +version = "1.0.1" +description = "A plugin providing an alternative, colourful diff output for failing assertions." +category = "dev" +optional = false +python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" +files = [ + {file = "pytest-clarity-1.0.1.tar.gz", hash = "sha256:505fe345fad4fe11c6a4187fe683f2c7c52c077caa1e135f3e483fe112db7772"}, +] + +[package.dependencies] +pprintpp = ">=0.4.0" +pytest = ">=3.5.0" +rich = ">=8.0.0" + [[package]] name = "pytest-cov" version = "4.0.0" @@ -1161,6 +1241,23 @@ pytest = ">=4.6" [package.extras] testing = ["fields", "hunter", "process-tests", "pytest-xdist", "six", "virtualenv"] +[[package]] +name = "pytest-sugar" +version = "0.9.6" +description = "pytest-sugar is a plugin for pytest that changes the default look and feel of pytest (e.g. progressbar, show tests that fail instantly)." +category = "dev" +optional = false +python-versions = "*" +files = [ + {file = "pytest-sugar-0.9.6.tar.gz", hash = "sha256:c4793495f3c32e114f0f5416290946c316eb96ad5a3684dcdadda9267e59b2b8"}, + {file = "pytest_sugar-0.9.6-py2.py3-none-any.whl", hash = "sha256:30e5225ed2b3cc988a8a672f8bda0fc37bcd92d62e9273937f061112b3f2186d"}, +] + +[package.dependencies] +packaging = ">=14.1" +pytest = ">=2.9" +termcolor = ">=1.1.0" + [[package]] name = "pyyaml" version = "6.0" @@ -1251,6 +1348,26 @@ idna = {version = "*", optional = true, markers = "extra == \"idna2008\""} [package.extras] idna2008 = ["idna"] +[[package]] +name = "rich" +version = "13.3.3" +description = "Render rich text, tables, progress bars, syntax highlighting, markdown and more to the terminal" +category = "dev" +optional = false +python-versions = ">=3.7.0" +files = [ + {file = "rich-13.3.3-py3-none-any.whl", hash = "sha256:540c7d6d26a1178e8e8b37e9ba44573a3cd1464ff6348b99ee7061b95d1c6333"}, + {file = "rich-13.3.3.tar.gz", hash = "sha256:dc84400a9d842b3a9c5ff74addd8eb798d155f36c1c91303888e0a66850d2a15"}, +] + +[package.dependencies] +markdown-it-py = ">=2.2.0,<3.0.0" +pygments = ">=2.13.0,<3.0.0" +typing-extensions = {version = ">=4.0.0,<5.0", markers = "python_version < \"3.9\""} + +[package.extras] +jupyter = ["ipywidgets (>=7.5.1,<9)"] + [[package]] name = "ruamel-yaml" version = "0.17.21" @@ -1379,6 +1496,21 @@ files = [ {file = "sniffio-1.3.0.tar.gz", hash = "sha256:e60305c5e5d314f5389259b7f22aaa33d8f7dee49763119234af3755c55b9101"}, ] +[[package]] +name = "termcolor" +version = "2.2.0" +description = "ANSI color formatting for output in terminal" +category = "dev" +optional = false +python-versions = ">=3.7" +files = [ + {file = "termcolor-2.2.0-py3-none-any.whl", hash = "sha256:91ddd848e7251200eac969846cbae2dacd7d71c2871e92733289e7e3666f48e7"}, + {file = "termcolor-2.2.0.tar.gz", hash = "sha256:dfc8ac3f350788f23b2947b3e6cfa5a53b630b612e6cd8965a015a776020b99a"}, +] + +[package.extras] +tests = ["pytest", "pytest-cov"] + [[package]] name = "toml" version = "0.10.2" @@ -1527,4 +1659,4 @@ yaml = ["pyyaml"] [metadata] lock-version = "2.0" python-versions = ">=3.8,<4" -content-hash = "a9be7ef38b884976411795505ac0c5175f8374bb24b32ff4eac51c885766a759" +content-hash = "7e0fbd207804f888fdafe22eca755001f3cf9ec9cb8b5a6b50759c393bea09af" diff --git a/pyproject.toml b/pyproject.toml index 329d62c99..7a8c21da8 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -48,6 +48,8 @@ types-PyYAML = "*" jsonpath-ng = "^1.5.3" datamodel-code-generator = {extras = ["http"], version = "^0.17.1"} types-requests = "^2.28.11.12" +pytest-clarity = "^1.0.1" +pytest-sugar = "^0.9.6" [build-system] requires = ["poetry-core>=1.0.0"] @@ -59,7 +61,7 @@ max-line-length = 119 target-version = ['py38'] [tool.pytest.ini_options] -addopts = "--durations=5 -vv --cov=hera --cov-report=term-missing --cov-report=xml --cov-config=pyproject.toml" +addopts = "-vv --cov=hera --cov-report=xml --cov-config=pyproject.toml" filterwarnings = [ # Hide the hera.host_config deprecations 'ignore:.*is deprecated in favor of `global_config.GlobalConfig', diff --git a/src/hera/shared/_global_config.py b/src/hera/shared/_global_config.py index c1a041abd..d8084f801 100644 --- a/src/hera/shared/_global_config.py +++ b/src/hera/shared/_global_config.py @@ -1,6 +1,7 @@ from __future__ import annotations import inspect +from dataclasses import dataclass, field from typing import Callable, Dict, List, Optional, Type, Union from ._base_model import TBase @@ -9,6 +10,7 @@ _HookMap = Dict[Type[TBase], List[Hook]] +@dataclass class _GlobalConfig: """Hera global configuration holds any user configuration such as global tokens, hooks, etc. @@ -27,12 +29,12 @@ class _GlobalConfig: namespace: Optional[str] = None _image: Union[str, Callable[[], str]] = "python:3.7" service_account_name: Optional[str] = None - script_command: Optional[List[str]] = ["python"] + script_command: Optional[List[str]] = field(default_factory=lambda: ["python"]) _pre_build_hooks: Optional[_HookMap] = None def reset(self) -> None: """Resets the global config container to its initial state""" - self.__dict__.clear() # Wipe instance values to fallback to the class defaults + self.__dict__ = _GlobalConfig().__dict__ @property def image(self) -> str: diff --git a/src/hera/workflows/script.py b/src/hera/workflows/script.py index 32450aadd..4c14762d5 100644 --- a/src/hera/workflows/script.py +++ b/src/hera/workflows/script.py @@ -8,6 +8,8 @@ import textwrap from typing import Callable, Dict, List, Optional, Union +from pydantic import validator + from hera.shared import global_config from hera.workflows._mixins import ( CallableTemplateMixin, @@ -43,13 +45,20 @@ class Script( container_name: Optional[str] = None args: Optional[List[str]] = None - command: Optional[List[str]] = global_config.script_command + command: Optional[List[str]] = None lifecycle: Optional[Lifecycle] = None security_context: Optional[SecurityContext] = None source: Optional[Union[Callable, str]] = None working_dir: Optional[str] = None add_cwd_to_sys_path: bool = True + @validator("command", pre=True, always=True) + @classmethod + def _check_command(cls, v): + if v is None: + return global_config.script_command + return v + def _get_param_script_portion(self) -> str: """Constructs and returns a script that loads the parameters of the specified arguments. Since Argo passes parameters through {{input.parameters.name}} it can be very cumbersome for users to manage that. This creates a