An asynchronous GitHub API library designed as a sans-I/O library for GitHub API access
—
Utilities for GitHub Actions workflows including environment variable management, path manipulation, and logging command output. These functions help create GitHub Actions that integrate with the workflow environment.
Access the GitHub Actions workspace directory.
import pathlib
def workspace() -> pathlib.Path:
"""
Return the action workspace as a pathlib.Path object.
Returns:
- Path object pointing to the workspace directory
Note: Uses the GITHUB_WORKSPACE environment variable.
This function is cached with @functools.lru_cache(maxsize=1).
"""Access the webhook event that triggered the workflow.
def event() -> Any:
"""
Return the webhook event data for the running action.
Returns:
- Parsed JSON data from the event that triggered the workflow
Note: Uses the GITHUB_EVENT_PATH environment variable.
This function is cached with @functools.lru_cache(maxsize=1).
"""Issue logging commands that are processed by the GitHub Actions runner.
def command(cmd: str, data: str = "", **parameters: str) -> None:
"""
Issue a logging command that will be processed by the Actions runner.
Parameters:
- cmd: Command name (e.g., "error", "warning", "notice", "debug")
- data: Command data/message
- **parameters: Command parameters (e.g., file, line, col, title)
Examples:
- command("error", "Something went wrong", file="app.py", line="42")
- command("warning", "Deprecated function used")
- command("notice", "Build completed successfully")
"""Manage environment variables for the current and future actions.
def setenv(name: str, value: str) -> None:
"""
Create or update an environment variable.
The change applies to this action and future actions in the job.
Parameters:
- name: Environment variable name
- value: Environment variable value (can be multiline)
Note: Uses the GITHUB_ENV environment file for persistence.
"""
def addpath(path: Union[str, "os.PathLike[str]"]) -> None:
"""
Prepend a directory to the PATH environment variable.
This affects this action and all subsequent actions in the current job.
Parameters:
- path: Directory path to add to PATH
Note: Uses the GITHUB_PATH environment file for persistence.
"""#!/usr/bin/env python3
import gidgethub.actions
import subprocess
import sys
def main():
# Get workspace directory
workspace = gidgethub.actions.workspace()
print(f"Working in: {workspace}")
# Get event data
event_data = gidgethub.actions.event()
event_name = event_data.get('action', 'unknown')
gidgethub.actions.command("notice", f"Processing {event_name} event")
# Example: Run tests
try:
result = subprocess.run(
["python", "-m", "pytest"],
cwd=workspace,
capture_output=True,
text=True
)
if result.returncode == 0:
gidgethub.actions.command("notice", "Tests passed successfully")
else:
gidgethub.actions.command("error", f"Tests failed: {result.stderr}")
sys.exit(1)
except Exception as e:
gidgethub.actions.command("error", f"Failed to run tests: {e}")
sys.exit(1)
if __name__ == "__main__":
main()import gidgethub.actions
import os
def setup_build_environment():
# Set build configuration
gidgethub.actions.setenv("BUILD_TYPE", "release")
gidgethub.actions.setenv("OPTIMIZATION_LEVEL", "3")
# Set multiline environment variable
config_json = """{
"api_url": "https://api.example.com",
"timeout": 30,
"retries": 3
}"""
gidgethub.actions.setenv("BUILD_CONFIG", config_json)
# Add custom tools to PATH
tools_dir = gidgethub.actions.workspace() / "tools" / "bin"
gidgethub.actions.addpath(tools_dir)
# Verify environment
print(f"BUILD_TYPE: {os.environ.get('BUILD_TYPE')}")
print(f"PATH includes tools: {str(tools_dir) in os.environ.get('PATH', '')}")
setup_build_environment()import gidgethub.actions
import ast
import sys
def lint_python_file(file_path):
"""Lint a Python file and report errors with locations."""
try:
with open(file_path, 'r') as f:
source = f.read()
# Parse the file
ast.parse(source)
gidgethub.actions.command("notice", f"✓ {file_path} is valid Python")
except SyntaxError as e:
# Report syntax error with file location
gidgethub.actions.command(
"error",
f"Syntax error: {e.msg}",
file=str(file_path),
line=str(e.lineno),
col=str(e.offset) if e.offset else "1"
)
return False
except Exception as e:
gidgethub.actions.command("error", f"Failed to process {file_path}: {e}")
return False
return True
def main():
workspace = gidgethub.actions.workspace()
python_files = list(workspace.glob("**/*.py"))
gidgethub.actions.command("notice", f"Linting {len(python_files)} Python files")
all_valid = True
for py_file in python_files:
if not lint_python_file(py_file):
all_valid = False
if not all_valid:
gidgethub.actions.command("error", "Linting failed")
sys.exit(1)
else:
gidgethub.actions.command("notice", "All files passed linting")
if __name__ == "__main__":
main()import gidgethub.actions
def process_pull_request_event():
event = gidgethub.actions.event()
if event.get('action') == 'opened':
pr = event['pull_request']
gidgethub.actions.command(
"notice",
f"New PR opened: {pr['title']}",
title="Pull Request Opened"
)
# Check if PR affects specific files
changed_files = [] # You'd get this from the GitHub API
if any(f.endswith('.py') for f in changed_files):
gidgethub.actions.setenv("RUN_PYTHON_TESTS", "true")
if any(f.endswith(('.js', '.ts')) for f in changed_files):
gidgethub.actions.setenv("RUN_JS_TESTS", "true")
elif event.get('action') == 'closed':
pr = event['pull_request']
if pr['merged']:
gidgethub.actions.command("notice", f"PR merged: {pr['title']}")
else:
gidgethub.actions.command("notice", f"PR closed: {pr['title']}")
process_pull_request_event()import gidgethub.actions
import os
import json
def parse_inputs():
"""Parse action inputs from environment variables."""
return {
'target': os.environ.get('INPUT_TARGET', 'main'),
'dry_run': os.environ.get('INPUT_DRY_RUN', 'false').lower() == 'true',
'config_file': os.environ.get('INPUT_CONFIG_FILE', 'config.json')
}
def set_outputs(**outputs):
"""Set action outputs."""
for key, value in outputs.items():
# GitHub Actions uses special echo commands for outputs
print(f"::set-output name={key}::{value}")
def main():
inputs = parse_inputs()
gidgethub.actions.command(
"notice",
f"Running with target: {inputs['target']}, dry_run: {inputs['dry_run']}"
)
workspace = gidgethub.actions.workspace()
config_path = workspace / inputs['config_file']
if not config_path.exists():
gidgethub.actions.command(
"error",
f"Config file not found: {config_path}",
file=str(config_path)
)
return 1
# Process configuration
try:
with open(config_path) as f:
config = json.load(f)
# Set outputs for other actions to use
set_outputs(
config_valid="true",
target_branch=inputs['target'],
config_version=config.get('version', 'unknown')
)
gidgethub.actions.command("notice", "Action completed successfully")
return 0
except Exception as e:
gidgethub.actions.command("error", f"Failed to process config: {e}")
return 1
if __name__ == "__main__":
exit_code = main()
exit(exit_code)import asyncio
import aiohttp
import gidgethub.actions
from gidgethub.aiohttp import GitHubAPI
async def comment_on_pr():
"""Add a comment to the PR that triggered this action."""
event = gidgethub.actions.event()
if event.get('action') != 'opened':
gidgethub.actions.command("notice", "Not a PR opened event, skipping")
return
# Get GitHub token from environment
github_token = os.environ.get('GITHUB_TOKEN')
if not github_token:
gidgethub.actions.command("error", "GITHUB_TOKEN not set")
return
repository = event['repository']['full_name']
pr_number = event['pull_request']['number']
async with aiohttp.ClientSession() as session:
gh = GitHubAPI(session, "pr-commenter/1.0", oauth_token=github_token)
try:
# Add comment to PR
await gh.post(
f"/repos/{repository}/issues/{pr_number}/comments",
data={
"body": "🎉 Thanks for opening this PR! Our automated checks are running."
}
)
gidgethub.actions.command("notice", f"Added comment to PR #{pr_number}")
except Exception as e:
gidgethub.actions.command("error", f"Failed to add comment: {e}")
# Run the async function
asyncio.run(comment_on_pr())Common environment variables available in GitHub Actions:
GITHUB_WORKSPACE: The workspace directory pathGITHUB_EVENT_PATH: Path to the webhook event JSON fileGITHUB_TOKEN: GitHub token for API accessGITHUB_REPOSITORY: Repository name (owner/name)GITHUB_REF: Git ref that triggered the workflowGITHUB_SHA: Commit SHA that triggered the workflowGITHUB_ACTOR: Username of the user that triggered the workflowGITHUB_WORKFLOW: Name of the workflowGITHUB_RUN_ID: Unique identifier for the workflow runGITHUB_RUN_NUMBER: Sequential number for the workflow runCommon workflow commands you can use with the command() function:
error: Create an error messagewarning: Create a warning messagenotice: Create a notice messagedebug: Create a debug messagegroup: Start a collapsible groupendgroup: End a collapsible groupsave-state: Save state for post-actionset-output: Set action output (deprecated, use environment files)import pathlib
import os
from typing import Any, Union
# Path types for workspace and file operations
PathLike = Union[str, "os.PathLike[str]"]
WorkspacePath = pathlib.Path
# Event data type (parsed JSON)
EventData = Any # Dictionary containing webhook event dataInstall with Tessl CLI
npx tessl i tessl/pypi-gidgethub