Skip to content

Reference

github_custom_actions

Python package for creating custom GitHub Actions.

The file is mandatory for build system to find the package.

ActionBase

Base class for GitHub Actions.

You should implement main() method in the subclass.

You can define custom inputs and / or outputs types in the subclass. You can do nothing in the subclass if you don't need typed inputs and outputs.

Note these are just types, instances of these types are automatically created in the __init__ method.

Usage:

class MyInputs(ActionInputs):
    my_input: str
    '''My input description'''

    my_path: Path
    '''My path description'''

class MyOutputs(ActionOutputs):
    runner_os: str
    '''Runner OS description'''

class MyAction(ActionBase):
    inputs: MyInputs
    outputs: MyOutputs

    def main(self):
        if self.inputs.my_path is None:
            raise ValueError("my-path is required")
        self.inputs.my_path.mkdir(exist_ok=True)
        self.outputs.runner_os = self.env.runner_os
        self.summary += (
            self.render(
                "### {{ inputs.my_input }}.\n"
                "Have a nice day, {{ inputs['name'] }}!"
            )
        )

if __name__ == "__main__":
    MyAction().run()

Source code in src/github_custom_actions/action_base.py
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
class ActionBase:
    """Base class for GitHub Actions.

    You should implement `main()` method in the subclass.

    You can define custom inputs and / or outputs types in the subclass.
    You can do nothing in the subclass if you don't need typed inputs and outputs.

    Note these are just types, instances of these types are automatically created
    in the `__init__` method.

    Usage:
    ```python
    class MyInputs(ActionInputs):
        my_input: str
        '''My input description'''

        my_path: Path
        '''My path description'''

    class MyOutputs(ActionOutputs):
        runner_os: str
        '''Runner OS description'''

    class MyAction(ActionBase):
        inputs: MyInputs
        outputs: MyOutputs

        def main(self):
            if self.inputs.my_path is None:
                raise ValueError("my-path is required")
            self.inputs.my_path.mkdir(exist_ok=True)
            self.outputs.runner_os = self.env.runner_os
            self.summary += (
                self.render(
                    "### {{ inputs.my_input }}.\\n"
                    "Have a nice day, {{ inputs['name'] }}!"
                )
            )

    if __name__ == "__main__":
        MyAction().run()
    ```
    """

    inputs: ActionInputs
    outputs: ActionOutputs
    env: GithubVars

    def __init__(self) -> None:
        """Initialize inputs, outputs according to the type than could be set in subclass."""
        types = get_type_hints(self.__class__)
        self.inputs = types["inputs"]()
        self.outputs = types["outputs"]()
        self.env = GithubVars()

        base_dir = Path(__file__).resolve().parent
        templates_dir = base_dir / "templates"
        self.environment = Environment(  # noqa: S701
            loader=FileSystemLoader(str(templates_dir)),
        )

    summary = FileTextProperty("github_step_summary")

    def main(self) -> None:
        """Business logic of the action.

        Is called by `run()` method.
        """
        raise NotImplementedError

    def run(self) -> None:
        """Run the action.

        `run()` calls the `main()` method of the action with the necessary boilerplate to catch and
        report exceptions.

        Usage:
        ```python
        if __name__ == "__main__":
            MyAction().run()
        ```

        `main()` is where you implement the business logic of your action.
        """
        try:
            self.main()
        except Exception:  # noqa: BLE001
            traceback.print_exc(file=sys.stderr)
            sys.exit(1)

    @staticmethod
    def debug(message: str):
        """
        Emits a debug message. The runner needs to be invoked with enabled debug
        logging to show these.

        Example usage:

        ```python
        self.debug("Action invoked.")
        ```
        """
        print(f"::debug::{message}")

    @staticmethod
    def message(  # noqa: PLR0913
        severity: Literal["error", "notice", "warning"],
        message: str,
        title: Optional[str] = None,
        file: Optional[str] = None,
        line: Optional[int] = None,
        column: Optional[int] = None,
        end_line: Optional[int] = None,
        end_column: Optional[int] = None,
    ):
        """
        Emits a message at the given `severity` level. The keyword arguments can be used
        to generate annotations that will be displayed within the Review section of a
        Pull Request. Refer to the messages related section in the
        [workflows commands reference](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands)
        for semantic details.

        There are also the methods `error_message`, `notice_message` and
        `warning_message` as shortcuts.

        Example usages:

        ```python
        self.message("warning", "Deprecated input used: pattern")
        # or equivalently:
        self.warning_message("Deprecated input used: pattern")

        self.error_message(
            "Value exceeds limit.",
            title="Schema error",
            file="config.yml",
            line=7,
            column=42
        )
        ```

        """
        _locals = locals().copy()
        parameters = ",".join(
            f"{x}={value}"
            for x in ("title", "file", "line", "column", "end_line", "end_column")
            if (value := _locals[x]) is not None
        )
        print(f"::{severity}{(' ' + parameters) if parameters else ''}::{message}")

    error_message = partialmethod(message, "error")
    notice_message = partialmethod(message, "notice")
    warning_message = partialmethod(message, "warning")

    def render(self, template: str, **kwargs: Any) -> str:
        """Render the template from the string with Jinja.

        `kwargs` are the template context variables.

        Also includes to the context the action's `inputs`, `outputs`, and `env`.

        So you can use something like:
        ```python
        self.render("### {{ inputs.name }}!\\nHave a nice day!")
        ```

        """
        return Template(template.replace("\\n", "\n")).render(
            env=self.env,
            inputs=self.inputs,
            outputs=self.outputs,
            **kwargs,
        )

    def render_template(self, template_name: str, **kwargs: Any) -> str:
        """Render template from the `templates` directory.

        `template_name` is the name of the template file without the extension.
        `kwargs` are the template context variables.

        Also includes to the context the action's `inputs`, `outputs`, and `env`.

        Usage:
        ```python
        self.render_template("executor.json", image="ubuntu-latest")
        ```
        """
        template = self.environment.get_template(template_name)
        return template.render(
            env=self.env,
            inputs=self.inputs,
            outputs=self.outputs,
            **kwargs,
        )

__init__()

Initialize inputs, outputs according to the type than could be set in subclass.

Source code in src/github_custom_actions/action_base.py
88
89
90
91
92
93
94
95
96
97
98
99
def __init__(self) -> None:
    """Initialize inputs, outputs according to the type than could be set in subclass."""
    types = get_type_hints(self.__class__)
    self.inputs = types["inputs"]()
    self.outputs = types["outputs"]()
    self.env = GithubVars()

    base_dir = Path(__file__).resolve().parent
    templates_dir = base_dir / "templates"
    self.environment = Environment(  # noqa: S701
        loader=FileSystemLoader(str(templates_dir)),
    )

debug(message) staticmethod

Emits a debug message. The runner needs to be invoked with enabled debug logging to show these.

Example usage:

self.debug("Action invoked.")
Source code in src/github_custom_actions/action_base.py
130
131
132
133
134
135
136
137
138
139
140
141
142
@staticmethod
def debug(message: str):
    """
    Emits a debug message. The runner needs to be invoked with enabled debug
    logging to show these.

    Example usage:

    ```python
    self.debug("Action invoked.")
    ```
    """
    print(f"::debug::{message}")

main()

Business logic of the action.

Is called by run() method.

Source code in src/github_custom_actions/action_base.py
103
104
105
106
107
108
def main(self) -> None:
    """Business logic of the action.

    Is called by `run()` method.
    """
    raise NotImplementedError

message(severity, message, title=None, file=None, line=None, column=None, end_line=None, end_column=None) staticmethod

Emits a message at the given severity level. The keyword arguments can be used to generate annotations that will be displayed within the Review section of a Pull Request. Refer to the messages related section in the workflows commands reference for semantic details.

There are also the methods error_message, notice_message and warning_message as shortcuts.

Example usages:

self.message("warning", "Deprecated input used: pattern")
# or equivalently:
self.warning_message("Deprecated input used: pattern")

self.error_message(
    "Value exceeds limit.",
    title="Schema error",
    file="config.yml",
    line=7,
    column=42
)
Source code in src/github_custom_actions/action_base.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
@staticmethod
def message(  # noqa: PLR0913
    severity: Literal["error", "notice", "warning"],
    message: str,
    title: Optional[str] = None,
    file: Optional[str] = None,
    line: Optional[int] = None,
    column: Optional[int] = None,
    end_line: Optional[int] = None,
    end_column: Optional[int] = None,
):
    """
    Emits a message at the given `severity` level. The keyword arguments can be used
    to generate annotations that will be displayed within the Review section of a
    Pull Request. Refer to the messages related section in the
    [workflows commands reference](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands)
    for semantic details.

    There are also the methods `error_message`, `notice_message` and
    `warning_message` as shortcuts.

    Example usages:

    ```python
    self.message("warning", "Deprecated input used: pattern")
    # or equivalently:
    self.warning_message("Deprecated input used: pattern")

    self.error_message(
        "Value exceeds limit.",
        title="Schema error",
        file="config.yml",
        line=7,
        column=42
    )
    ```

    """
    _locals = locals().copy()
    parameters = ",".join(
        f"{x}={value}"
        for x in ("title", "file", "line", "column", "end_line", "end_column")
        if (value := _locals[x]) is not None
    )
    print(f"::{severity}{(' ' + parameters) if parameters else ''}::{message}")

render(template, **kwargs)

Render the template from the string with Jinja.

kwargs are the template context variables.

Also includes to the context the action's inputs, outputs, and env.

So you can use something like:

self.render("### {{ inputs.name }}!\nHave a nice day!")

Source code in src/github_custom_actions/action_base.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def render(self, template: str, **kwargs: Any) -> str:
    """Render the template from the string with Jinja.

    `kwargs` are the template context variables.

    Also includes to the context the action's `inputs`, `outputs`, and `env`.

    So you can use something like:
    ```python
    self.render("### {{ inputs.name }}!\\nHave a nice day!")
    ```

    """
    return Template(template.replace("\\n", "\n")).render(
        env=self.env,
        inputs=self.inputs,
        outputs=self.outputs,
        **kwargs,
    )

render_template(template_name, **kwargs)

Render template from the templates directory.

template_name is the name of the template file without the extension. kwargs are the template context variables.

Also includes to the context the action's inputs, outputs, and env.

Usage:

self.render_template("executor.json", image="ubuntu-latest")

Source code in src/github_custom_actions/action_base.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def render_template(self, template_name: str, **kwargs: Any) -> str:
    """Render template from the `templates` directory.

    `template_name` is the name of the template file without the extension.
    `kwargs` are the template context variables.

    Also includes to the context the action's `inputs`, `outputs`, and `env`.

    Usage:
    ```python
    self.render_template("executor.json", image="ubuntu-latest")
    ```
    """
    template = self.environment.get_template(template_name)
    return template.render(
        env=self.env,
        inputs=self.inputs,
        outputs=self.outputs,
        **kwargs,
    )

run()

Run the action.

run() calls the main() method of the action with the necessary boilerplate to catch and report exceptions.

Usage:

if __name__ == "__main__":
    MyAction().run()

main() is where you implement the business logic of your action.

Source code in src/github_custom_actions/action_base.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def run(self) -> None:
    """Run the action.

    `run()` calls the `main()` method of the action with the necessary boilerplate to catch and
    report exceptions.

    Usage:
    ```python
    if __name__ == "__main__":
        MyAction().run()
    ```

    `main()` is where you implement the business logic of your action.
    """
    try:
        self.main()
    except Exception:  # noqa: BLE001
        traceback.print_exc(file=sys.stderr)
        sys.exit(1)

ActionInputs

Bases: EnvAttrDictVars

GitHub Action input variables.

Usage
class MyInputs(ActionInputs):
    my_input: str

action = ActionBase(inputs=MyInputs())
print(action.inputs.my_input)
print(action.inputs["my-input"])  # the same as above

With attributes, you can only access explicitly declared vars, with dict-like access you can access any var. This way you can find your balance between strictly defined vars and flexibility.

Attribute names are converted to kebab-case. So action.inputs.my_input is the same as action.inputs["my-input"].

If you need to access a snake_case named input my_input, you should use dict-style only: action.inputs["my_input"]. But it's common to use kebab-case in GitHub Actions input names.

By GitHub convention, all input names are upper-cased in the environment and prefixed with "INPUT_". So actions.inputs.my_input or actions.inputs['my-input'] will be the variable INPUT_MY-INPUT in the environment. The ActionInputs does the conversion automatically.

Uses lazy loading of the values. So the value is read from the environment only when accessed and only once, and saved in the object's internal dict.

Source code in src/github_custom_actions/inputs_outputs.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class ActionInputs(EnvAttrDictVars):
    """GitHub Action input variables.

    Usage:
        ```python
        class MyInputs(ActionInputs):
            my_input: str

        action = ActionBase(inputs=MyInputs())
        print(action.inputs.my_input)
        print(action.inputs["my-input"])  # the same as above
        ```

    With attributes, you can only access explicitly declared vars, with dict-like access
    you can access any var.
    This way you can find your balance between strictly defined vars and flexibility.

    Attribute names are converted to `kebab-case`.
    So `action.inputs.my_input` is the same as `action.inputs["my-input"]`.

    If you need to access a `snake_case` named input `my_input`, you should
    use dict-style only: `action.inputs["my_input"]`.
    But it's common to use `kebab-case` in GitHub Actions input names.

    By GitHub convention, all input names are upper-cased in the environment
    and prefixed with "INPUT_".
    So `actions.inputs.my_input` or `actions.inputs['my-input']` will be the variable
    `INPUT_MY-INPUT` in the environment.
    The ActionInputs does the conversion automatically.

    Uses lazy loading of the values.
    So the value is read from the environment only when accessed and only once,
    and saved in the object's internal dict."""

    # pylint: disable=abstract-method  # we want RO implementation that raises NotImplementedError on write

    def _external_name(self, name: str) -> str:
        """Convert variable name to the external form."""
        return INPUT_PREFIX + name.upper()

ActionOutputs

Bases: FileAttrDictVars

GitHub Actions output variables.

Usage
class MyOutputs(ActionOutputs):
    my_output: str

action = ActionBase(outputs=MyOutputs())
action.outputs["my-output"] = "value"
action.outputs.my_output = "value"  # the same as above

With attributes, you can only access explicitly declared vars, with dict-like access you can access any var. This way you can find your balance between strictly defined vars and flexibility.

Attribute names are converted to kebab-case. So action.outputs.my_output is the same as action.outputs["my-output"].

If you need to access a snake_case named output like my_output you should use dict-style only: action.outputs["my_output"]. But it's common to use kebab-case in GitHub Actions output names.

Each output var assignment changes the GitHub outputs file (the path is defined as action.env.github_output).

Source code in src/github_custom_actions/inputs_outputs.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class ActionOutputs(FileAttrDictVars):
    """GitHub Actions output variables.

    Usage:
       ```python
       class MyOutputs(ActionOutputs):
           my_output: str

       action = ActionBase(outputs=MyOutputs())
       action.outputs["my-output"] = "value"
       action.outputs.my_output = "value"  # the same as above
       ```

    With attributes, you can only access explicitly declared vars,
    with dict-like access you can access any var.
    This way you can find your balance between strictly defined vars and flexibility.

    Attribute names are converted to `kebab-case`.
    So `action.outputs.my_output` is the same as `action.outputs["my-output"]`.

    If you need to access a `snake_case` named output like `my_output` you should
    use dict-style only: `action.outputs["my_output"]`.
    But it's common to use `kebab-case` in GitHub Actions output names.

    Each output var assignment changes the GitHub outputs file
    (the path is defined as `action.env.github_output`).
    """

    def __init__(self) -> None:
        super().__init__(Path(os.environ["GITHUB_OUTPUT"]))

GithubVars

Bases: EnvAttrDictVars

GitHub Action environment variables.

https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables

Usage
class MyAction:
    @property
    def env(self):
        return GithubVars()

action = MyAction()
print(action.env.github_repository)

Thanks to the docstrings your IDE will provide you with doc hints when you hover over the property. We do not load the attributes on the class init but do it Lazily. Once read, the value is stored in the instance dictionary and is not extracted from env anymore.

Converts attribute names to uppercase. Leave dict-style names unchanged.

Paths and files have type Path.

Source code in src/github_custom_actions/github_vars.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
class GithubVars(EnvAttrDictVars):
    """GitHub Action environment variables.

    https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables

    Usage:
       ```python
       class MyAction:
           @property
           def env(self):
               return GithubVars()

       action = MyAction()
       print(action.env.github_repository)
       ```

    Thanks to the docstrings your IDE will provide you with doc hints when
    you hover over the property.
    We do not load the attributes on the class init but do it Lazily.
    Once read, the value is stored in the instance dictionary and is not extracted from env anymore.

    Converts attribute names to uppercase.
    Leave dict-style names unchanged.

    Paths and files have type Path.
    """

    # pylint: disable=abstract-method  # we want RO implementation that raises NotImplementedError on write

    def _attr_to_var_name(self, name: str) -> str:
        return name.upper()

    CI: str
    """Always set to true."""

    github_action: str
    """The name of the action currently running, or the id of a step.
    For example, for an action, __repo-owner_name-of-action-repo.
    GitHub removes special characters, and uses the name __run when the current step runs a script
    without an id. If you use the same script or action more than once in the same job, the name
    will include a suffix that consists of the sequence number preceded by an underscore. For
    example, the first script you run will have the name __run, and the second script will be
    named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2."""

    github_action_path: Path
    """The path where an action is located. This property is only supported in composite actions.
    You can use this path to change directories to where the action is located and access other
    files in that same repository. For example,
    /home/runner/work/_actions/repo-owner/name-of-action-repo/v1."""

    github_action_repository: str
    """For a step executing an action, this is the owner and repository name of the action.
    For example, actions/checkout."""

    github_actions: str
    """Always set to true when GitHub Actions is running the workflow. You can use this variable
    to differentiate when tests are being run locally or by GitHub Actions."""

    github_actor: str
    """The name of the person or app that initiated the workflow. For example, octocat."""

    github_actor_id: str
    """The account ID of the person or app that triggered the initial workflow run.
    For example, 1234567. Note that this is different from the actor username."""

    github_api_url: str
    """Returns the API URL. For example: https://api.github.com."""

    github_base_ref: str
    """The name of the base ref or target branch of the pull request in a workflow run.
    This is only set when the event that triggers a workflow run is either pull_request or
    pull_request_target. For example, main."""

    github_env: str
    """The path on the runner to the file that sets variables from workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/work/_temp/_runner_file_commands/set_env_87406d6e-4979-4d42-98e1-3dab1f48b13a.
    For more information, see "Workflow commands for GitHub Actions"."""

    github_event_name: str
    """The name of the event that triggered the workflow. For example, workflow_dispatch."""

    github_event_path: Path
    """The path to the file on the runner that contains the full event webhook payload.
    For example, /github/workflow/event.json."""

    github_graphql_url: str
    """Returns the GraphQL API URL. For example: https://api.github.com/graphql."""

    github_head_ref: str
    """The head ref or source branch of the pull request in a workflow run. This property is only
    set when the event that triggers a workflow run is either pull_request or pull_request_target.
    For example, feature-branch-1."""

    github_job: str
    """The job_id of the current job. For example, greeting_job."""

    github_output: Path
    """The path on the runner to the file that sets the current step's outputs from
    workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/work/_temp/_runner_file_commands/set_output_a50ef383-b063-46d9-9157-57953fc9f3f0.
    For more information, see "Workflow commands for GitHub Actions"."""

    github_path: Path
    """The path on the runner to the file that sets system PATH variables from workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/work/_temp/_runner_file_commands/add_path_899b9445-ad4a-400c-aa89-249f18632cf5.
    For more information, see "Workflow commands for GitHub Actions"."""

    github_ref: str
    """The fully-formed ref of the branch or tag that triggered the workflow run. For workflows
    triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by
    pull_request, this is the pull request merge branch. For workflows triggered by release,
    this is the release tag created. For other triggers, this is the branch or tag ref that
    triggered the workflow run. This is only set if a branch or tag is available for the
    event type. The ref given is fully-formed, meaning that for branches the format is
    refs/heads/<branch_name>, for pull requests it is refs/pull/<pr_number>/merge,
    and for tags it is refs/tags/<tag_name>. For example, refs/heads/feature-branch-1."""

    github_ref_name: str
    """The short ref name of the branch or tag that triggered the workflow run. This value matches
    the branch or tag name shown on GitHub. For example, feature-branch-1.
    For pull requests, the format is <pr_number>/merge."""

    github_ref_protected: str
    """true if branch protections or rulesets are configured for the ref that triggered the
    workflow run."""

    github_ref_type: str
    """The type of ref that triggered the workflow run. Valid values are branch or tag."""

    github_repository: str
    """The owner and repository name. For example, octocat/Hello-World."""

    github_repository_id: str
    """The ID of the repository. For example, 123456789. Note that this is different from the
    repository name."""

    github_repository_owner: str
    """The repository owner's name. For example, octocat."""

    github_repository_owner_id: str
    """The repository owner's account ID. For example, 1234567. Note that this is different
    from the owner's name."""

    github_retention_days: str
    """The number of days that workflow run logs and artifacts are kept. For example, 90."""

    github_run_attempt: str
    """A unique number for each attempt of a particular workflow run in a repository. This number
    begins at 1 for the workflow run's first attempt, and increments with each re-run.
    For example, 3."""

    github_run_id: str
    """A unique number for each workflow run within a repository. This number does not change if
    you re-run the workflow run. For example, 1658821493."""

    github_run_number: str
    """A unique number for each run of a particular workflow in a repository. This number begins
    at 1 for the workflow's first run, and increments with each new run. This number does not
    change if you re-run the workflow run. For example, 3."""

    github_server_url: str
    """The URL of the GitHub server. For example: https://github.com."""

    github_sha: str
    """The commit SHA that triggered the workflow. The value of this commit SHA depends on the
    event that triggered the workflow. For more information, see "Events that trigger workflows."
    For example, ffac537e6cbbf934b08745a378932722df287a53."""

    github_step_summary: Path
    """The path on the runner to the file that contains job summaries from workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/_layout/_work/_temp/_runner_file_commands/step_summary_1cb22d7f-5663-41a8-
    9ffc-13472605c76c. For more information, see "Workflow commands for GitHub Actions"."""

    github_triggering_actor: str
    """The username of the user that initiated the workflow run. If the workflow run is a re-run,
    this value may differ from github.actor. Any workflow re-runs will use the privileges of
    github.actor, even if the actor initiating the re-run (github.triggering_actor) has different
    privileges."""

    github_workflow: str
    """The name of the workflow. For example, My test workflow. If the workflow file doesn't specify
    a name, the value of this variable is the full path of the workflow file in the repository."""

    github_workflow_ref: str
    """The ref path to the workflow. For example,
    octocat/hello-world/.github/workflows/my-workflow.yml@
    refs/heads/my_branch."""

    github_workflow_sha: str
    """The commit SHA for the workflow file."""

    github_workspace: Path
    """The default working directory on the runner for steps, and the default location of your
    repository when using the checkout action. For example,
    /home/runner/work/my-repo-name/my-repo-name."""

    runner_arch: str
    """The architecture of the runner executing the job. Possible values are X86, X64, ARM,
    or ARM64."""

    runner_debug: str
    """This is set only if debug logging is enabled, and always has the value of 1. It can be useful
    as an indicator to enable additional debugging or verbose logging in your own job steps."""

    runner_name: str
    """The name of the runner executing the job. This name may not be unique in a workflow run as
    runners at the repository and organization levels could use the same name.
    For example, Hosted Agent"""

    runner_os: str
    """The operating system of the runner executing the job. Possible values are Linux, Windows,
    or macOS.
    For example, Windows"""

    runner_temp: Path
    """The path to a temporary directory on the runner. This directory is emptied at the beginning
    and end of each job. Note that files will not be removed if the runner's user account does
    not have permission to delete them. For example, D:\\a\\_temp"""

    runner_tool_cache: str
    """The path to the directory containing preinstalled tools for GitHub-hosted runners.
    For more information, see "Using GitHub-hosted runners".
    For example, C:\\hostedtoolcache\\windows"""

CI instance-attribute

Always set to true.

github_action instance-attribute

The name of the action currently running, or the id of a step. For example, for an action, __repo-owner_name-of-action-repo. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same script or action more than once in the same job, the name will include a suffix that consists of the sequence number preceded by an underscore. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.

github_action_path instance-attribute

The path where an action is located. This property is only supported in composite actions. You can use this path to change directories to where the action is located and access other files in that same repository. For example, /home/runner/work/_actions/repo-owner/name-of-action-repo/v1.

github_action_repository instance-attribute

For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout.

github_actions instance-attribute

Always set to true when GitHub Actions is running the workflow. You can use this variable to differentiate when tests are being run locally or by GitHub Actions.

github_actor instance-attribute

The name of the person or app that initiated the workflow. For example, octocat.

github_actor_id instance-attribute

The account ID of the person or app that triggered the initial workflow run. For example, 1234567. Note that this is different from the actor username.

github_api_url instance-attribute

Returns the API URL. For example: https://api.github.com.

github_base_ref instance-attribute

The name of the base ref or target branch of the pull request in a workflow run. This is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, main.

github_env instance-attribute

The path on the runner to the file that sets variables from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/set_env_87406d6e-4979-4d42-98e1-3dab1f48b13a. For more information, see "Workflow commands for GitHub Actions".

github_event_name instance-attribute

The name of the event that triggered the workflow. For example, workflow_dispatch.

github_event_path instance-attribute

The path to the file on the runner that contains the full event webhook payload. For example, /github/workflow/event.json.

github_graphql_url instance-attribute

Returns the GraphQL API URL. For example: https://api.github.com/graphql.

github_head_ref instance-attribute

The head ref or source branch of the pull request in a workflow run. This property is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, feature-branch-1.

github_job instance-attribute

The job_id of the current job. For example, greeting_job.

github_output instance-attribute

The path on the runner to the file that sets the current step's outputs from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/set_output_a50ef383-b063-46d9-9157-57953fc9f3f0. For more information, see "Workflow commands for GitHub Actions".

github_path instance-attribute

The path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/add_path_899b9445-ad4a-400c-aa89-249f18632cf5. For more information, see "Workflow commands for GitHub Actions".

github_ref instance-attribute

The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1.

github_ref_name instance-attribute

The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. For pull requests, the format is /merge.

github_ref_protected instance-attribute

true if branch protections or rulesets are configured for the ref that triggered the workflow run.

github_ref_type instance-attribute

The type of ref that triggered the workflow run. Valid values are branch or tag.

github_repository instance-attribute

The owner and repository name. For example, octocat/Hello-World.

github_repository_id instance-attribute

The ID of the repository. For example, 123456789. Note that this is different from the repository name.

github_repository_owner instance-attribute

The repository owner's name. For example, octocat.

github_repository_owner_id instance-attribute

The repository owner's account ID. For example, 1234567. Note that this is different from the owner's name.

github_retention_days instance-attribute

The number of days that workflow run logs and artifacts are kept. For example, 90.

github_run_attempt instance-attribute

A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. For example, 3.

github_run_id instance-attribute

A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. For example, 1658821493.

github_run_number instance-attribute

A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. For example, 3.

github_server_url instance-attribute

The URL of the GitHub server. For example: https://github.com.

github_sha instance-attribute

The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53.

github_step_summary instance-attribute

The path on the runner to the file that contains job summaries from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/_layout/_work/_temp/_runner_file_commands/step_summary_1cb22d7f-5663-41a8- 9ffc-13472605c76c. For more information, see "Workflow commands for GitHub Actions".

github_triggering_actor instance-attribute

The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges.

github_workflow instance-attribute

The name of the workflow. For example, My test workflow. If the workflow file doesn't specify a name, the value of this variable is the full path of the workflow file in the repository.

github_workflow_ref instance-attribute

The ref path to the workflow. For example, octocat/hello-world/.github/workflows/my-workflow.yml@ refs/heads/my_branch.

github_workflow_sha instance-attribute

The commit SHA for the workflow file.

github_workspace instance-attribute

The default working directory on the runner for steps, and the default location of your repository when using the checkout action. For example, /home/runner/work/my-repo-name/my-repo-name.

runner_arch instance-attribute

The architecture of the runner executing the job. Possible values are X86, X64, ARM, or ARM64.

runner_debug instance-attribute

This is set only if debug logging is enabled, and always has the value of 1. It can be useful as an indicator to enable additional debugging or verbose logging in your own job steps.

runner_name instance-attribute

The name of the runner executing the job. This name may not be unique in a workflow run as runners at the repository and organization levels could use the same name. For example, Hosted Agent

runner_os instance-attribute

The operating system of the runner executing the job. Possible values are Linux, Windows, or macOS. For example, Windows

runner_temp instance-attribute

The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner's user account does not have permission to delete them. For example, D:\a_temp

runner_tool_cache instance-attribute

The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see "Using GitHub-hosted runners". For example, C:\hostedtoolcache\windows

action_base

ActionBase

Base class for GitHub Actions.

You should implement main() method in the subclass.

You can define custom inputs and / or outputs types in the subclass. You can do nothing in the subclass if you don't need typed inputs and outputs.

Note these are just types, instances of these types are automatically created in the __init__ method.

Usage:

class MyInputs(ActionInputs):
    my_input: str
    '''My input description'''

    my_path: Path
    '''My path description'''

class MyOutputs(ActionOutputs):
    runner_os: str
    '''Runner OS description'''

class MyAction(ActionBase):
    inputs: MyInputs
    outputs: MyOutputs

    def main(self):
        if self.inputs.my_path is None:
            raise ValueError("my-path is required")
        self.inputs.my_path.mkdir(exist_ok=True)
        self.outputs.runner_os = self.env.runner_os
        self.summary += (
            self.render(
                "### {{ inputs.my_input }}.\n"
                "Have a nice day, {{ inputs['name'] }}!"
            )
        )

if __name__ == "__main__":
    MyAction().run()

Source code in src/github_custom_actions/action_base.py
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
class ActionBase:
    """Base class for GitHub Actions.

    You should implement `main()` method in the subclass.

    You can define custom inputs and / or outputs types in the subclass.
    You can do nothing in the subclass if you don't need typed inputs and outputs.

    Note these are just types, instances of these types are automatically created
    in the `__init__` method.

    Usage:
    ```python
    class MyInputs(ActionInputs):
        my_input: str
        '''My input description'''

        my_path: Path
        '''My path description'''

    class MyOutputs(ActionOutputs):
        runner_os: str
        '''Runner OS description'''

    class MyAction(ActionBase):
        inputs: MyInputs
        outputs: MyOutputs

        def main(self):
            if self.inputs.my_path is None:
                raise ValueError("my-path is required")
            self.inputs.my_path.mkdir(exist_ok=True)
            self.outputs.runner_os = self.env.runner_os
            self.summary += (
                self.render(
                    "### {{ inputs.my_input }}.\\n"
                    "Have a nice day, {{ inputs['name'] }}!"
                )
            )

    if __name__ == "__main__":
        MyAction().run()
    ```
    """

    inputs: ActionInputs
    outputs: ActionOutputs
    env: GithubVars

    def __init__(self) -> None:
        """Initialize inputs, outputs according to the type than could be set in subclass."""
        types = get_type_hints(self.__class__)
        self.inputs = types["inputs"]()
        self.outputs = types["outputs"]()
        self.env = GithubVars()

        base_dir = Path(__file__).resolve().parent
        templates_dir = base_dir / "templates"
        self.environment = Environment(  # noqa: S701
            loader=FileSystemLoader(str(templates_dir)),
        )

    summary = FileTextProperty("github_step_summary")

    def main(self) -> None:
        """Business logic of the action.

        Is called by `run()` method.
        """
        raise NotImplementedError

    def run(self) -> None:
        """Run the action.

        `run()` calls the `main()` method of the action with the necessary boilerplate to catch and
        report exceptions.

        Usage:
        ```python
        if __name__ == "__main__":
            MyAction().run()
        ```

        `main()` is where you implement the business logic of your action.
        """
        try:
            self.main()
        except Exception:  # noqa: BLE001
            traceback.print_exc(file=sys.stderr)
            sys.exit(1)

    @staticmethod
    def debug(message: str):
        """
        Emits a debug message. The runner needs to be invoked with enabled debug
        logging to show these.

        Example usage:

        ```python
        self.debug("Action invoked.")
        ```
        """
        print(f"::debug::{message}")

    @staticmethod
    def message(  # noqa: PLR0913
        severity: Literal["error", "notice", "warning"],
        message: str,
        title: Optional[str] = None,
        file: Optional[str] = None,
        line: Optional[int] = None,
        column: Optional[int] = None,
        end_line: Optional[int] = None,
        end_column: Optional[int] = None,
    ):
        """
        Emits a message at the given `severity` level. The keyword arguments can be used
        to generate annotations that will be displayed within the Review section of a
        Pull Request. Refer to the messages related section in the
        [workflows commands reference](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands)
        for semantic details.

        There are also the methods `error_message`, `notice_message` and
        `warning_message` as shortcuts.

        Example usages:

        ```python
        self.message("warning", "Deprecated input used: pattern")
        # or equivalently:
        self.warning_message("Deprecated input used: pattern")

        self.error_message(
            "Value exceeds limit.",
            title="Schema error",
            file="config.yml",
            line=7,
            column=42
        )
        ```

        """
        _locals = locals().copy()
        parameters = ",".join(
            f"{x}={value}"
            for x in ("title", "file", "line", "column", "end_line", "end_column")
            if (value := _locals[x]) is not None
        )
        print(f"::{severity}{(' ' + parameters) if parameters else ''}::{message}")

    error_message = partialmethod(message, "error")
    notice_message = partialmethod(message, "notice")
    warning_message = partialmethod(message, "warning")

    def render(self, template: str, **kwargs: Any) -> str:
        """Render the template from the string with Jinja.

        `kwargs` are the template context variables.

        Also includes to the context the action's `inputs`, `outputs`, and `env`.

        So you can use something like:
        ```python
        self.render("### {{ inputs.name }}!\\nHave a nice day!")
        ```

        """
        return Template(template.replace("\\n", "\n")).render(
            env=self.env,
            inputs=self.inputs,
            outputs=self.outputs,
            **kwargs,
        )

    def render_template(self, template_name: str, **kwargs: Any) -> str:
        """Render template from the `templates` directory.

        `template_name` is the name of the template file without the extension.
        `kwargs` are the template context variables.

        Also includes to the context the action's `inputs`, `outputs`, and `env`.

        Usage:
        ```python
        self.render_template("executor.json", image="ubuntu-latest")
        ```
        """
        template = self.environment.get_template(template_name)
        return template.render(
            env=self.env,
            inputs=self.inputs,
            outputs=self.outputs,
            **kwargs,
        )
__init__()

Initialize inputs, outputs according to the type than could be set in subclass.

Source code in src/github_custom_actions/action_base.py
88
89
90
91
92
93
94
95
96
97
98
99
def __init__(self) -> None:
    """Initialize inputs, outputs according to the type than could be set in subclass."""
    types = get_type_hints(self.__class__)
    self.inputs = types["inputs"]()
    self.outputs = types["outputs"]()
    self.env = GithubVars()

    base_dir = Path(__file__).resolve().parent
    templates_dir = base_dir / "templates"
    self.environment = Environment(  # noqa: S701
        loader=FileSystemLoader(str(templates_dir)),
    )
debug(message) staticmethod

Emits a debug message. The runner needs to be invoked with enabled debug logging to show these.

Example usage:

self.debug("Action invoked.")
Source code in src/github_custom_actions/action_base.py
130
131
132
133
134
135
136
137
138
139
140
141
142
@staticmethod
def debug(message: str):
    """
    Emits a debug message. The runner needs to be invoked with enabled debug
    logging to show these.

    Example usage:

    ```python
    self.debug("Action invoked.")
    ```
    """
    print(f"::debug::{message}")
main()

Business logic of the action.

Is called by run() method.

Source code in src/github_custom_actions/action_base.py
103
104
105
106
107
108
def main(self) -> None:
    """Business logic of the action.

    Is called by `run()` method.
    """
    raise NotImplementedError
message(severity, message, title=None, file=None, line=None, column=None, end_line=None, end_column=None) staticmethod

Emits a message at the given severity level. The keyword arguments can be used to generate annotations that will be displayed within the Review section of a Pull Request. Refer to the messages related section in the workflows commands reference for semantic details.

There are also the methods error_message, notice_message and warning_message as shortcuts.

Example usages:

self.message("warning", "Deprecated input used: pattern")
# or equivalently:
self.warning_message("Deprecated input used: pattern")

self.error_message(
    "Value exceeds limit.",
    title="Schema error",
    file="config.yml",
    line=7,
    column=42
)
Source code in src/github_custom_actions/action_base.py
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
@staticmethod
def message(  # noqa: PLR0913
    severity: Literal["error", "notice", "warning"],
    message: str,
    title: Optional[str] = None,
    file: Optional[str] = None,
    line: Optional[int] = None,
    column: Optional[int] = None,
    end_line: Optional[int] = None,
    end_column: Optional[int] = None,
):
    """
    Emits a message at the given `severity` level. The keyword arguments can be used
    to generate annotations that will be displayed within the Review section of a
    Pull Request. Refer to the messages related section in the
    [workflows commands reference](https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-commands)
    for semantic details.

    There are also the methods `error_message`, `notice_message` and
    `warning_message` as shortcuts.

    Example usages:

    ```python
    self.message("warning", "Deprecated input used: pattern")
    # or equivalently:
    self.warning_message("Deprecated input used: pattern")

    self.error_message(
        "Value exceeds limit.",
        title="Schema error",
        file="config.yml",
        line=7,
        column=42
    )
    ```

    """
    _locals = locals().copy()
    parameters = ",".join(
        f"{x}={value}"
        for x in ("title", "file", "line", "column", "end_line", "end_column")
        if (value := _locals[x]) is not None
    )
    print(f"::{severity}{(' ' + parameters) if parameters else ''}::{message}")
render(template, **kwargs)

Render the template from the string with Jinja.

kwargs are the template context variables.

Also includes to the context the action's inputs, outputs, and env.

So you can use something like:

self.render("### {{ inputs.name }}!\nHave a nice day!")

Source code in src/github_custom_actions/action_base.py
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
def render(self, template: str, **kwargs: Any) -> str:
    """Render the template from the string with Jinja.

    `kwargs` are the template context variables.

    Also includes to the context the action's `inputs`, `outputs`, and `env`.

    So you can use something like:
    ```python
    self.render("### {{ inputs.name }}!\\nHave a nice day!")
    ```

    """
    return Template(template.replace("\\n", "\n")).render(
        env=self.env,
        inputs=self.inputs,
        outputs=self.outputs,
        **kwargs,
    )
render_template(template_name, **kwargs)

Render template from the templates directory.

template_name is the name of the template file without the extension. kwargs are the template context variables.

Also includes to the context the action's inputs, outputs, and env.

Usage:

self.render_template("executor.json", image="ubuntu-latest")

Source code in src/github_custom_actions/action_base.py
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
def render_template(self, template_name: str, **kwargs: Any) -> str:
    """Render template from the `templates` directory.

    `template_name` is the name of the template file without the extension.
    `kwargs` are the template context variables.

    Also includes to the context the action's `inputs`, `outputs`, and `env`.

    Usage:
    ```python
    self.render_template("executor.json", image="ubuntu-latest")
    ```
    """
    template = self.environment.get_template(template_name)
    return template.render(
        env=self.env,
        inputs=self.inputs,
        outputs=self.outputs,
        **kwargs,
    )
run()

Run the action.

run() calls the main() method of the action with the necessary boilerplate to catch and report exceptions.

Usage:

if __name__ == "__main__":
    MyAction().run()

main() is where you implement the business logic of your action.

Source code in src/github_custom_actions/action_base.py
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
def run(self) -> None:
    """Run the action.

    `run()` calls the `main()` method of the action with the necessary boilerplate to catch and
    report exceptions.

    Usage:
    ```python
    if __name__ == "__main__":
        MyAction().run()
    ```

    `main()` is where you implement the business logic of your action.
    """
    try:
        self.main()
    except Exception:  # noqa: BLE001
        traceback.print_exc(file=sys.stderr)
        sys.exit(1)

FileTextProperty

Property descriptor read / write from a file.

Source code in src/github_custom_actions/action_base.py
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
class FileTextProperty:
    """Property descriptor read / write from a file."""

    def __init__(self, var_name: str) -> None:
        """Initialize the property descriptor.

        `var_name` is the name of the object's `vars` attribute with the path to the file.
        """
        self.var_name = var_name

    def __get__(self, obj: Any, objtype: Optional[Type[Any]] = None) -> str:
        path = getattr(obj.env, self.var_name)
        try:
            return path.read_text()  # type: ignore
        except FileNotFoundError:
            return ""

    def __set__(self, obj: Any, value: str) -> None:
        path = getattr(obj.env, self.var_name)
        try:
            path.write_text(value)
        except FileNotFoundError:
            path.parent.mkdir(parents=True, exist_ok=True)
            path.write_text(value)
__init__(var_name)

Initialize the property descriptor.

var_name is the name of the object's vars attribute with the path to the file.

Source code in src/github_custom_actions/action_base.py
16
17
18
19
20
21
def __init__(self, var_name: str) -> None:
    """Initialize the property descriptor.

    `var_name` is the name of the object's `vars` attribute with the path to the file.
    """
    self.var_name = var_name

attr_dict_vars

AttrDictVars

Common base class for accessing variables as attributes or dict.

Source code in src/github_custom_actions/attr_dict_vars.py
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
class AttrDictVars:
    """Common base class for accessing variables as attributes or dict."""

    _type_hints_cache: Dict[str, Dict[str, Type[Any]]] = {}

    @classmethod
    def get_type_hints(cls) -> Dict[str, Any]:
        class_name = cls.__name__
        if class_name not in cls._type_hints_cache:
            cls._type_hints_cache[class_name] = typing.get_type_hints(cls)
        return cls._type_hints_cache[class_name]

    def _attr_to_var_name(self, name: str) -> str:
        return name.replace("_", "-")

    def _external_name(self, name: str) -> str:
        return name

env_attr_dict_vars

EnvAttrDictVars

Bases: AttrDictVars

Dual access env vars.

Access to env vars as object attributes or as dict items. Do not allow changing vars, so this is a read-only source of env vars values.

With attributes, you can only access explicitly declared vars, with dict-like access you can access any var. This way you can find your balance between strictly defined vars and flexibility.

Usage
class MyVars(EnvAttrDictVars):
    documented_var: str

vars = MyVars(prefix="INPUT_")
print(vars["undocumented_var"])  # from os.environ["INPUT_UNDOCUMENTED_VAR"]
print(vars.documented_var)  # from os.environ["INPUT_DOCUMENTED-VAR"]

Attribute names are converted with the method _attr_to_var_name() - it converts Python attribute names from snake_case to kebab-case.

Source code in src/github_custom_actions/env_attr_dict_vars.py
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
class EnvAttrDictVars(AttrDictVars):
    """Dual access env vars.

    Access to env vars as object attributes or as dict items.
    Do not allow changing vars, so this is a read-only source of env vars values.

    With attributes, you can only access explicitly declared vars,
    with dict-like access you can access any var.
    This way you can find your balance between strictly defined vars and flexibility.

    Usage:
       ```python
       class MyVars(EnvAttrDictVars):
           documented_var: str

       vars = MyVars(prefix="INPUT_")
       print(vars["undocumented_var"])  # from os.environ["INPUT_UNDOCUMENTED_VAR"]
       print(vars.documented_var)  # from os.environ["INPUT_DOCUMENTED-VAR"]
       ```

    Attribute names are converted with the method `_attr_to_var_name()` -
    it converts Python attribute
    names from snake_case to kebab-case.
    """

    def __getattribute__(self, name: str) -> Any:
        try:
            return super().__getattribute__(name)
        except AttributeError as exc:
            type_hints = self.__class__.get_type_hints()
            if name not in type_hints:
                raise AttributeError(f"Unknown {name}") from exc
            env_var_name = self._external_name(self._attr_to_var_name(name))
            if env_var_name in os.environ:
                value: typing.Optional[typing.Union[str, Path]] = os.environ[env_var_name]

                # If the type hint is Path, convert the value to Path
                if type_hints[name] is Path:
                    value = Path(value) if value else None
                self.__dict__[name] = value
                return value
            raise AttributeError(
                f"`{name}` ({env_var_name}) not found in environment variables",
            ) from exc

    def __getitem__(self, key: str) -> Any:
        env_var_name = self._external_name(key)
        if env_var_name in os.environ:
            return os.environ[env_var_name]
        raise KeyError(f"`{key}` ({env_var_name}) not found in environment variables")

    def __setitem__(self, key: str, value: Any) -> None:
        raise NotImplementedError("Setting environment variables is not supported.")

    def __delitem__(self, key: str) -> None:
        raise NotImplementedError("Deleting environment variables is not supported.")

    def __iter__(self) -> typing.Iterator[str]:
        raise NotImplementedError("Iterating over environment variables is not supported.")

    def __len__(self) -> int:
        raise NotImplementedError("Getting the number of environment variables is not supported.")

    def __contains__(self, key: object) -> bool:
        env_var_name = self._external_name(self._attr_to_var_name(typing.cast(str, key)))
        exists = env_var_name in os.environ
        if not exists:
            print(f"`{key}` ({env_var_name}) not found in environment variables")
        return exists

file_attr_dict_vars

FileAttrDictVars

Bases: AttrDictVars, MutableMapping

Dual access vars in a file.

File contains vars as key=value lines. Access with attributes or as dict.

With attributes, you can only access explicitly declared vars, with dict-like access you can access any var. This way you can find your balance between strictly defined vars and flexibility.

Usage

class MyVars(FileAttrDictVars): documented_var: str

vars = MyVars(Path("my_vars.txt")) vars["undocumented_var"] = "value1" vars.documented_var == "value2"

Produces "my_vars.txt" with:
documented-var=value2
undocumented_var=value1

On read/write, it converts var names with _name_from_external()/_external_name() methods. They remove/add _external_name_prefix to the names.

Attribute access also uses _attr_to_var_name() - by default it converts Python attribute names from snake_case to kebab-case.

Source code in src/github_custom_actions/file_attr_dict_vars.py
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
class FileAttrDictVars(AttrDictVars, MutableMapping):  # type: ignore
    """Dual access vars in a file.

    File contains vars as `key=value` lines.
    Access with attributes or as dict.

    With attributes, you can only access explicitly declared vars,
    with dict-like access you can access any var.
    This way you can find your balance between strictly defined vars and flexibility.

    Usage:
       class MyVars(FileAttrDictVars):
           documented_var: str

       vars = MyVars(Path("my_vars.txt"))
       vars["undocumented_var"] = "value1"
       vars.documented_var == "value2"

       # Produces "my_vars.txt" with:
       #    documented-var=value2
       #    undocumented_var=value1


    On read/write, it converts var names with `_name_from_external()`/`_external_name()` methods.
    They remove/add `_external_name_prefix` to the names.

    Attribute access also uses `_attr_to_var_name()` - by default it converts Python attribute names
    from snake_case to kebab-case.
    """

    def __init__(self, vars_file: Path, *, prefix: str = "") -> None:
        """Init the vars file and prefix."""
        self._external_name_prefix = prefix
        self._vars_file: Path = vars_file
        self._var_keys_cache: Optional[Dict[str, Any]] = None

    def _external_name(self, name: str) -> str:
        """Convert variable name to the external form."""
        return self._external_name_prefix + name

    def _name_from_external(self, name: str) -> str:
        """Convert external variable name to the internal form."""
        return name[len(self._external_name_prefix) :]

    def __getattribute__(self, name: str) -> Any:
        try:
            return object.__getattribute__(self, name)
        except AttributeError as exc:
            type_hints = self.__class__.get_type_hints()
            if name in type_hints:
                var_name = self._attr_to_var_name(name)
                value = self[var_name]
                self.__dict__[var_name] = value
                return value
            raise AttributeError(f"Unknown {name}") from exc

    def __getitem__(self, key: str) -> Any:
        try:
            return self._get_var_keys[key]
        except KeyError:
            self._get_var_keys[key] = ""
            self._save_var_file()
            print(f"Variable `{key}` not found in `{self._vars_file}`")
            return ""

    def __setitem__(self, key: str, value: Any) -> None:
        """Access dict-style.

        vars["key"] = "value"
        """
        value_str = str(value)
        if "\n" in value_str or "\r" in value_str:
            raise ValueError(
                "GitHub outputs must be single-line strings; "
                f"value for '{key}' contains newline characters.",
            )
        self._get_var_keys[key] = value
        self._save_var_file()

    def __setattr__(self, name: str, value: Any) -> None:
        """Access attribute-style.

        vars.key = "value"
        """
        type_hints = self.__class__.get_type_hints()
        if not name.startswith("_"):
            if name not in type_hints:
                raise AttributeError(f"Unknown {name}")
            self[self._attr_to_var_name(name)] = value
        else:
            super().__setattr__(name, value)

    def __delitem__(self, key: str) -> None:
        del self._get_var_keys[key]
        self._save_var_file()

    def __iter__(self) -> Iterator[str]:
        return iter(self._get_var_keys)

    def __len__(self) -> int:
        return len(self._get_var_keys)

    def __contains__(self, key: object) -> bool:
        return key in self._get_var_keys

    @property
    def _get_var_keys(self) -> Dict[str, Any]:
        """Load key-value pairs from a file, returning {} if the file does not exist."""
        if self._var_keys_cache is None:
            try:
                content = self._vars_file.read_text(encoding="utf-8")
                self._var_keys_cache = {
                    self._name_from_external(k): v
                    for k, v in (line.split("=", 1) for line in content.splitlines() if "=" in line)
                }
            except FileNotFoundError:
                self._var_keys_cache = {}
        return self._var_keys_cache

    def _save_var_file(self) -> None:
        self._vars_file.parent.mkdir(parents=True, exist_ok=True)
        lines: List[str] = [
            f"{self._external_name(key)}={value}" for key, value in self._get_var_keys.items()
        ]
        self._vars_file.write_text("\n".join(lines), encoding="utf-8")
__init__(vars_file, *, prefix='')

Init the vars file and prefix.

Source code in src/github_custom_actions/file_attr_dict_vars.py
38
39
40
41
42
def __init__(self, vars_file: Path, *, prefix: str = "") -> None:
    """Init the vars file and prefix."""
    self._external_name_prefix = prefix
    self._vars_file: Path = vars_file
    self._var_keys_cache: Optional[Dict[str, Any]] = None
__setattr__(name, value)

Access attribute-style.

vars.key = "value"

Source code in src/github_custom_actions/file_attr_dict_vars.py
87
88
89
90
91
92
93
94
95
96
97
98
def __setattr__(self, name: str, value: Any) -> None:
    """Access attribute-style.

    vars.key = "value"
    """
    type_hints = self.__class__.get_type_hints()
    if not name.startswith("_"):
        if name not in type_hints:
            raise AttributeError(f"Unknown {name}")
        self[self._attr_to_var_name(name)] = value
    else:
        super().__setattr__(name, value)
__setitem__(key, value)

Access dict-style.

vars["key"] = "value"

Source code in src/github_custom_actions/file_attr_dict_vars.py
73
74
75
76
77
78
79
80
81
82
83
84
85
def __setitem__(self, key: str, value: Any) -> None:
    """Access dict-style.

    vars["key"] = "value"
    """
    value_str = str(value)
    if "\n" in value_str or "\r" in value_str:
        raise ValueError(
            "GitHub outputs must be single-line strings; "
            f"value for '{key}' contains newline characters.",
        )
    self._get_var_keys[key] = value
    self._save_var_file()

github_vars

GithubVars

Bases: EnvAttrDictVars

GitHub Action environment variables.

https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables

Usage
class MyAction:
    @property
    def env(self):
        return GithubVars()

action = MyAction()
print(action.env.github_repository)

Thanks to the docstrings your IDE will provide you with doc hints when you hover over the property. We do not load the attributes on the class init but do it Lazily. Once read, the value is stored in the instance dictionary and is not extracted from env anymore.

Converts attribute names to uppercase. Leave dict-style names unchanged.

Paths and files have type Path.

Source code in src/github_custom_actions/github_vars.py
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
class GithubVars(EnvAttrDictVars):
    """GitHub Action environment variables.

    https://docs.github.com/en/actions/learn-github-actions/variables#default-environment-variables

    Usage:
       ```python
       class MyAction:
           @property
           def env(self):
               return GithubVars()

       action = MyAction()
       print(action.env.github_repository)
       ```

    Thanks to the docstrings your IDE will provide you with doc hints when
    you hover over the property.
    We do not load the attributes on the class init but do it Lazily.
    Once read, the value is stored in the instance dictionary and is not extracted from env anymore.

    Converts attribute names to uppercase.
    Leave dict-style names unchanged.

    Paths and files have type Path.
    """

    # pylint: disable=abstract-method  # we want RO implementation that raises NotImplementedError on write

    def _attr_to_var_name(self, name: str) -> str:
        return name.upper()

    CI: str
    """Always set to true."""

    github_action: str
    """The name of the action currently running, or the id of a step.
    For example, for an action, __repo-owner_name-of-action-repo.
    GitHub removes special characters, and uses the name __run when the current step runs a script
    without an id. If you use the same script or action more than once in the same job, the name
    will include a suffix that consists of the sequence number preceded by an underscore. For
    example, the first script you run will have the name __run, and the second script will be
    named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2."""

    github_action_path: Path
    """The path where an action is located. This property is only supported in composite actions.
    You can use this path to change directories to where the action is located and access other
    files in that same repository. For example,
    /home/runner/work/_actions/repo-owner/name-of-action-repo/v1."""

    github_action_repository: str
    """For a step executing an action, this is the owner and repository name of the action.
    For example, actions/checkout."""

    github_actions: str
    """Always set to true when GitHub Actions is running the workflow. You can use this variable
    to differentiate when tests are being run locally or by GitHub Actions."""

    github_actor: str
    """The name of the person or app that initiated the workflow. For example, octocat."""

    github_actor_id: str
    """The account ID of the person or app that triggered the initial workflow run.
    For example, 1234567. Note that this is different from the actor username."""

    github_api_url: str
    """Returns the API URL. For example: https://api.github.com."""

    github_base_ref: str
    """The name of the base ref or target branch of the pull request in a workflow run.
    This is only set when the event that triggers a workflow run is either pull_request or
    pull_request_target. For example, main."""

    github_env: str
    """The path on the runner to the file that sets variables from workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/work/_temp/_runner_file_commands/set_env_87406d6e-4979-4d42-98e1-3dab1f48b13a.
    For more information, see "Workflow commands for GitHub Actions"."""

    github_event_name: str
    """The name of the event that triggered the workflow. For example, workflow_dispatch."""

    github_event_path: Path
    """The path to the file on the runner that contains the full event webhook payload.
    For example, /github/workflow/event.json."""

    github_graphql_url: str
    """Returns the GraphQL API URL. For example: https://api.github.com/graphql."""

    github_head_ref: str
    """The head ref or source branch of the pull request in a workflow run. This property is only
    set when the event that triggers a workflow run is either pull_request or pull_request_target.
    For example, feature-branch-1."""

    github_job: str
    """The job_id of the current job. For example, greeting_job."""

    github_output: Path
    """The path on the runner to the file that sets the current step's outputs from
    workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/work/_temp/_runner_file_commands/set_output_a50ef383-b063-46d9-9157-57953fc9f3f0.
    For more information, see "Workflow commands for GitHub Actions"."""

    github_path: Path
    """The path on the runner to the file that sets system PATH variables from workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/work/_temp/_runner_file_commands/add_path_899b9445-ad4a-400c-aa89-249f18632cf5.
    For more information, see "Workflow commands for GitHub Actions"."""

    github_ref: str
    """The fully-formed ref of the branch or tag that triggered the workflow run. For workflows
    triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by
    pull_request, this is the pull request merge branch. For workflows triggered by release,
    this is the release tag created. For other triggers, this is the branch or tag ref that
    triggered the workflow run. This is only set if a branch or tag is available for the
    event type. The ref given is fully-formed, meaning that for branches the format is
    refs/heads/<branch_name>, for pull requests it is refs/pull/<pr_number>/merge,
    and for tags it is refs/tags/<tag_name>. For example, refs/heads/feature-branch-1."""

    github_ref_name: str
    """The short ref name of the branch or tag that triggered the workflow run. This value matches
    the branch or tag name shown on GitHub. For example, feature-branch-1.
    For pull requests, the format is <pr_number>/merge."""

    github_ref_protected: str
    """true if branch protections or rulesets are configured for the ref that triggered the
    workflow run."""

    github_ref_type: str
    """The type of ref that triggered the workflow run. Valid values are branch or tag."""

    github_repository: str
    """The owner and repository name. For example, octocat/Hello-World."""

    github_repository_id: str
    """The ID of the repository. For example, 123456789. Note that this is different from the
    repository name."""

    github_repository_owner: str
    """The repository owner's name. For example, octocat."""

    github_repository_owner_id: str
    """The repository owner's account ID. For example, 1234567. Note that this is different
    from the owner's name."""

    github_retention_days: str
    """The number of days that workflow run logs and artifacts are kept. For example, 90."""

    github_run_attempt: str
    """A unique number for each attempt of a particular workflow run in a repository. This number
    begins at 1 for the workflow run's first attempt, and increments with each re-run.
    For example, 3."""

    github_run_id: str
    """A unique number for each workflow run within a repository. This number does not change if
    you re-run the workflow run. For example, 1658821493."""

    github_run_number: str
    """A unique number for each run of a particular workflow in a repository. This number begins
    at 1 for the workflow's first run, and increments with each new run. This number does not
    change if you re-run the workflow run. For example, 3."""

    github_server_url: str
    """The URL of the GitHub server. For example: https://github.com."""

    github_sha: str
    """The commit SHA that triggered the workflow. The value of this commit SHA depends on the
    event that triggered the workflow. For more information, see "Events that trigger workflows."
    For example, ffac537e6cbbf934b08745a378932722df287a53."""

    github_step_summary: Path
    """The path on the runner to the file that contains job summaries from workflow commands.
    This file is unique to the current step and changes for each step in a job.
    For example,
    /home/runner/_layout/_work/_temp/_runner_file_commands/step_summary_1cb22d7f-5663-41a8-
    9ffc-13472605c76c. For more information, see "Workflow commands for GitHub Actions"."""

    github_triggering_actor: str
    """The username of the user that initiated the workflow run. If the workflow run is a re-run,
    this value may differ from github.actor. Any workflow re-runs will use the privileges of
    github.actor, even if the actor initiating the re-run (github.triggering_actor) has different
    privileges."""

    github_workflow: str
    """The name of the workflow. For example, My test workflow. If the workflow file doesn't specify
    a name, the value of this variable is the full path of the workflow file in the repository."""

    github_workflow_ref: str
    """The ref path to the workflow. For example,
    octocat/hello-world/.github/workflows/my-workflow.yml@
    refs/heads/my_branch."""

    github_workflow_sha: str
    """The commit SHA for the workflow file."""

    github_workspace: Path
    """The default working directory on the runner for steps, and the default location of your
    repository when using the checkout action. For example,
    /home/runner/work/my-repo-name/my-repo-name."""

    runner_arch: str
    """The architecture of the runner executing the job. Possible values are X86, X64, ARM,
    or ARM64."""

    runner_debug: str
    """This is set only if debug logging is enabled, and always has the value of 1. It can be useful
    as an indicator to enable additional debugging or verbose logging in your own job steps."""

    runner_name: str
    """The name of the runner executing the job. This name may not be unique in a workflow run as
    runners at the repository and organization levels could use the same name.
    For example, Hosted Agent"""

    runner_os: str
    """The operating system of the runner executing the job. Possible values are Linux, Windows,
    or macOS.
    For example, Windows"""

    runner_temp: Path
    """The path to a temporary directory on the runner. This directory is emptied at the beginning
    and end of each job. Note that files will not be removed if the runner's user account does
    not have permission to delete them. For example, D:\\a\\_temp"""

    runner_tool_cache: str
    """The path to the directory containing preinstalled tools for GitHub-hosted runners.
    For more information, see "Using GitHub-hosted runners".
    For example, C:\\hostedtoolcache\\windows"""
CI instance-attribute

Always set to true.

github_action instance-attribute

The name of the action currently running, or the id of a step. For example, for an action, __repo-owner_name-of-action-repo. GitHub removes special characters, and uses the name __run when the current step runs a script without an id. If you use the same script or action more than once in the same job, the name will include a suffix that consists of the sequence number preceded by an underscore. For example, the first script you run will have the name __run, and the second script will be named __run_2. Similarly, the second invocation of actions/checkout will be actionscheckout2.

github_action_path instance-attribute

The path where an action is located. This property is only supported in composite actions. You can use this path to change directories to where the action is located and access other files in that same repository. For example, /home/runner/work/_actions/repo-owner/name-of-action-repo/v1.

github_action_repository instance-attribute

For a step executing an action, this is the owner and repository name of the action. For example, actions/checkout.

github_actions instance-attribute

Always set to true when GitHub Actions is running the workflow. You can use this variable to differentiate when tests are being run locally or by GitHub Actions.

github_actor instance-attribute

The name of the person or app that initiated the workflow. For example, octocat.

github_actor_id instance-attribute

The account ID of the person or app that triggered the initial workflow run. For example, 1234567. Note that this is different from the actor username.

github_api_url instance-attribute

Returns the API URL. For example: https://api.github.com.

github_base_ref instance-attribute

The name of the base ref or target branch of the pull request in a workflow run. This is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, main.

github_env instance-attribute

The path on the runner to the file that sets variables from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/set_env_87406d6e-4979-4d42-98e1-3dab1f48b13a. For more information, see "Workflow commands for GitHub Actions".

github_event_name instance-attribute

The name of the event that triggered the workflow. For example, workflow_dispatch.

github_event_path instance-attribute

The path to the file on the runner that contains the full event webhook payload. For example, /github/workflow/event.json.

github_graphql_url instance-attribute

Returns the GraphQL API URL. For example: https://api.github.com/graphql.

github_head_ref instance-attribute

The head ref or source branch of the pull request in a workflow run. This property is only set when the event that triggers a workflow run is either pull_request or pull_request_target. For example, feature-branch-1.

github_job instance-attribute

The job_id of the current job. For example, greeting_job.

github_output instance-attribute

The path on the runner to the file that sets the current step's outputs from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/set_output_a50ef383-b063-46d9-9157-57953fc9f3f0. For more information, see "Workflow commands for GitHub Actions".

github_path instance-attribute

The path on the runner to the file that sets system PATH variables from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/work/_temp/_runner_file_commands/add_path_899b9445-ad4a-400c-aa89-249f18632cf5. For more information, see "Workflow commands for GitHub Actions".

github_ref instance-attribute

The fully-formed ref of the branch or tag that triggered the workflow run. For workflows triggered by push, this is the branch or tag ref that was pushed. For workflows triggered by pull_request, this is the pull request merge branch. For workflows triggered by release, this is the release tag created. For other triggers, this is the branch or tag ref that triggered the workflow run. This is only set if a branch or tag is available for the event type. The ref given is fully-formed, meaning that for branches the format is refs/heads/, for pull requests it is refs/pull//merge, and for tags it is refs/tags/. For example, refs/heads/feature-branch-1.

github_ref_name instance-attribute

The short ref name of the branch or tag that triggered the workflow run. This value matches the branch or tag name shown on GitHub. For example, feature-branch-1. For pull requests, the format is /merge.

github_ref_protected instance-attribute

true if branch protections or rulesets are configured for the ref that triggered the workflow run.

github_ref_type instance-attribute

The type of ref that triggered the workflow run. Valid values are branch or tag.

github_repository instance-attribute

The owner and repository name. For example, octocat/Hello-World.

github_repository_id instance-attribute

The ID of the repository. For example, 123456789. Note that this is different from the repository name.

github_repository_owner instance-attribute

The repository owner's name. For example, octocat.

github_repository_owner_id instance-attribute

The repository owner's account ID. For example, 1234567. Note that this is different from the owner's name.

github_retention_days instance-attribute

The number of days that workflow run logs and artifacts are kept. For example, 90.

github_run_attempt instance-attribute

A unique number for each attempt of a particular workflow run in a repository. This number begins at 1 for the workflow run's first attempt, and increments with each re-run. For example, 3.

github_run_id instance-attribute

A unique number for each workflow run within a repository. This number does not change if you re-run the workflow run. For example, 1658821493.

github_run_number instance-attribute

A unique number for each run of a particular workflow in a repository. This number begins at 1 for the workflow's first run, and increments with each new run. This number does not change if you re-run the workflow run. For example, 3.

github_server_url instance-attribute

The URL of the GitHub server. For example: https://github.com.

github_sha instance-attribute

The commit SHA that triggered the workflow. The value of this commit SHA depends on the event that triggered the workflow. For more information, see "Events that trigger workflows." For example, ffac537e6cbbf934b08745a378932722df287a53.

github_step_summary instance-attribute

The path on the runner to the file that contains job summaries from workflow commands. This file is unique to the current step and changes for each step in a job. For example, /home/runner/_layout/_work/_temp/_runner_file_commands/step_summary_1cb22d7f-5663-41a8- 9ffc-13472605c76c. For more information, see "Workflow commands for GitHub Actions".

github_triggering_actor instance-attribute

The username of the user that initiated the workflow run. If the workflow run is a re-run, this value may differ from github.actor. Any workflow re-runs will use the privileges of github.actor, even if the actor initiating the re-run (github.triggering_actor) has different privileges.

github_workflow instance-attribute

The name of the workflow. For example, My test workflow. If the workflow file doesn't specify a name, the value of this variable is the full path of the workflow file in the repository.

github_workflow_ref instance-attribute

The ref path to the workflow. For example, octocat/hello-world/.github/workflows/my-workflow.yml@ refs/heads/my_branch.

github_workflow_sha instance-attribute

The commit SHA for the workflow file.

github_workspace instance-attribute

The default working directory on the runner for steps, and the default location of your repository when using the checkout action. For example, /home/runner/work/my-repo-name/my-repo-name.

runner_arch instance-attribute

The architecture of the runner executing the job. Possible values are X86, X64, ARM, or ARM64.

runner_debug instance-attribute

This is set only if debug logging is enabled, and always has the value of 1. It can be useful as an indicator to enable additional debugging or verbose logging in your own job steps.

runner_name instance-attribute

The name of the runner executing the job. This name may not be unique in a workflow run as runners at the repository and organization levels could use the same name. For example, Hosted Agent

runner_os instance-attribute

The operating system of the runner executing the job. Possible values are Linux, Windows, or macOS. For example, Windows

runner_temp instance-attribute

The path to a temporary directory on the runner. This directory is emptied at the beginning and end of each job. Note that files will not be removed if the runner's user account does not have permission to delete them. For example, D:\a_temp

runner_tool_cache instance-attribute

The path to the directory containing preinstalled tools for GitHub-hosted runners. For more information, see "Using GitHub-hosted runners". For example, C:\hostedtoolcache\windows

inputs_outputs

Github Actions helper functions.

We want to support Python 3.7 that you still have on some self-hosted action runners. So no fancy features like walrus operator, @cached_property, etc.

ActionInputs

Bases: EnvAttrDictVars

GitHub Action input variables.

Usage
class MyInputs(ActionInputs):
    my_input: str

action = ActionBase(inputs=MyInputs())
print(action.inputs.my_input)
print(action.inputs["my-input"])  # the same as above

With attributes, you can only access explicitly declared vars, with dict-like access you can access any var. This way you can find your balance between strictly defined vars and flexibility.

Attribute names are converted to kebab-case. So action.inputs.my_input is the same as action.inputs["my-input"].

If you need to access a snake_case named input my_input, you should use dict-style only: action.inputs["my_input"]. But it's common to use kebab-case in GitHub Actions input names.

By GitHub convention, all input names are upper-cased in the environment and prefixed with "INPUT_". So actions.inputs.my_input or actions.inputs['my-input'] will be the variable INPUT_MY-INPUT in the environment. The ActionInputs does the conversion automatically.

Uses lazy loading of the values. So the value is read from the environment only when accessed and only once, and saved in the object's internal dict.

Source code in src/github_custom_actions/inputs_outputs.py
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
class ActionInputs(EnvAttrDictVars):
    """GitHub Action input variables.

    Usage:
        ```python
        class MyInputs(ActionInputs):
            my_input: str

        action = ActionBase(inputs=MyInputs())
        print(action.inputs.my_input)
        print(action.inputs["my-input"])  # the same as above
        ```

    With attributes, you can only access explicitly declared vars, with dict-like access
    you can access any var.
    This way you can find your balance between strictly defined vars and flexibility.

    Attribute names are converted to `kebab-case`.
    So `action.inputs.my_input` is the same as `action.inputs["my-input"]`.

    If you need to access a `snake_case` named input `my_input`, you should
    use dict-style only: `action.inputs["my_input"]`.
    But it's common to use `kebab-case` in GitHub Actions input names.

    By GitHub convention, all input names are upper-cased in the environment
    and prefixed with "INPUT_".
    So `actions.inputs.my_input` or `actions.inputs['my-input']` will be the variable
    `INPUT_MY-INPUT` in the environment.
    The ActionInputs does the conversion automatically.

    Uses lazy loading of the values.
    So the value is read from the environment only when accessed and only once,
    and saved in the object's internal dict."""

    # pylint: disable=abstract-method  # we want RO implementation that raises NotImplementedError on write

    def _external_name(self, name: str) -> str:
        """Convert variable name to the external form."""
        return INPUT_PREFIX + name.upper()

ActionOutputs

Bases: FileAttrDictVars

GitHub Actions output variables.

Usage
class MyOutputs(ActionOutputs):
    my_output: str

action = ActionBase(outputs=MyOutputs())
action.outputs["my-output"] = "value"
action.outputs.my_output = "value"  # the same as above

With attributes, you can only access explicitly declared vars, with dict-like access you can access any var. This way you can find your balance between strictly defined vars and flexibility.

Attribute names are converted to kebab-case. So action.outputs.my_output is the same as action.outputs["my-output"].

If you need to access a snake_case named output like my_output you should use dict-style only: action.outputs["my_output"]. But it's common to use kebab-case in GitHub Actions output names.

Each output var assignment changes the GitHub outputs file (the path is defined as action.env.github_output).

Source code in src/github_custom_actions/inputs_outputs.py
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
class ActionOutputs(FileAttrDictVars):
    """GitHub Actions output variables.

    Usage:
       ```python
       class MyOutputs(ActionOutputs):
           my_output: str

       action = ActionBase(outputs=MyOutputs())
       action.outputs["my-output"] = "value"
       action.outputs.my_output = "value"  # the same as above
       ```

    With attributes, you can only access explicitly declared vars,
    with dict-like access you can access any var.
    This way you can find your balance between strictly defined vars and flexibility.

    Attribute names are converted to `kebab-case`.
    So `action.outputs.my_output` is the same as `action.outputs["my-output"]`.

    If you need to access a `snake_case` named output like `my_output` you should
    use dict-style only: `action.outputs["my_output"]`.
    But it's common to use `kebab-case` in GitHub Actions output names.

    Each output var assignment changes the GitHub outputs file
    (the path is defined as `action.env.github_output`).
    """

    def __init__(self) -> None:
        super().__init__(Path(os.environ["GITHUB_OUTPUT"]))

github_custom_actions.ActionBase.error_message = partialmethod(message, 'error') class-attribute instance-attribute

github_custom_actions.ActionBase.notice_message = partialmethod(message, 'notice') class-attribute instance-attribute

github_custom_actions.ActionBase.warning_message = partialmethod(message, 'warning') class-attribute instance-attribute