mirror of
https://github.com/czlonkowski/n8n-skills.git
synced 2026-03-16 23:43:08 +00:00
Implements comprehensive Python Code node guidance with critical focus on "NO external libraries" limitation. ## Skill #7 - n8n Code Python **Critical Message**: Use JavaScript for 95% of use cases. Python in n8n has NO external libraries (no requests, pandas, numpy). ### Files Created **Core Skill Files (6 files, 4,205 lines total)**: 1. **SKILL.md** (748 lines) - When to use Python vs JavaScript (95% JavaScript recommendation) - Critical limitation: NO external libraries - Mode selection (All Items vs Each Item) - Data access overview (_input, _json, _node) - Return format requirements - Standard library overview 2. **DATA_ACCESS.md** (702 lines) - _input.all() - Process all items - _input.first() - Get first item - _input.item - Current item (Each Item mode only) - _node["Name"] - Reference other nodes - Webhook body structure (data under ["body"]) - Pattern selection guide - Python vs JavaScript comparison 3. **STANDARD_LIBRARY.md** (974 lines) - Complete reference for available modules - json - JSON parsing and generation - datetime - Date/time operations - re - Regular expressions - base64 - Encoding/decoding - hashlib - Hashing (MD5, SHA256) - urllib.parse - URL operations - math, random, statistics - What's NOT available (requests, pandas, numpy, etc.) - Workarounds for missing libraries 4. **COMMON_PATTERNS.md** (794 lines) - 10 production-tested Python patterns - Multi-source data aggregation - Regex-based filtering - Markdown to structured data - JSON object comparison - CRM data transformation - Release notes processing - Array transformation - Dictionary lookup - Top N filtering - String aggregation - Python vs JavaScript pattern comparison 5. **ERROR_PATTERNS.md** (601 lines) - Top 5 Python-specific errors with solutions - Error #1: ModuleNotFoundError (THE critical Python error) - Error #2: Empty code / missing return - Error #3: KeyError (use .get() instead) - Error #4: IndexError (check bounds first) - Error #5: Incorrect return format - Error prevention checklist - Quick fix reference table - Testing patterns 6. **README.md** (386 lines) - Skill metadata and activation triggers - "JavaScript First" recommendation prominent - What this skill teaches - File structure overview - Integration with other skills - Success metrics checklist - Quick reference guide - Common use cases - Limitations and workarounds - Best practices **Evaluations (5 scenarios)**: 1. **eval-001-module-import-error.json** - Tests understanding of external library limitation - Scenario: ModuleNotFoundError with requests - Expected: Recommend JavaScript or HTTP Request node 2. **eval-002-dictionary-keyerror.json** - Tests safe dictionary access with .get() - Scenario: KeyError when accessing missing field - Expected: Use .get() with default values 3. **eval-003-webhook-body-gotcha.json** - Tests webhook data under ["body"] understanding - Scenario: KeyError when accessing webhook data directly - Expected: Access via data.get("body", {}) 4. **eval-004-return-format-error.json** - Tests proper return format requirement - Scenario: Returning plain dict instead of array - Expected: Return [{"json": {...}}] 5. **eval-005-standard-library-usage.json** - Tests knowledge of available modules - Scenario: What modules for JSON, hashing, dates, regex - Expected: json, hashlib, datetime, re (standard library only) ### Key Features **Critical Limitations Emphasized**: - NO external libraries (no requests, pandas, numpy) - JavaScript recommended for 95% of use cases - Only standard library available - ModuleNotFoundError is #1 Python error **Python-Specific Syntax**: - Underscore prefix: _input, _json, _node (vs $ in JavaScript) - Dictionary access: _json["body"]["field"] (vs dot notation) - Safe access: .get() method with defaults **Complete Standard Library Coverage**: - 15+ modules documented with examples - json, datetime, re, base64, hashlib, urllib.parse - math, random, statistics, collections - Clear list of what's NOT available - Workarounds for missing functionality **Production Patterns**: - 10 tested patterns adapted from JavaScript - Python-specific implementations - List comprehensions and dictionary operations - Standard library usage examples **Error Prevention Focus**: - Top 5 errors cover majority of failures - ModuleNotFoundError prominently featured - Safe dictionary access (.get()) - Proper return format emphasized - Error prevention checklist ### Integration Works seamlessly with: - **n8n Code JavaScript**: Compare approaches, know when to use which - **n8n Expression Syntax**: Different from {{}} expressions - **n8n MCP Tools Expert**: Validate Code node configurations - **n8n Workflow Patterns**: Code nodes in larger workflows - **n8n Node Configuration**: Configure mode and connections ### Statistics - **6 skill files**: 4,205 lines total - **5 evaluations**: Cover critical Python scenarios - **10 patterns**: Production-tested Python code - **15+ modules**: Standard library coverage - **5 top errors**: Prevention and solutions ### Design Principles 1. **JavaScript First**: 95% recommendation throughout 2. **Critical Limitation**: NO external libraries emphasized everywhere 3. **Safe Patterns**: .get() for dicts, bounds checking for lists 4. **Proper Format**: [{"json": {...}}] return format 5. **Standard Library**: Complete reference with examples 6. **Error Prevention**: Top 5 errors with solutions ### Recommendation **Use JavaScript Code node for 95% of use cases.** Use Python only when: - Complex Python-specific logic required - Python standard library features needed - Team more comfortable with Python than JavaScript For HTTP requests, date operations, and most transformations → Use JavaScript. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> Conceived by Romuald Członkowski - https://www.aiadvisors.pl/en
602 lines
14 KiB
Markdown
602 lines
14 KiB
Markdown
# Error Patterns - Python Code Node
|
|
|
|
Common Python Code node errors and how to fix them.
|
|
|
|
---
|
|
|
|
## Error Overview
|
|
|
|
**Top 5 Python Code Node Errors**:
|
|
|
|
1. **ModuleNotFoundError** - Trying to import external libraries (Python-specific)
|
|
2. **Empty Code / Missing Return** - No code or return statement
|
|
3. **KeyError** - Dictionary access without .get()
|
|
4. **IndexError** - List access without bounds checking
|
|
5. **Incorrect Return Format** - Wrong data structure returned
|
|
|
|
These 5 errors cover the majority of Python Code node failures.
|
|
|
|
---
|
|
|
|
## Error #1: ModuleNotFoundError (MOST CRITICAL)
|
|
|
|
**Frequency**: Very common in Python Code nodes
|
|
|
|
**What it is**: Attempting to import external libraries that aren't available.
|
|
|
|
### The Problem
|
|
|
|
```python
|
|
# ❌ WRONG: External libraries not available
|
|
import requests # ModuleNotFoundError: No module named 'requests'
|
|
import pandas # ModuleNotFoundError: No module named 'pandas'
|
|
import numpy # ModuleNotFoundError: No module named 'numpy'
|
|
import bs4 # ModuleNotFoundError: No module named 'bs4'
|
|
import pymongo # ModuleNotFoundError: No module named 'pymongo'
|
|
import psycopg2 # ModuleNotFoundError: No module named 'psycopg2'
|
|
|
|
# This code will FAIL - these libraries are not installed!
|
|
response = requests.get("https://api.example.com/data")
|
|
```
|
|
|
|
### The Solution
|
|
|
|
**Option 1: Use JavaScript Instead** (Recommended for 95% of cases)
|
|
|
|
```javascript
|
|
// ✅ JavaScript Code node with $helpers.httpRequest()
|
|
const response = await $helpers.httpRequest({
|
|
method: 'GET',
|
|
url: 'https://api.example.com/data'
|
|
});
|
|
|
|
return [{json: response}];
|
|
```
|
|
|
|
**Option 2: Use n8n HTTP Request Node**
|
|
|
|
```python
|
|
# ✅ Add HTTP Request node BEFORE Python Code node
|
|
# Access the response in Python Code node
|
|
|
|
response = _input.first()["json"]
|
|
|
|
return [{
|
|
"json": {
|
|
"status": response.get("status"),
|
|
"data": response.get("body"),
|
|
"processed": True
|
|
}
|
|
}]
|
|
```
|
|
|
|
**Option 3: Use Standard Library Only**
|
|
|
|
```python
|
|
# ✅ Use urllib from standard library (limited functionality)
|
|
from urllib.request import urlopen
|
|
from urllib.parse import urlencode
|
|
import json
|
|
|
|
# Simple GET request (no headers, no auth)
|
|
url = "https://api.example.com/data"
|
|
with urlopen(url) as response:
|
|
data = json.loads(response.read())
|
|
|
|
return [{"json": data}]
|
|
```
|
|
|
|
### Common Library Replacements
|
|
|
|
| Need | ❌ External Library | ✅ Alternative |
|
|
|------|-------------------|----------------|
|
|
| HTTP requests | `requests` | Use HTTP Request node or JavaScript |
|
|
| Data analysis | `pandas` | Use Python list comprehensions |
|
|
| Database | `psycopg2`, `pymongo` | Use n8n database nodes |
|
|
| Web scraping | `beautifulsoup4` | Use HTML Extract node |
|
|
| Excel | `openpyxl` | Use Spreadsheet File node |
|
|
| Image processing | `pillow` | Use external API or node |
|
|
|
|
### Available Standard Library Modules
|
|
|
|
```python
|
|
# ✅ THESE WORK - Standard library only
|
|
import json # JSON parsing
|
|
import datetime # Date/time operations
|
|
import re # Regular expressions
|
|
import base64 # Base64 encoding
|
|
import hashlib # Hashing (MD5, SHA256)
|
|
import urllib.parse # URL parsing and encoding
|
|
import math # Math functions
|
|
import random # Random numbers
|
|
import statistics # Statistical functions
|
|
import collections # defaultdict, Counter, etc.
|
|
```
|
|
|
|
---
|
|
|
|
## Error #2: Empty Code / Missing Return
|
|
|
|
**Frequency**: Common across all Code nodes
|
|
|
|
**What it is**: Code node has no code or no return statement.
|
|
|
|
### The Problem
|
|
|
|
```python
|
|
# ❌ WRONG: Empty code
|
|
# (nothing here)
|
|
|
|
# ❌ WRONG: Code but no return
|
|
items = _input.all()
|
|
processed = [item for item in items if item["json"].get("active")]
|
|
# Forgot to return!
|
|
|
|
# ❌ WRONG: Return in wrong scope
|
|
if _input.all():
|
|
return [{"json": {"result": "success"}}]
|
|
# Return is inside if block - may not execute!
|
|
```
|
|
|
|
### The Solution
|
|
|
|
```python
|
|
# ✅ CORRECT: Always return
|
|
all_items = _input.all()
|
|
|
|
if not all_items:
|
|
# Return empty array or error
|
|
return [{"json": {"error": "No items"}}]
|
|
|
|
# Process items
|
|
processed = [item for item in all_items if item["json"].get("active")]
|
|
|
|
# Always return at the end
|
|
return processed if processed else [{"json": {"message": "No active items"}}]
|
|
```
|
|
|
|
### Best Practice
|
|
|
|
```python
|
|
# ✅ GOOD: Return at end of function (unconditional)
|
|
def process_items():
|
|
items = _input.all()
|
|
|
|
if not items:
|
|
return [{"json": {"error": "Empty input"}}]
|
|
|
|
# Process
|
|
result = []
|
|
for item in items:
|
|
result.append({"json": item["json"]})
|
|
|
|
return result
|
|
|
|
# Call function and return result
|
|
return process_items()
|
|
```
|
|
|
|
---
|
|
|
|
## Error #3: KeyError
|
|
|
|
**Frequency**: Very common in Python Code nodes
|
|
|
|
**What it is**: Accessing dictionary key that doesn't exist.
|
|
|
|
### The Problem
|
|
|
|
```python
|
|
# ❌ WRONG: Direct key access
|
|
item = _input.first()["json"]
|
|
|
|
name = item["name"] # KeyError if "name" doesn't exist!
|
|
email = item["email"] # KeyError if "email" doesn't exist!
|
|
age = item["age"] # KeyError if "age" doesn't exist!
|
|
|
|
return [{
|
|
"json": {
|
|
"name": name,
|
|
"email": email,
|
|
"age": age
|
|
}
|
|
}]
|
|
```
|
|
|
|
### Error Message
|
|
|
|
```
|
|
KeyError: 'name'
|
|
```
|
|
|
|
### The Solution
|
|
|
|
```python
|
|
# ✅ CORRECT: Use .get() with defaults
|
|
item = _input.first()["json"]
|
|
|
|
name = item.get("name", "Unknown")
|
|
email = item.get("email", "no-email@example.com")
|
|
age = item.get("age", 0)
|
|
|
|
return [{
|
|
"json": {
|
|
"name": name,
|
|
"email": email,
|
|
"age": age
|
|
}
|
|
}]
|
|
```
|
|
|
|
### Nested Dictionary Access
|
|
|
|
```python
|
|
# ❌ WRONG: Nested key access
|
|
webhook = _input.first()["json"]
|
|
name = webhook["body"]["user"]["name"] # Multiple KeyErrors possible!
|
|
|
|
# ✅ CORRECT: Safe nested access
|
|
webhook = _input.first()["json"]
|
|
body = webhook.get("body", {})
|
|
user = body.get("user", {})
|
|
name = user.get("name", "Unknown")
|
|
|
|
# ✅ ALSO CORRECT: Chained .get()
|
|
name = (
|
|
webhook
|
|
.get("body", {})
|
|
.get("user", {})
|
|
.get("name", "Unknown")
|
|
)
|
|
|
|
return [{"json": {"name": name}}]
|
|
```
|
|
|
|
### Webhook Body Access (Critical!)
|
|
|
|
```python
|
|
# ❌ WRONG: Forgetting webhook data is under "body"
|
|
webhook = _input.first()["json"]
|
|
name = webhook["name"] # KeyError!
|
|
email = webhook["email"] # KeyError!
|
|
|
|
# ✅ CORRECT: Access via ["body"]
|
|
webhook = _input.first()["json"]
|
|
body = webhook.get("body", {})
|
|
name = body.get("name", "Unknown")
|
|
email = body.get("email", "no-email")
|
|
|
|
return [{
|
|
"json": {
|
|
"name": name,
|
|
"email": email
|
|
}
|
|
}]
|
|
```
|
|
|
|
---
|
|
|
|
## Error #4: IndexError
|
|
|
|
**Frequency**: Common when processing arrays/lists
|
|
|
|
**What it is**: Accessing list index that doesn't exist.
|
|
|
|
### The Problem
|
|
|
|
```python
|
|
# ❌ WRONG: Assuming items exist
|
|
all_items = _input.all()
|
|
first_item = all_items[0] # IndexError if list is empty!
|
|
second_item = all_items[1] # IndexError if only 1 item!
|
|
|
|
return [{
|
|
"json": {
|
|
"first": first_item["json"],
|
|
"second": second_item["json"]
|
|
}
|
|
}]
|
|
```
|
|
|
|
### Error Message
|
|
|
|
```
|
|
IndexError: list index out of range
|
|
```
|
|
|
|
### The Solution
|
|
|
|
```python
|
|
# ✅ CORRECT: Check length first
|
|
all_items = _input.all()
|
|
|
|
if len(all_items) >= 2:
|
|
first_item = all_items[0]["json"]
|
|
second_item = all_items[1]["json"]
|
|
|
|
return [{
|
|
"json": {
|
|
"first": first_item,
|
|
"second": second_item
|
|
}
|
|
}]
|
|
else:
|
|
return [{
|
|
"json": {
|
|
"error": f"Expected 2+ items, got {len(all_items)}"
|
|
}
|
|
}]
|
|
```
|
|
|
|
### Safe First Item Access
|
|
|
|
```python
|
|
# ✅ CORRECT: Use _input.first() instead of [0]
|
|
# This is safer than manual indexing
|
|
first_item = _input.first()["json"]
|
|
|
|
return [{"json": first_item}]
|
|
|
|
# ✅ ALSO CORRECT: Check before accessing
|
|
all_items = _input.all()
|
|
if all_items:
|
|
first_item = all_items[0]["json"]
|
|
else:
|
|
first_item = {}
|
|
|
|
return [{"json": first_item}]
|
|
```
|
|
|
|
### Slice Instead of Index
|
|
|
|
```python
|
|
# ✅ CORRECT: Use slicing (never raises IndexError)
|
|
all_items = _input.all()
|
|
|
|
# Get first 5 items (won't fail if fewer than 5)
|
|
first_five = all_items[:5]
|
|
|
|
# Get items after first (won't fail if empty)
|
|
rest = all_items[1:]
|
|
|
|
return [{"json": item["json"]} for item in first_five]
|
|
```
|
|
|
|
---
|
|
|
|
## Error #5: Incorrect Return Format
|
|
|
|
**Frequency**: Common for new users
|
|
|
|
**What it is**: Returning data in wrong format (n8n expects array of objects with "json" key).
|
|
|
|
### The Problem
|
|
|
|
```python
|
|
# ❌ WRONG: Returning plain dictionary
|
|
return {"name": "Alice", "age": 30}
|
|
|
|
# ❌ WRONG: Returning array without "json" wrapper
|
|
return [{"name": "Alice"}, {"name": "Bob"}]
|
|
|
|
# ❌ WRONG: Returning None
|
|
return None
|
|
|
|
# ❌ WRONG: Returning string
|
|
return "success"
|
|
|
|
# ❌ WRONG: Returning single item (not array)
|
|
return {"json": {"name": "Alice"}}
|
|
```
|
|
|
|
### The Solution
|
|
|
|
```python
|
|
# ✅ CORRECT: Array of objects with "json" key
|
|
return [{"json": {"name": "Alice", "age": 30}}]
|
|
|
|
# ✅ CORRECT: Multiple items
|
|
return [
|
|
{"json": {"name": "Alice"}},
|
|
{"json": {"name": "Bob"}}
|
|
]
|
|
|
|
# ✅ CORRECT: Transform items
|
|
all_items = _input.all()
|
|
return [
|
|
{"json": item["json"]}
|
|
for item in all_items
|
|
]
|
|
|
|
# ✅ CORRECT: Empty array (valid)
|
|
return []
|
|
|
|
# ✅ CORRECT: Single item still needs array wrapper
|
|
return [{"json": {"result": "success"}}]
|
|
```
|
|
|
|
### Common Scenarios
|
|
|
|
**Scenario 1: Aggregation (Return Single Result)**
|
|
|
|
```python
|
|
# Calculate total
|
|
all_items = _input.all()
|
|
total = sum(item["json"].get("amount", 0) for item in all_items)
|
|
|
|
# ✅ CORRECT: Wrap in array with "json"
|
|
return [{
|
|
"json": {
|
|
"total": total,
|
|
"count": len(all_items)
|
|
}
|
|
}]
|
|
```
|
|
|
|
**Scenario 2: Filtering (Return Multiple Results)**
|
|
|
|
```python
|
|
# Filter active items
|
|
all_items = _input.all()
|
|
active = [item for item in all_items if item["json"].get("active")]
|
|
|
|
# ✅ CORRECT: Already in correct format
|
|
return active
|
|
|
|
# ✅ ALSO CORRECT: If transforming
|
|
return [
|
|
{"json": {**item["json"], "filtered": True}}
|
|
for item in active
|
|
]
|
|
```
|
|
|
|
**Scenario 3: No Results**
|
|
|
|
```python
|
|
# ✅ CORRECT: Return empty array
|
|
return []
|
|
|
|
# ✅ ALSO CORRECT: Return error message
|
|
return [{"json": {"error": "No results found"}}]
|
|
```
|
|
|
|
---
|
|
|
|
## Bonus Error: AttributeError
|
|
|
|
**What it is**: Using _input.item in wrong mode.
|
|
|
|
### The Problem
|
|
|
|
```python
|
|
# ❌ WRONG: Using _input.item in "All Items" mode
|
|
current = _input.item # None in "All Items" mode
|
|
data = current["json"] # AttributeError: 'NoneType' object has no attribute '__getitem__'
|
|
```
|
|
|
|
### The Solution
|
|
|
|
```python
|
|
# ✅ CORRECT: Check mode or use appropriate method
|
|
# In "All Items" mode, use:
|
|
all_items = _input.all()
|
|
|
|
# In "Each Item" mode, use:
|
|
current_item = _input.item
|
|
|
|
# ✅ SAFE: Check if item exists
|
|
current = _input.item
|
|
if current:
|
|
data = current["json"]
|
|
return [{"json": data}]
|
|
else:
|
|
# Running in "All Items" mode
|
|
return _input.all()
|
|
```
|
|
|
|
---
|
|
|
|
## Error Prevention Checklist
|
|
|
|
Before running your Python Code node, verify:
|
|
|
|
- [ ] **No external imports**: Only standard library (json, datetime, re, etc.)
|
|
- [ ] **Code returns data**: Every code path ends with `return`
|
|
- [ ] **Correct format**: Returns `[{"json": {...}}]` (array with "json" key)
|
|
- [ ] **Safe dictionary access**: Uses `.get()` instead of `[]` for dictionaries
|
|
- [ ] **Safe list access**: Checks length before indexing or uses slicing
|
|
- [ ] **Webhook body access**: Accesses webhook data via `_json["body"]`
|
|
- [ ] **No None returns**: Returns empty array `[]` instead of `None`
|
|
- [ ] **Mode awareness**: Uses `_input.all()`, `_input.first()`, or `_input.item` appropriately
|
|
|
|
---
|
|
|
|
## Quick Fix Reference
|
|
|
|
| Error | Quick Fix |
|
|
|-------|-----------|
|
|
| `ModuleNotFoundError` | Use JavaScript or HTTP Request node instead |
|
|
| `KeyError: 'field'` | Change `data["field"]` to `data.get("field", default)` |
|
|
| `IndexError: list index out of range` | Check `if len(items) > 0:` before `items[0]` |
|
|
| Empty output | Add `return [{"json": {...}}]` at end |
|
|
| `AttributeError: 'NoneType'` | Check mode setting or verify `_input.item` exists |
|
|
| Wrong format error | Wrap result: `return [{"json": result}]` |
|
|
| Webhook KeyError | Access via `_json.get("body", {})` |
|
|
|
|
---
|
|
|
|
## Testing Your Code
|
|
|
|
### Test Pattern 1: Handle Empty Input
|
|
|
|
```python
|
|
# ✅ Always test with empty input
|
|
all_items = _input.all()
|
|
|
|
if not all_items:
|
|
return [{"json": {"message": "No items to process"}}]
|
|
|
|
# Continue with processing
|
|
# ...
|
|
```
|
|
|
|
### Test Pattern 2: Test with Missing Fields
|
|
|
|
```python
|
|
# ✅ Use .get() with defaults
|
|
item = _input.first()["json"]
|
|
|
|
# These won't fail even if fields missing
|
|
name = item.get("name", "Unknown")
|
|
email = item.get("email", "no-email")
|
|
age = item.get("age", 0)
|
|
|
|
return [{"json": {"name": name, "email": email, "age": age}}]
|
|
```
|
|
|
|
### Test Pattern 3: Test Both Modes
|
|
|
|
```python
|
|
# ✅ Code that works in both modes
|
|
try:
|
|
# Try "Each Item" mode first
|
|
current = _input.item
|
|
if current:
|
|
return [{"json": current["json"]}]
|
|
except:
|
|
pass
|
|
|
|
# Fall back to "All Items" mode
|
|
all_items = _input.all()
|
|
return all_items if all_items else [{"json": {"message": "No data"}}]
|
|
```
|
|
|
|
---
|
|
|
|
## Summary
|
|
|
|
**Top 5 Errors to Avoid**:
|
|
1. **ModuleNotFoundError** - Use JavaScript or n8n nodes instead
|
|
2. **Missing return** - Always end with `return [{"json": {...}}]`
|
|
3. **KeyError** - Use `.get()` for dictionary access
|
|
4. **IndexError** - Check length before indexing
|
|
5. **Wrong format** - Return `[{"json": {...}}]`, not plain objects
|
|
|
|
**Golden Rules**:
|
|
- NO external libraries (use JavaScript instead)
|
|
- ALWAYS use `.get()` for dictionaries
|
|
- ALWAYS return `[{"json": {...}}]` format
|
|
- CHECK lengths before list access
|
|
- ACCESS webhook data via `["body"]`
|
|
|
|
**Remember**:
|
|
- JavaScript is recommended for 95% of use cases
|
|
- Python has limitations (no requests, pandas, numpy)
|
|
- Use n8n nodes for complex operations
|
|
|
|
**See Also**:
|
|
- [SKILL.md](SKILL.md) - Python Code overview
|
|
- [DATA_ACCESS.md](DATA_ACCESS.md) - Data access patterns
|
|
- [STANDARD_LIBRARY.md](STANDARD_LIBRARY.md) - Available modules
|
|
- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Production patterns
|