Skip to content

The ActionBase base class also exposes helpers to emit the standard GitHub workflow log commands. Use debug(message: str) when you want to show extra information only when a workflow runs with debug logging enabled. For annotations that should show up in the PR “Files changed” view, call ActionBase.message() (or its convenience aliases error_message, notice_message, and warning_message) so you can attach file, line, and column information:

class MyAction(ActionBase):
    def main(self):
        self.debug("Finished parsing configuration")
        self.error_message(
            message="Unknown key 'service_port'",
            title="Invalid config",
            file="config.yml",
            line=14,
            column=1,
        )

Those helpers format the underlying ::<severity>:: workflow command for you; refer to the links above for the full list of keyword arguments if you need to build more complex annotations.

github_custom_actions.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)