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:
@@ -0,0 +1,5 @@
|
||||
{
|
||||
"name": "@activepieces/piece-python-code",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
{
|
||||
"name": "pieces-python-code",
|
||||
"$schema": "../../../../node_modules/nx/schemas/project-schema.json",
|
||||
"sourceRoot": "packages/pieces/community/python-code/src",
|
||||
"projectType": "library",
|
||||
"release": {
|
||||
"version": {
|
||||
"currentVersionResolver": "git-tag",
|
||||
"preserveLocalDependencyProtocols": false,
|
||||
"manifestRootsToUpdate": [
|
||||
"dist/{projectRoot}"
|
||||
]
|
||||
}
|
||||
},
|
||||
"tags": [],
|
||||
"targets": {
|
||||
"build": {
|
||||
"executor": "@nx/js:tsc",
|
||||
"outputs": [
|
||||
"{options.outputPath}"
|
||||
],
|
||||
"options": {
|
||||
"outputPath": "dist/packages/pieces/community/python-code",
|
||||
"tsConfig": "packages/pieces/community/python-code/tsconfig.lib.json",
|
||||
"packageJson": "packages/pieces/community/python-code/package.json",
|
||||
"main": "packages/pieces/community/python-code/src/index.ts",
|
||||
"assets": [
|
||||
"packages/pieces/community/python-code/*.md"
|
||||
],
|
||||
"buildableProjectDepsInPackageJsonType": "dependencies",
|
||||
"updateBuildableProjectDepsInPackageJson": true
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build",
|
||||
"prebuild"
|
||||
]
|
||||
},
|
||||
"nx-release-publish": {
|
||||
"options": {
|
||||
"packageRoot": "dist/{projectRoot}"
|
||||
}
|
||||
},
|
||||
"lint": {
|
||||
"executor": "@nx/eslint:lint",
|
||||
"outputs": [
|
||||
"{options.outputFile}"
|
||||
]
|
||||
},
|
||||
"prebuild": {
|
||||
"executor": "nx:run-commands",
|
||||
"options": {
|
||||
"cwd": "packages/pieces/community/python-code",
|
||||
"command": "bun install --no-save --silent"
|
||||
},
|
||||
"dependsOn": [
|
||||
"^build"
|
||||
]
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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: [],
|
||||
});
|
||||
@@ -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}`);
|
||||
}
|
||||
},
|
||||
});
|
||||
@@ -0,0 +1,19 @@
|
||||
{
|
||||
"extends": "../../../../tsconfig.base.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"strict": true,
|
||||
"noImplicitOverride": true,
|
||||
"noImplicitReturns": true,
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noPropertyAccessFromIndexSignature": true
|
||||
},
|
||||
"files": [],
|
||||
"include": [],
|
||||
"references": [
|
||||
{
|
||||
"path": "./tsconfig.lib.json"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"extends": "./tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"module": "commonjs",
|
||||
"outDir": "../../../../dist/out-tsc",
|
||||
"declaration": true,
|
||||
"types": ["node"]
|
||||
},
|
||||
"exclude": ["jest.config.ts", "src/**/*.spec.ts", "src/**/*.test.ts"],
|
||||
"include": ["src/**/*.ts"]
|
||||
}
|
||||
Reference in New Issue
Block a user