Comparison with Javascript @actions/toolkit¶
This guide helps you compare GitHub Action Toolkit in Python compares JavaScript/TypeScript using @actions/toolkit with Python using github-action-toolkit.
Package Mapping¶
Javascript Package |
Python Equivalent |
Notes |
|---|---|---|
|
|
Core functionality |
|
|
GitHub API access |
|
Python |
Command execution |
|
Python |
File operations |
|
|
Caching |
|
|
Artifacts |
Function Mapping¶
Core Functions (@actions/core)¶
Inputs and Outputs¶
// JavaScript
const core = require('@actions/core');
const name = core.getInput('name', { required: true });
const timeout = parseInt(core.getInput('timeout')) || 30;
core.setOutput('result', 'success');
# Python
from github_action_toolkit import get_user_input, get_user_input_as, set_output
name = get_user_input('name') # Returns None if not set
if not name:
raise ValueError("name is required")
timeout = get_user_input_as('timeout', int, default_value=30)
set_output('result', 'success')
Logging¶
// JavaScript
core.info('Information message');
core.debug('Debug message');
core.warning('Warning message');
core.error('Error message');
core.notice('Notice message');
# Python
from github_action_toolkit import info, debug, warning, error, notice
info('Information message')
debug('Debug message')
warning('Warning message')
error('Error message')
notice('Notice message')
Groups¶
// JavaScript
core.startGroup('My group');
console.log('Inside group');
core.endGroup();
// Or with async function
await core.group('My group', async () => {
console.log('Inside group');
});
# Python
from github_action_toolkit import start_group, end_group, group
# Manual start/end
start_group('My group')
print('Inside group')
end_group()
# Or with context manager
with group('My group'):
print('Inside group')
Environment Variables¶
// JavaScript
core.exportVariable('MY_VAR', 'value');
const value = process.env.MY_VAR;
# Python
from github_action_toolkit import export_variable, get_env
export_variable('MY_VAR', 'value')
value = get_env('MY_VAR')
Secrets¶
// JavaScript
core.setSecret('my-password');
# Python
from github_action_toolkit import add_mask
add_mask('my-password')
PATH¶
// JavaScript
core.addPath('/usr/local/bin');
# Python
from github_action_toolkit import add_path
add_path('/usr/local/bin')
State¶
// JavaScript
core.saveState('my-state', 'value');
const state = core.getState('my-state');
# Python
from github_action_toolkit import save_state, get_state
save_state('my-state', 'value')
state = get_state('my-state')
GitHub API (@actions/github)¶
// JavaScript
const github = require('@actions/github');
const octokit = github.getOctokit(process.env.GITHUB_TOKEN);
const { data: repo } = await octokit.rest.repos.get({
owner: 'owner',
repo: 'repo'
});
# Python
from github_action_toolkit import GitHubAPIClient
client = GitHubAPIClient()
repo = client.get_repository('owner/repo')
Job Summaries¶
// JavaScript
const core = require('@actions/core');
await core.summary
.addHeading('Test Results')
.addTable([
[{data: 'Test', header: true}, {data: 'Result', header: true}],
['test1', '✓ Pass'],
['test2', '✓ Pass']
])
.write();
# Python
from github_action_toolkit import JobSummary
summary = JobSummary()
summary.add_heading('Test Results', 1)
summary.add_table([
['Test', 'Result'],
['test1', '✓ Pass'],
['test2', '✓ Pass']
])
summary.write()
Workflow File Changes¶
Minimal Changes¶
You can keep most of your workflow structure the same:
# Before (JavaScript)
name: My Action
runs:
using: 'node20'
main: 'dist/index.js'
# After (Python)
name: My Action
runs:
using: 'composite'
steps:
- name: Run action
run: python ${{ github.action_path }}/action.py
shell: bash
Full Comparison¶
JavaScript Action:
name: 'JavaScript Action'
description: 'My action in JavaScript'
inputs:
name:
description: 'Name to greet'
required: true
outputs:
greeting:
description: 'The greeting'
runs:
using: 'node20'
main: 'dist/index.js'
Python Action (Composite):
name: 'Python Action'
description: 'My action in Python'
inputs:
name:
description: 'Name to greet'
required: true
outputs:
greeting:
description: 'The greeting'
runs:
using: 'composite'
steps:
- name: Set up Python
uses: actions/setup-python@v5
with:
python-version: '3.11'
- name: Install dependencies
run: pip install github-action-toolkit
shell: bash
- name: Run action
run: python ${{ github.action_path }}/action.py
shell: bash
Python Action (Docker):
name: 'Python Action'
description: 'My action in Python'
inputs:
name:
description: 'Name to greet'
required: true
outputs:
greeting:
description: 'The greeting'
runs:
using: 'docker'
image: 'Dockerfile'
Error Handling¶
JavaScript¶
// JavaScript
try {
const result = doSomething();
core.setOutput('result', result);
} catch (error) {
core.setFailed(error.message);
}
Python¶
# Python
from github_action_toolkit import error, set_output
try:
result = do_something()
set_output('result', result)
except Exception as e:
error(str(e), title="Action Failed")
raise SystemExit(1)
Advanced Patterns¶
Async Operations¶
JavaScript’s async/await has Python equivalents:
// JavaScript
const results = await Promise.all([
fetchData1(),
fetchData2(),
fetchData3()
]);
# Python with asyncio
import asyncio
results = await asyncio.gather(
fetch_data1(),
fetch_data2(),
fetch_data3()
)
# Or with threading for I/O
from concurrent.futures import ThreadPoolExecutor
with ThreadPoolExecutor() as executor:
results = list(executor.map(fetch_data, [1, 2, 3]))
Cancellation Handling¶
// JavaScript
process.on('SIGTERM', () => {
console.log('Cleaning up...');
cleanup();
process.exit(0);
});
# Python
from github_action_toolkit import CancellationHandler
from github_action_toolkit.exceptions import CancellationRequested
cancellation = CancellationHandler()
def cleanup():
print('Cleaning up...')
# Cleanup code
cancellation.register(cleanup)
cancellation.enable()
try:
# Your long-running code
process_data()
except CancellationRequested:
print('Operation cancelled')
raise SystemExit(0)
Common Pitfalls¶
1. Input Naming¶
Node.js / Javascript converts input names automatically:
// JavaScript - both work
core.getInput('my-input')
core.getInput('my_input')
Python keeps the exact name:
# Python - use exact name from action.yml
get_user_input('my-input') # if action.yml has 'my-input'
get_user_input('my_input') # if action.yml has 'my_input'
2. Boolean Inputs¶
JavaScript has special boolean handling:
// JavaScript
const enabled = core.getBooleanInput('enabled'); // true/false
Python requires explicit conversion:
# Python
from github_action_toolkit import get_user_input_as
enabled = get_user_input_as('enabled', bool, default_value=False)
3. Multiline Outputs¶
Both support multiline outputs with proper escaping:
// JavaScript
core.setOutput('result', 'line1\nline2\nline3');
# Python - automatically handles newlines
from github_action_toolkit import set_output
set_output('result', 'line1\nline2\nline3')
4. Action Metadata¶
Python actions typically need explicit Python setup in composite actions, while JavaScript actions bundle dependencies:
# JavaScript - dependencies bundled in dist/
runs:
using: 'node20'
main: 'dist/index.js'
# Python - needs runtime setup
runs:
using: 'composite'
steps:
- uses: actions/setup-python@v5
with:
python-version: '3.11'
- run: pip install github-action-toolkit
shell: bash
- run: python action.py
shell: bash
Benefits of Python¶
Simpler Dependency Management: No need for
npm installor bundling withnccBetter Data Processing: Rich ecosystem for data manipulation (pandas, numpy, etc.)
Type Safety: Strong type system with type hints
Familiar Syntax: Python is widely known and easy to read
Scientific Computing: Access to ML/AI libraries if needed
Need Help?¶
GitHub Issues - Report migration issues
Discussions - Ask questions about migration