Skip to content

Commit

Permalink
Update save_state and set_output implementation
Browse files Browse the repository at this point in the history
  • Loading branch information
saadmk11 committed Oct 17, 2022
1 parent ecb6e9f commit fa6f385
Show file tree
Hide file tree
Showing 3 changed files with 73 additions and 76 deletions.
18 changes: 6 additions & 12 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,36 +210,30 @@ GitHub Actions Docs: [error](https://docs.github.com/en/actions/using-workflows/
# ::error title=test title,file=abc.py,col=1,endColumn=2,line=4,endLine=5::test message
```

### **`set_output(name, value, use_subprocess=False)`**
### **`set_output(name, value)`**

Sets an action's output parameter for the running workflow.
Sets a step's output parameter by writing to `GITHUB_OUTPUT` environment file. Note that the step will need an `id` to be defined to later retrieve the output value.
GitHub Actions Docs: [set_output](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#setting-an-output-parameter)

**example:**

```python
>> from github_action_utils import set_output
>> set_output("test_name", "test_value",)
# Output:
# ::set-output name=test_name::test_value
>> set_output("my_output", "test value")
```

### **`save_state(name, value, use_subprocess=False)`**
### **`save_state(name, value)`**

Creates environment variable for sharing state with workflow's pre: or post: actions.
Creates an environment variable by writing this to the `GITHUB_STATE` environment file which is available to workflow's pre: or post: actions.
GitHub Actions Docs: [save_state](https://docs.github.com/en/actions/using-workflows/workflow-commands-for-github-actions#sending-values-to-the-pre-and-post-actions)

**example:**

```python
>> from github_action_utils import save_state
>> save_state("test_name", "test_value",)
# Output:
# ::save-state name=test_name::test_value
>> save_state("my_state", "test value")
```

### **`get_state(name)`**
Expand Down
64 changes: 34 additions & 30 deletions github_action_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
from contextlib import contextmanager
from functools import lru_cache
from typing import Any, Dict, Generator, Union
from warnings import warn

if sys.version_info >= (3, 8):
from typing import Literal
Expand Down Expand Up @@ -136,24 +137,32 @@ def _build_options_string(**kwargs: Any) -> str:
)


def set_output(name: str, value: Any, use_subprocess: bool = False) -> None:
"""
Sets an action's output parameter.
def _build_file_input(name: str, value: Any) -> bytes:
return (
f"{_escape_property(name)}"
f"<<{ACTION_ENV_DELIMITER}\n"
f"{_escape_data(value)}\n"
f"{ACTION_ENV_DELIMITER}\n".encode("utf-8")
)

Template: ::set-output name={name}::{value}
Example: echo "::set-output name=action_fruit::strawberry"

def set_output(name: str, value: Any, use_subprocess: Union[bool, None] = None) -> None:
"""
sets out for your workflow using GITHUB_OUTPUT file.
:param name: name of the output
:param value: value of the output
:param use_subprocess: use subprocess module to echo command
:returns: None
"""
_print_command(
"set-output",
value,
options_string=_build_options_string(name=name),
use_subprocess=use_subprocess,
)
if use_subprocess is not None:
warn(
"Argument `use_subprocess` for `set_output()` is deprecated and "
"going to be removed in the next version.",
DeprecationWarning,
)

with open(os.environ["GITHUB_OUTPUT"], "ab") as f:
f.write(_build_file_input(name, value))


def echo(message: Any, use_subprocess: bool = False) -> None:
Expand Down Expand Up @@ -317,24 +326,24 @@ def error(
)


def save_state(name: str, value: Any, use_subprocess: bool = False) -> None:
def save_state(name: str, value: Any, use_subprocess: Union[bool, None] = None) -> None:
"""
creates environment variable for sharing with your workflow's pre: or post: actions.
Template: ::save-state name={name}::{value}
Example: echo "::save-state name=processID::12345"
sets state for your workflow using $GITHUB_STATE file
for sharing it with your workflow's pre: or post: actions.
:param name: Name of the state environment variable (e.g: STATE_{name})
:param value: value of the state environment variable
:param use_subprocess: use subprocess module to echo command
:returns: None
"""
_print_command(
"save-state",
value,
options_string=_build_options_string(name=name),
use_subprocess=use_subprocess,
)
if use_subprocess is not None:
warn(
"Argument `use_subprocess` for `save_state()` is deprecated and "
"going to be removed in the next version.",
DeprecationWarning,
)

with open(os.environ["GITHUB_STATE"], "ab") as f:
f.write(_build_file_input(name, value))


def get_state(name: str) -> Union[str, None]:
Expand Down Expand Up @@ -484,12 +493,7 @@ def set_env(name: str, value: Any) -> None:
:returns: None
"""
with open(os.environ["GITHUB_ENV"], "ab") as f:
f.write(
f"{_escape_property(name)}"
f"<<{ACTION_ENV_DELIMITER}\n"
f"{_escape_data(value)}\n"
f"{ACTION_ENV_DELIMITER}\n".encode("utf-8")
)
f.write(_build_file_input(name, value))


def get_workflow_environment_variables() -> Dict[str, Any]:
Expand Down
67 changes: 33 additions & 34 deletions tests/test_github_action_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -117,6 +117,13 @@ def test__build_options_string(input_kwargs: Any, expected: str) -> None:
assert gha_utils._build_options_string(**input_kwargs) == expected


def test__build_file_input() -> None:
assert (
gha_utils._build_file_input("test", "value")
== b"test<<__ENV_DELIMITER__\nvalue\n__ENV_DELIMITER__\n"
)


@pytest.mark.parametrize(
"test_input,expected",
[
Expand Down Expand Up @@ -235,42 +242,34 @@ def test_error(
assert out == expected


@pytest.mark.parametrize(
"input_args,expected",
[
(["abc", 123], "::set-output name=abc::123\n"),
(["abc", "test"], "::set-output name=abc::test\n"),
(["abc", {"k": "v"}], '::set-output name=abc::{"k": "v"}\n'),
],
)
def test_set_output(
capfd: Any,
input_args: Any,
expected: str,
) -> None:
gha_utils.set_output(*input_args)
out, err = capfd.readouterr()
print(out)
assert out == expected
def test_set_output(tmpdir: Any) -> None:
file = tmpdir.join("output_file")

with mock.patch.dict(os.environ, {"GITHUB_OUTPUT": file.strpath}):
gha_utils.set_output("test", "test")
gha_utils.set_output("another", 2)

@pytest.mark.parametrize(
"input_args,expected",
[
(["abc", 123], "::save-state name=abc::123\n"),
(["abc", "test"], "::save-state name=abc::test\n"),
(["abc", {"k": "v"}], '::save-state name=abc::{"k": "v"}\n'),
],
)
def test_save_state(
capfd: Any,
input_args: Any,
expected: str,
) -> None:
gha_utils.save_state(*input_args)
out, err = capfd.readouterr()
print(out)
assert out == expected
assert file.read() == (
"test<<__ENV_DELIMITER__\n"
"test\n__ENV_DELIMITER__\n"
"another<<__ENV_DELIMITER__\n2\n"
"__ENV_DELIMITER__\n"
)


def test_save_state(tmpdir: Any) -> None:
file = tmpdir.join("state_file")

with mock.patch.dict(os.environ, {"GITHUB_STATE": file.strpath}):
gha_utils.save_state("test", "test")
gha_utils.save_state("another", 2)

assert file.read() == (
"test<<__ENV_DELIMITER__\n"
"test\n__ENV_DELIMITER__\n"
"another<<__ENV_DELIMITER__\n2\n"
"__ENV_DELIMITER__\n"
)


@mock.patch.dict(os.environ, {"STATE_test_state": "test", "abc": "another test"})
Expand Down

0 comments on commit fa6f385

Please sign in to comment.