Add Python/Ruby code pieces and fix template loading performance

- Add Python code execution piece with subprocess-based runner
- Add Ruby code execution piece with subprocess-based runner
- Fix template loading: fetch individual templates from cloud for community edition
- Add piece name aliasing for renamed pieces (piece-text-ai → piece-ai, etc.)
- Add dev pieces caching to avoid disk reads on every request (60s TTL)
- Add Python and Ruby logos to Django static files

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
poduck
2025-12-20 00:18:42 -05:00
parent 3aa7199503
commit f3e1b8f8bf
19 changed files with 557 additions and 6 deletions

View File

@@ -0,0 +1,18 @@
import { createPiece, PieceAuth } from '@activepieces/pieces-framework';
import { PieceCategory } from '@activepieces/shared';
import { runPythonCode } from './lib/run-python-code';
// Python logo - hosted on Django backend
const PYTHON_LOGO = 'http://lvh.me:8000/static/images/python-logo.svg';
export const pythonCode = createPiece({
displayName: 'Python Code',
description: 'Execute Python code in your automations',
auth: PieceAuth.None(),
minimumSupportedRelease: '0.36.1',
logoUrl: PYTHON_LOGO,
categories: [PieceCategory.CORE, PieceCategory.DEVELOPER_TOOLS],
authors: ['smoothschedule'],
actions: [runPythonCode],
triggers: [],
});

View File

@@ -0,0 +1,112 @@
import { createAction, Property } from '@activepieces/pieces-framework';
import { exec } from 'child_process';
import { promisify } from 'util';
import * as fs from 'fs';
import * as path from 'path';
import * as os from 'os';
const execAsync = promisify(exec);
export const runPythonCode = createAction({
name: 'run_python_code',
displayName: 'Run Python Code',
description: 'Execute Python code and return the output. Use print() to output results.',
props: {
code: Property.LongText({
displayName: 'Python Code',
description: 'The Python code to execute. Use print() to output results that will be captured.',
required: true,
defaultValue: `# Example: Process input data
import json
# Access inputs via the 'inputs' variable (parsed from JSON)
# Example: name = inputs.get('name', 'World')
# Your code here
result = "Hello from Python!"
# Print your output (will be captured as the action result)
print(result)`,
}),
inputs: Property.Object({
displayName: 'Inputs',
description: 'Input data to pass to the Python code. Available as the `inputs` variable (dict).',
required: false,
defaultValue: {},
}),
timeout: Property.Number({
displayName: 'Timeout (seconds)',
description: 'Maximum execution time in seconds',
required: false,
defaultValue: 30,
}),
},
async run(context) {
const { code, inputs, timeout } = context.propsValue;
const timeoutMs = (timeout || 30) * 1000;
// Create a temporary file for the Python code
const tmpDir = os.tmpdir();
const scriptPath = path.join(tmpDir, `ap_python_${Date.now()}.py`);
const inputPath = path.join(tmpDir, `ap_python_input_${Date.now()}.json`);
try {
// Write inputs to a JSON file
await fs.promises.writeFile(inputPath, JSON.stringify(inputs || {}));
// Wrap the user code to load inputs
const wrappedCode = `
import json
import sys
# Load inputs from JSON file
with open('${inputPath.replace(/\\/g, '\\\\')}', 'r') as f:
inputs = json.load(f)
# User code starts here
${code}
`;
// Write the script to a temp file
await fs.promises.writeFile(scriptPath, wrappedCode);
// Execute Python
const { stdout, stderr } = await execAsync(`python3 "${scriptPath}"`, {
timeout: timeoutMs,
maxBuffer: 10 * 1024 * 1024, // 10MB buffer
});
// Clean up temp files
await fs.promises.unlink(scriptPath).catch(() => {});
await fs.promises.unlink(inputPath).catch(() => {});
// Try to parse output as JSON, otherwise return as string
const output = stdout.trim();
let result: unknown;
try {
result = JSON.parse(output);
} catch {
result = output;
}
return {
success: true,
output: result,
stdout: stdout,
stderr: stderr || null,
};
} catch (error: unknown) {
// Clean up temp files on error
await fs.promises.unlink(scriptPath).catch(() => {});
await fs.promises.unlink(inputPath).catch(() => {});
const execError = error as { stderr?: string; message?: string; killed?: boolean };
if (execError.killed) {
throw new Error(`Python script timed out after ${timeout} seconds`);
}
throw new Error(`Python execution failed: ${execError.stderr || execError.message}`);
}
},
});