diff --git a/evaluations/code-python/eval-001-module-import-error.json b/evaluations/code-python/eval-001-module-import-error.json new file mode 100644 index 0000000..b1365b9 --- /dev/null +++ b/evaluations/code-python/eval-001-module-import-error.json @@ -0,0 +1,28 @@ +{ + "id": "code-python-001", + "skill": "n8n-code-python", + "name": "Module Import Error - External Libraries Not Available", + "description": "Tests understanding that Python Code nodes have NO external libraries", + "query": "I'm writing a Python Code node to fetch data from an API. Here's my code:\n\n```python\nimport requests\n\nurl = \"https://api.example.com/users\"\nresponse = requests.get(url)\ndata = response.json()\n\nreturn [{\"json\": data}]\n```\n\nBut I'm getting a ModuleNotFoundError. What's wrong?", + "expected_behavior": [ + "Immediately identify this is the #1 Python Code node limitation", + "Explain that NO external libraries are available (no requests, pandas, numpy)", + "Recommend using JavaScript Code node instead (95% recommendation)", + "Suggest alternative: Use HTTP Request node BEFORE Python Code node", + "Mention urllib.request from standard library as limited workaround", + "Emphasize JavaScript has $helpers.httpRequest() built-in", + "Reference ERROR_PATTERNS.md Error #1" + ], + "expected_output_includes": [ + "ModuleNotFoundError", + "NO external libraries", + "Use JavaScript", + "HTTP Request node", + "standard library only" + ], + "should_not_include": [ + "pip install", + "install requests", + "add requirements.txt" + ] +} diff --git a/evaluations/code-python/eval-002-dictionary-keyerror.json b/evaluations/code-python/eval-002-dictionary-keyerror.json new file mode 100644 index 0000000..de1723a --- /dev/null +++ b/evaluations/code-python/eval-002-dictionary-keyerror.json @@ -0,0 +1,26 @@ +{ + "id": "code-python-002", + "skill": "n8n-code-python", + "name": "Dictionary KeyError - Use .get() Instead", + "description": "Tests understanding of safe dictionary access with .get()", + "query": "My Python Code node is failing with this error:\n\n```\nKeyError: 'email'\n```\n\nHere's my code:\n\n```python\nitem = _input.first()[\"json\"]\n\nname = item[\"name\"]\nemail = item[\"email\"]\nage = item[\"age\"]\n\nreturn [{\n \"json\": {\n \"name\": name,\n \"email\": email,\n \"age\": age\n }\n}]\n```\n\nHow do I fix this?", + "expected_behavior": [ + "Identify KeyError is from direct dictionary key access", + "Explain that some items may not have 'email' field", + "Recommend using .get() method with default values", + "Show corrected code with .get()", + "Mention this is Error #3 in ERROR_PATTERNS.md", + "Explain difference between item['key'] and item.get('key', default)" + ], + "expected_output_includes": [ + "KeyError", + ".get()", + "default value", + "item.get(\"email\", \"default\")" + ], + "should_not_include": [ + "try/except KeyError", + "if 'email' in item" + ], + "correct_code_pattern": "item.get(\"email\", " +} diff --git a/evaluations/code-python/eval-003-webhook-body-gotcha.json b/evaluations/code-python/eval-003-webhook-body-gotcha.json new file mode 100644 index 0000000..bab566b --- /dev/null +++ b/evaluations/code-python/eval-003-webhook-body-gotcha.json @@ -0,0 +1,23 @@ +{ + "id": "code-python-003", + "skill": "n8n-code-python", + "name": "Webhook Body Gotcha - Data Under [\"body\"]", + "description": "Tests understanding that webhook data is nested under [\"body\"] key", + "query": "I have a Webhook node receiving this JSON:\n\n```json\n{\n \"name\": \"Alice\",\n \"email\": \"alice@example.com\",\n \"age\": 30\n}\n```\n\nIn my Python Code node, I'm trying to access the data:\n\n```python\ndata = _input.first()[\"json\"]\n\nname = data[\"name\"] # KeyError!\nemail = data[\"email\"] # KeyError!\n\nreturn [{\"json\": {\"name\": name, \"email\": email}}]\n```\n\nBut I'm getting KeyError. The webhook is receiving data correctly. What's wrong?", + "expected_behavior": [ + "Immediately recognize this is the webhook .body gotcha", + "Explain webhook node wraps incoming data under 'body' key", + "Show the actual structure with headers, params, query, body", + "Provide corrected code accessing data[\"body\"]", + "Mention this is a CRITICAL gotcha highlighted in DATA_ACCESS.md", + "Recommend using .get() for safe access: data.get(\"body\", {})" + ], + "expected_output_includes": [ + "[\"body\"]", + "webhook wraps", + "nested under body", + "data.get(\"body\", {})" + ], + "correct_code_pattern": "data.get(\"body\", {})", + "should_emphasize": "This is the MOST COMMON webhook mistake" +} diff --git a/evaluations/code-python/eval-004-return-format-error.json b/evaluations/code-python/eval-004-return-format-error.json new file mode 100644 index 0000000..46f8cb4 --- /dev/null +++ b/evaluations/code-python/eval-004-return-format-error.json @@ -0,0 +1,25 @@ +{ + "id": "code-python-004", + "skill": "n8n-code-python", + "name": "Return Format Error - Must Return Array with json Key", + "description": "Tests understanding of correct return format for n8n Code nodes", + "query": "My Python Code node isn't outputting any data to the next node. Here's my code:\n\n```python\nall_items = _input.all()\n\ntotal = sum(item[\"json\"].get(\"amount\", 0) for item in all_items)\naverage = total / len(all_items) if all_items else 0\n\nreturn {\n \"total\": total,\n \"count\": len(all_items),\n \"average\": average\n}\n```\n\nThe code runs without errors, but the next node receives nothing. What's wrong?", + "expected_behavior": [ + "Identify incorrect return format (missing array wrapper and json key)", + "Explain n8n expects array of objects with 'json' key", + "Show corrected code with proper format: [{\"json\": {...}}]", + "Mention this is Error #5 in ERROR_PATTERNS.md", + "Emphasize: MUST return array, even for single result" + ], + "expected_output_includes": [ + "[{\"json\":", + "array", + "return format", + "must wrap" + ], + "correct_code_pattern": "return [{\"json\": {", + "should_not_include": [ + "return {", + "return total" + ] +} diff --git a/evaluations/code-python/eval-005-standard-library-usage.json b/evaluations/code-python/eval-005-standard-library-usage.json new file mode 100644 index 0000000..9cf4cd6 --- /dev/null +++ b/evaluations/code-python/eval-005-standard-library-usage.json @@ -0,0 +1,36 @@ +{ + "id": "code-python-005", + "skill": "n8n-code-python", + "name": "Standard Library Usage - Know What's Available", + "description": "Tests knowledge of available standard library modules", + "query": "I need to do the following in a Python Code node:\n\n1. Parse JSON from a string\n2. Calculate SHA256 hash of some data\n3. Format the current date as ISO string\n4. Extract email addresses using regex\n\nWhat modules are available? Can I use external libraries?", + "expected_behavior": [ + "Emphasize NO external libraries available", + "List available standard library modules for these tasks", + "json module for JSON parsing", + "hashlib module for SHA256", + "datetime module for dates", + "re module for regex", + "Provide code examples for each task", + "Reference STANDARD_LIBRARY.md" + ], + "expected_output_includes": [ + "import json", + "import hashlib", + "import datetime", + "import re", + "standard library only", + "NO external libraries" + ], + "should_not_include": [ + "import requests", + "import pandas", + "pip install" + ], + "correct_modules": [ + "json", + "hashlib", + "datetime", + "re" + ] +} diff --git a/skills/n8n-code-python/COMMON_PATTERNS.md b/skills/n8n-code-python/COMMON_PATTERNS.md new file mode 100644 index 0000000..c216f8a --- /dev/null +++ b/skills/n8n-code-python/COMMON_PATTERNS.md @@ -0,0 +1,794 @@ +# Common Patterns - Python Code Node + +Production-tested Python patterns for n8n Code nodes. + +--- + +## ⚠️ Important: JavaScript First + +**Use JavaScript for 95% of use cases.** + +Python in n8n has **NO external libraries** (no requests, pandas, numpy). + +Only use Python when: +- You have complex Python-specific logic +- You need Python's standard library features +- You're more comfortable with Python than JavaScript + +For most workflows, **JavaScript is the better choice**. + +--- + +## Pattern Overview + +These 10 patterns cover common n8n Code node scenarios using Python: + +1. **Multi-Source Data Aggregation** - Combine data from multiple nodes +2. **Regex-Based Filtering** - Filter items using pattern matching +3. **Markdown to Structured Data** - Parse markdown into structured format +4. **JSON Object Comparison** - Compare two JSON objects for changes +5. **CRM Data Transformation** - Transform CRM data to standard format +6. **Release Notes Processing** - Parse and categorize release notes +7. **Array Transformation** - Reshape arrays and extract fields +8. **Dictionary Lookup** - Create and use lookup dictionaries +9. **Top N Filtering** - Get top items by score/value +10. **String Aggregation** - Aggregate strings with formatting + +--- + +## Pattern 1: Multi-Source Data Aggregation + +**Use case**: Combine data from multiple sources (APIs, webhooks, databases). + +**Scenario**: Aggregate news articles from multiple sources. + +### Implementation + +```python +from datetime import datetime + +all_items = _input.all() +processed_articles = [] + +for item in all_items: + source_name = item["json"].get("name", "Unknown") + source_data = item["json"] + + # Process Hacker News source + if source_name == "Hacker News" and source_data.get("hits"): + for hit in source_data["hits"]: + processed_articles.append({ + "title": hit.get("title", "No title"), + "url": hit.get("url", ""), + "summary": hit.get("story_text") or "No summary", + "source": "Hacker News", + "score": hit.get("points", 0), + "fetched_at": datetime.now().isoformat() + }) + + # Process Reddit source + elif source_name == "Reddit" and source_data.get("data"): + for post in source_data["data"].get("children", []): + post_data = post.get("data", {}) + processed_articles.append({ + "title": post_data.get("title", "No title"), + "url": post_data.get("url", ""), + "summary": post_data.get("selftext", "")[:200], + "source": "Reddit", + "score": post_data.get("score", 0), + "fetched_at": datetime.now().isoformat() + }) + +# Sort by score descending +processed_articles.sort(key=lambda x: x["score"], reverse=True) + +# Return as n8n items +return [{"json": article} for article in processed_articles] +``` + +### Key Techniques + +- Process multiple data sources in one loop +- Normalize different data structures +- Use datetime for timestamps +- Sort by criteria +- Return properly formatted items + +--- + +## Pattern 2: Regex-Based Filtering + +**Use case**: Filter items based on pattern matching in text fields. + +**Scenario**: Filter support tickets by priority keywords. + +### Implementation + +```python +import re + +all_items = _input.all() +priority_tickets = [] + +# High priority keywords pattern +high_priority_pattern = re.compile( + r'\b(urgent|critical|emergency|asap|down|outage|broken)\b', + re.IGNORECASE +) + +for item in all_items: + ticket = item["json"] + + # Check subject and description + subject = ticket.get("subject", "") + description = ticket.get("description", "") + combined_text = f"{subject} {description}" + + # Find matches + matches = high_priority_pattern.findall(combined_text) + + if matches: + priority_tickets.append({ + "json": { + **ticket, + "priority": "high", + "matched_keywords": list(set(matches)), + "keyword_count": len(matches) + } + }) + else: + priority_tickets.append({ + "json": { + **ticket, + "priority": "normal", + "matched_keywords": [], + "keyword_count": 0 + } + }) + +# Sort by keyword count (most urgent first) +priority_tickets.sort(key=lambda x: x["json"]["keyword_count"], reverse=True) + +return priority_tickets +``` + +### Key Techniques + +- Use re.compile() for reusable patterns +- re.IGNORECASE for case-insensitive matching +- Combine multiple text fields for searching +- Extract and deduplicate matches +- Sort by priority indicators + +--- + +## Pattern 3: Markdown to Structured Data + +**Use case**: Parse markdown text into structured data. + +**Scenario**: Extract tasks from markdown checklist. + +### Implementation + +```python +import re + +markdown_text = _input.first()["json"]["body"].get("markdown", "") + +# Parse markdown checklist +tasks = [] +lines = markdown_text.split("\n") + +for line in lines: + # Match: - [ ] Task or - [x] Task + match = re.match(r'^\s*-\s*\[([ x])\]\s*(.+)$', line, re.IGNORECASE) + + if match: + checked = match.group(1).lower() == 'x' + task_text = match.group(2).strip() + + # Extract priority if present (e.g., [P1], [HIGH]) + priority_match = re.search(r'\[(P\d|HIGH|MEDIUM|LOW)\]', task_text, re.IGNORECASE) + priority = priority_match.group(1).upper() if priority_match else "NORMAL" + + # Remove priority tag from text + clean_text = re.sub(r'\[(P\d|HIGH|MEDIUM|LOW)\]', '', task_text, flags=re.IGNORECASE).strip() + + tasks.append({ + "text": clean_text, + "completed": checked, + "priority": priority, + "original_line": line.strip() + }) + +return [{ + "json": { + "tasks": tasks, + "total": len(tasks), + "completed": sum(1 for t in tasks if t["completed"]), + "pending": sum(1 for t in tasks if not t["completed"]) + } +}] +``` + +### Key Techniques + +- Line-by-line parsing +- Multiple regex patterns for extraction +- Extract metadata from text +- Calculate summary statistics +- Return structured data + +--- + +## Pattern 4: JSON Object Comparison + +**Use case**: Compare two JSON objects to find differences. + +**Scenario**: Compare old and new user profile data. + +### Implementation + +```python +import json + +all_items = _input.all() + +# Assume first item is old data, second is new data +old_data = all_items[0]["json"] if len(all_items) > 0 else {} +new_data = all_items[1]["json"] if len(all_items) > 1 else {} + +changes = { + "added": {}, + "removed": {}, + "modified": {}, + "unchanged": {} +} + +# Find all unique keys +all_keys = set(old_data.keys()) | set(new_data.keys()) + +for key in all_keys: + old_value = old_data.get(key) + new_value = new_data.get(key) + + if key not in old_data: + # Added field + changes["added"][key] = new_value + elif key not in new_data: + # Removed field + changes["removed"][key] = old_value + elif old_value != new_value: + # Modified field + changes["modified"][key] = { + "old": old_value, + "new": new_value + } + else: + # Unchanged field + changes["unchanged"][key] = old_value + +return [{ + "json": { + "changes": changes, + "summary": { + "added_count": len(changes["added"]), + "removed_count": len(changes["removed"]), + "modified_count": len(changes["modified"]), + "unchanged_count": len(changes["unchanged"]), + "has_changes": len(changes["added"]) > 0 or len(changes["removed"]) > 0 or len(changes["modified"]) > 0 + } + } +}] +``` + +### Key Techniques + +- Set operations for key comparison +- Dictionary .get() for safe access +- Categorize changes by type +- Create summary statistics +- Return detailed comparison + +--- + +## Pattern 5: CRM Data Transformation + +**Use case**: Transform CRM data to standard format. + +**Scenario**: Normalize data from different CRM systems. + +### Implementation + +```python +from datetime import datetime +import re + +all_items = _input.all() +normalized_contacts = [] + +for item in all_items: + raw_contact = item["json"] + source = raw_contact.get("source", "unknown") + + # Normalize email + email = raw_contact.get("email", "").lower().strip() + + # Normalize phone (remove non-digits) + phone_raw = raw_contact.get("phone", "") + phone = re.sub(r'\D', '', phone_raw) + + # Parse name + if "full_name" in raw_contact: + name_parts = raw_contact["full_name"].split(" ", 1) + first_name = name_parts[0] if len(name_parts) > 0 else "" + last_name = name_parts[1] if len(name_parts) > 1 else "" + else: + first_name = raw_contact.get("first_name", "") + last_name = raw_contact.get("last_name", "") + + # Normalize status + status_raw = raw_contact.get("status", "").lower() + status = "active" if status_raw in ["active", "enabled", "true", "1"] else "inactive" + + # Create normalized contact + normalized_contacts.append({ + "json": { + "id": raw_contact.get("id", ""), + "first_name": first_name.strip(), + "last_name": last_name.strip(), + "full_name": f"{first_name} {last_name}".strip(), + "email": email, + "phone": phone, + "status": status, + "source": source, + "normalized_at": datetime.now().isoformat(), + "original_data": raw_contact + } + }) + +return normalized_contacts +``` + +### Key Techniques + +- Multiple field name variations handling +- String cleaning and normalization +- Regex for phone number cleaning +- Name parsing logic +- Status normalization +- Preserve original data + +--- + +## Pattern 6: Release Notes Processing + +**Use case**: Parse release notes and categorize changes. + +**Scenario**: Extract features, fixes, and breaking changes from release notes. + +### Implementation + +```python +import re + +release_notes = _input.first()["json"]["body"].get("notes", "") + +categories = { + "features": [], + "fixes": [], + "breaking": [], + "other": [] +} + +# Split into lines +lines = release_notes.split("\n") + +for line in lines: + line = line.strip() + + # Skip empty lines and headers + if not line or line.startswith("#"): + continue + + # Remove bullet points + clean_line = re.sub(r'^[\*\-\+]\s*', '', line) + + # Categorize + if re.search(r'\b(feature|add|new)\b', clean_line, re.IGNORECASE): + categories["features"].append(clean_line) + elif re.search(r'\b(fix|bug|patch|resolve)\b', clean_line, re.IGNORECASE): + categories["fixes"].append(clean_line) + elif re.search(r'\b(breaking|deprecated|remove)\b', clean_line, re.IGNORECASE): + categories["breaking"].append(clean_line) + else: + categories["other"].append(clean_line) + +return [{ + "json": { + "categories": categories, + "summary": { + "features": len(categories["features"]), + "fixes": len(categories["fixes"]), + "breaking": len(categories["breaking"]), + "other": len(categories["other"]), + "total": sum(len(v) for v in categories.values()) + } + } +}] +``` + +### Key Techniques + +- Line-by-line parsing +- Pattern-based categorization +- Bullet point removal +- Skip headers and empty lines +- Summary statistics + +--- + +## Pattern 7: Array Transformation + +**Use case**: Reshape arrays and extract specific fields. + +**Scenario**: Transform user data array to extract specific fields. + +### Implementation + +```python +all_items = _input.all() + +# Extract and transform +transformed = [] + +for item in all_items: + user = item["json"] + + # Extract nested fields + profile = user.get("profile", {}) + settings = user.get("settings", {}) + + transformed.append({ + "json": { + "user_id": user.get("id"), + "email": user.get("email"), + "name": profile.get("name", "Unknown"), + "avatar": profile.get("avatar_url"), + "bio": profile.get("bio", "")[:100], # Truncate to 100 chars + "notifications_enabled": settings.get("notifications", True), + "theme": settings.get("theme", "light"), + "created_at": user.get("created_at"), + "last_login": user.get("last_login_at") + } + }) + +return transformed +``` + +### Key Techniques + +- Field extraction from nested objects +- Default values with .get() +- String truncation +- Flattening nested structures + +--- + +## Pattern 8: Dictionary Lookup + +**Use case**: Create lookup dictionary for fast data access. + +**Scenario**: Look up user details by ID. + +### Implementation + +```python +all_items = _input.all() + +# Build lookup dictionary +users_by_id = {} + +for item in all_items: + user = item["json"] + user_id = user.get("id") + + if user_id: + users_by_id[user_id] = { + "name": user.get("name"), + "email": user.get("email"), + "status": user.get("status") + } + +# Example: Look up specific users +lookup_ids = [1, 3, 5] +looked_up = [] + +for user_id in lookup_ids: + if user_id in users_by_id: + looked_up.append({ + "json": { + "id": user_id, + **users_by_id[user_id], + "found": True + } + }) + else: + looked_up.append({ + "json": { + "id": user_id, + "found": False + } + }) + +return looked_up +``` + +### Key Techniques + +- Dictionary comprehension alternative +- O(1) lookup time +- Handle missing keys gracefully +- Preserve lookup order + +--- + +## Pattern 9: Top N Filtering + +**Use case**: Get top items by score or value. + +**Scenario**: Get top 10 products by sales. + +### Implementation + +```python +all_items = _input.all() + +# Extract products with sales +products = [] + +for item in all_items: + product = item["json"] + products.append({ + "id": product.get("id"), + "name": product.get("name"), + "sales": product.get("sales", 0), + "revenue": product.get("revenue", 0.0), + "category": product.get("category") + }) + +# Sort by sales descending +products.sort(key=lambda p: p["sales"], reverse=True) + +# Get top 10 +top_10 = products[:10] + +return [ + { + "json": { + **product, + "rank": index + 1 + } + } + for index, product in enumerate(top_10) +] +``` + +### Key Techniques + +- List sorting with custom key +- Slicing for top N +- Add ranking information +- Enumerate for index + +--- + +## Pattern 10: String Aggregation + +**Use case**: Aggregate strings with formatting. + +**Scenario**: Create summary text from multiple items. + +### Implementation + +```python +all_items = _input.all() + +# Collect messages +messages = [] + +for item in all_items: + data = item["json"] + + user = data.get("user", "Unknown") + message = data.get("message", "") + timestamp = data.get("timestamp", "") + + # Format each message + formatted = f"[{timestamp}] {user}: {message}" + messages.append(formatted) + +# Join with newlines +summary = "\n".join(messages) + +# Create statistics +total_length = sum(len(msg) for msg in messages) +average_length = total_length / len(messages) if messages else 0 + +return [{ + "json": { + "summary": summary, + "message_count": len(messages), + "total_characters": total_length, + "average_length": round(average_length, 2) + } +}] +``` + +### Key Techniques + +- String formatting with f-strings +- Join lists with separator +- Calculate string statistics +- Handle empty lists + +--- + +## Pattern Comparison: Python vs JavaScript + +### Data Access + +```python +# Python +all_items = _input.all() +first_item = _input.first() +current = _input.item +webhook_data = _json["body"] + +# JavaScript +const allItems = $input.all(); +const firstItem = $input.first(); +const current = $input.item; +const webhookData = $json.body; +``` + +### Dictionary/Object Access + +```python +# Python - Dictionary key access +name = user["name"] # May raise KeyError +name = user.get("name", "?") # Safe with default + +# JavaScript - Object property access +const name = user.name; // May be undefined +const name = user.name || "?"; // Safe with default +``` + +### Array Operations + +```python +# Python - List comprehension +filtered = [item for item in items if item["active"]] + +# JavaScript - Array methods +const filtered = items.filter(item => item.active); +``` + +### Sorting + +```python +# Python +items.sort(key=lambda x: x["score"], reverse=True) + +# JavaScript +items.sort((a, b) => b.score - a.score); +``` + +--- + +## Best Practices + +### 1. Use .get() for Safe Access + +```python +# ✅ SAFE: Use .get() with defaults +name = user.get("name", "Unknown") +email = user.get("email", "no-email@example.com") + +# ❌ RISKY: Direct key access +name = user["name"] # KeyError if missing! +``` + +### 2. Handle Empty Lists + +```python +# ✅ SAFE: Check before processing +items = _input.all() +if items: + first = items[0] +else: + return [{"json": {"error": "No items"}}] + +# ❌ RISKY: Assume items exist +first = items[0] # IndexError if empty! +``` + +### 3. Use List Comprehensions + +```python +# ✅ PYTHONIC: List comprehension +active = [item for item in items if item["json"].get("active")] + +# ❌ VERBOSE: Traditional loop +active = [] +for item in items: + if item["json"].get("active"): + active.append(item) +``` + +### 4. Return Proper Format + +```python +# ✅ CORRECT: Array of objects with "json" key +return [{"json": {"field": "value"}}] + +# ❌ WRONG: Just the data +return {"field": "value"} + +# ❌ WRONG: Array without "json" wrapper +return [{"field": "value"}] +``` + +### 5. Use Standard Library + +```python +# ✅ GOOD: Use standard library +import statistics +average = statistics.mean(numbers) + +# ✅ ALSO GOOD: Built-in functions +average = sum(numbers) / len(numbers) if numbers else 0 + +# ❌ CAN'T DO: External libraries +import numpy as np # ModuleNotFoundError! +``` + +--- + +## When to Use Each Pattern + +| Pattern | When to Use | +|---------|-------------| +| Multi-Source Aggregation | Combining data from different nodes/sources | +| Regex Filtering | Text pattern matching, validation, extraction | +| Markdown Parsing | Processing formatted text into structured data | +| JSON Comparison | Detecting changes between objects | +| CRM Transformation | Normalizing data from different systems | +| Release Notes | Categorizing text by keywords | +| Array Transformation | Reshaping data, extracting fields | +| Dictionary Lookup | Fast ID-based lookups | +| Top N Filtering | Getting best/worst items by criteria | +| String Aggregation | Creating formatted text summaries | + +--- + +## Summary + +**Key Takeaways**: +- Use `.get()` for safe dictionary access +- List comprehensions are pythonic and efficient +- Handle empty lists/None values +- Use standard library (json, datetime, re) +- Return proper n8n format: `[{"json": {...}}]` + +**Remember**: +- JavaScript is recommended for 95% of use cases +- Python has NO external libraries +- Use n8n nodes for complex operations +- Code node is for data transformation, not API calls + +**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 +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes diff --git a/skills/n8n-code-python/DATA_ACCESS.md b/skills/n8n-code-python/DATA_ACCESS.md new file mode 100644 index 0000000..d87ed7a --- /dev/null +++ b/skills/n8n-code-python/DATA_ACCESS.md @@ -0,0 +1,702 @@ +# Data Access Patterns - Python Code Node + +Complete guide to accessing data in n8n Code nodes using Python. + +--- + +## Overview + +In n8n Python Code nodes, you access data using **underscore-prefixed** variables: `_input`, `_json`, `_node`. + +**Data Access Priority** (by common usage): +1. **`_input.all()`** - Most common - Batch operations, aggregations +2. **`_input.first()`** - Very common - Single item operations +3. **`_input.item`** - Common - Each Item mode only +4. **`_node["NodeName"]["json"]`** - Specific node references +5. **`_json`** - Direct current item (use `_input` instead) + +**Python vs JavaScript**: +| JavaScript | Python (Beta) | Python (Native) | +|------------|---------------|-----------------| +| `$input.all()` | `_input.all()` | `_items` | +| `$input.first()` | `_input.first()` | `_items[0]` | +| `$input.item` | `_input.item` | `_item` | +| `$json` | `_json` | `_item["json"]` | +| `$node["Name"]` | `_node["Name"]` | Not available | + +--- + +## Pattern 1: _input.all() - Process All Items + +**Usage**: Most common pattern for batch processing + +**When to use:** +- Processing multiple records +- Aggregating data (sum, count, average) +- Filtering lists +- Transforming datasets + +### Basic Usage + +```python +# Get all items from previous node +all_items = _input.all() + +# all_items is a list of dictionaries like: +# [ +# {"json": {"id": 1, "name": "Alice"}}, +# {"json": {"id": 2, "name": "Bob"}} +# ] + +print(f"Received {len(all_items)} items") + +return all_items +``` + +### Example 1: Filter Active Items + +```python +all_items = _input.all() + +# Filter only active items +active_items = [ + item for item in all_items + if item["json"].get("status") == "active" +] + +return active_items +``` + +### Example 2: Transform All Items + +```python +all_items = _input.all() + +# Transform to new structure +transformed = [] +for item in all_items: + transformed.append({ + "json": { + "id": item["json"].get("id"), + "full_name": f"{item['json'].get('first_name', '')} {item['json'].get('last_name', '')}", + "email": item["json"].get("email"), + "processed_at": datetime.now().isoformat() + } + }) + +return transformed +``` + +### Example 3: Aggregate Data + +```python +all_items = _input.all() + +# Calculate total +total = sum(item["json"].get("amount", 0) for item in all_items) + +return [{ + "json": { + "total": total, + "count": len(all_items), + "average": total / len(all_items) if all_items else 0 + } +}] +``` + +### Example 4: Sort and Limit + +```python +all_items = _input.all() + +# Get top 5 by score +sorted_items = sorted( + all_items, + key=lambda item: item["json"].get("score", 0), + reverse=True +) +top_five = sorted_items[:5] + +return [{"json": item["json"]} for item in top_five] +``` + +### Example 5: Group By Category + +```python +all_items = _input.all() + +# Group items by category +grouped = {} +for item in all_items: + category = item["json"].get("category", "Uncategorized") + + if category not in grouped: + grouped[category] = [] + + grouped[category].append(item["json"]) + +# Convert to list format +return [ + { + "json": { + "category": category, + "items": items, + "count": len(items) + } + } + for category, items in grouped.items() +] +``` + +### Example 6: Deduplicate by ID + +```python +all_items = _input.all() + +# Remove duplicates by ID +seen = set() +unique = [] + +for item in all_items: + item_id = item["json"].get("id") + + if item_id and item_id not in seen: + seen.add(item_id) + unique.append(item) + +return unique +``` + +--- + +## Pattern 2: _input.first() - Get First Item + +**Usage**: Very common for single-item operations + +**When to use:** +- Previous node returns single object +- Working with API responses +- Getting initial/first data point + +### Basic Usage + +```python +# Get first item from previous node +first_item = _input.first() + +# Access the JSON data +data = first_item["json"] + +print(f"First item: {data}") + +return [{"json": data}] +``` + +### Example 1: Process Single API Response + +```python +# Get API response (typically single object) +response = _input.first()["json"] + +# Extract what you need +return [{ + "json": { + "user_id": response.get("data", {}).get("user", {}).get("id"), + "user_name": response.get("data", {}).get("user", {}).get("name"), + "status": response.get("status"), + "fetched_at": datetime.now().isoformat() + } +}] +``` + +### Example 2: Transform Single Object + +```python +data = _input.first()["json"] + +# Transform structure +return [{ + "json": { + "id": data.get("id"), + "contact": { + "email": data.get("email"), + "phone": data.get("phone") + }, + "address": { + "street": data.get("street"), + "city": data.get("city"), + "zip": data.get("zip") + } + } +}] +``` + +### Example 3: Validate Single Item + +```python +item = _input.first()["json"] + +# Validation logic +is_valid = bool(item.get("email") and "@" in item.get("email", "")) + +return [{ + "json": { + **item, + "valid": is_valid, + "validated_at": datetime.now().isoformat() + } +}] +``` + +### Example 4: Extract Nested Data + +```python +response = _input.first()["json"] + +# Navigate nested structure +users = response.get("data", {}).get("users", []) + +return [ + { + "json": { + "id": user.get("id"), + "name": user.get("profile", {}).get("name", "Unknown"), + "email": user.get("contact", {}).get("email", "no-email") + } + } + for user in users +] +``` + +--- + +## Pattern 3: _input.item - Current Item (Each Item Mode) + +**Usage**: Common in "Run Once for Each Item" mode + +**When to use:** +- Mode is set to "Run Once for Each Item" +- Need to process items independently +- Per-item API calls or validations + +**IMPORTANT**: Only use in "Each Item" mode. Will be undefined in "All Items" mode. + +### Basic Usage + +```python +# In "Run Once for Each Item" mode +current_item = _input.item +data = current_item["json"] + +print(f"Processing item: {data.get('id')}") + +return [{ + "json": { + **data, + "processed": True + } +}] +``` + +### Example 1: Add Processing Metadata + +```python +item = _input.item + +return [{ + "json": { + **item["json"], + "processed": True, + "processed_at": datetime.now().isoformat(), + "processing_duration": random.random() * 1000 # Simulated + } +}] +``` + +### Example 2: Per-Item Validation + +```python +item = _input.item +data = item["json"] + +# Validate this specific item +errors = [] + +if not data.get("email"): + errors.append("Email required") +if not data.get("name"): + errors.append("Name required") +if data.get("age") and data["age"] < 18: + errors.append("Must be 18+") + +return [{ + "json": { + **data, + "valid": len(errors) == 0, + "errors": errors if errors else None + } +}] +``` + +### Example 3: Conditional Processing + +```python +item = _input.item +data = item["json"] + +# Process based on item type +if data.get("type") == "premium": + return [{ + "json": { + **data, + "discount": 0.20, + "tier": "premium" + } + }] +else: + return [{ + "json": { + **data, + "discount": 0.05, + "tier": "standard" + } + }] +``` + +--- + +## Pattern 4: _node - Reference Other Nodes + +**Usage**: Less common, but powerful for specific scenarios + +**When to use:** +- Need data from specific named node +- Combining data from multiple nodes + +### Basic Usage + +```python +# Get output from specific node +webhook_data = _node["Webhook"]["json"] +api_data = _node["HTTP Request"]["json"] + +return [{ + "json": { + "from_webhook": webhook_data, + "from_api": api_data + } +}] +``` + +### Example 1: Combine Multiple Sources + +```python +# Reference multiple nodes +webhook = _node["Webhook"]["json"] +database = _node["Postgres"]["json"] +api = _node["HTTP Request"]["json"] + +return [{ + "json": { + "combined": { + "webhook": webhook.get("body", {}), + "db_records": len(database) if isinstance(database, list) else 1, + "api_response": api.get("status") + }, + "processed_at": datetime.now().isoformat() + } +}] +``` + +### Example 2: Compare Across Nodes + +```python +old_data = _node["Get Old Data"]["json"] +new_data = _node["Get New Data"]["json"] + +# Simple comparison +changes = { + "added": [n for n in new_data if n.get("id") not in [o.get("id") for o in old_data]], + "removed": [o for o in old_data if o.get("id") not in [n.get("id") for n in new_data]] +} + +return [{ + "json": { + "changes": changes, + "summary": { + "added": len(changes["added"]), + "removed": len(changes["removed"]) + } + } +}] +``` + +--- + +## Critical: Webhook Data Structure + +**MOST COMMON MISTAKE**: Forgetting webhook data is nested under `["body"]` + +### The Problem + +Webhook node wraps all incoming data under a `"body"` property. + +### Structure + +```python +# Webhook node output structure: +{ + "headers": { + "content-type": "application/json", + "user-agent": "..." + }, + "params": {}, + "query": {}, + "body": { + # ← YOUR DATA IS HERE + "name": "Alice", + "email": "alice@example.com", + "message": "Hello!" + } +} +``` + +### Wrong vs Right + +```python +# ❌ WRONG: Trying to access directly +name = _json["name"] # KeyError! +email = _json["email"] # KeyError! + +# ✅ CORRECT: Access via ["body"] +name = _json["body"]["name"] # "Alice" +email = _json["body"]["email"] # "alice@example.com" + +# ✅ SAFER: Use .get() for safe access +webhook_data = _json.get("body", {}) +name = webhook_data.get("name") # None if missing +email = webhook_data.get("email", "no-email") # Default value +``` + +### Example: Full Webhook Processing + +```python +# Get webhook data from previous node +webhook_output = _input.first()["json"] + +# Access the actual payload +payload = webhook_output.get("body", {}) + +# Access headers if needed +content_type = webhook_output.get("headers", {}).get("content-type") + +# Access query parameters if needed +api_key = webhook_output.get("query", {}).get("api_key") + +# Process the actual data +return [{ + "json": { + # Data from webhook body + "user_name": payload.get("name"), + "user_email": payload.get("email"), + "message": payload.get("message"), + + # Metadata + "received_at": datetime.now().isoformat(), + "content_type": content_type, + "authenticated": bool(api_key) + } +}] +``` + +### POST Data, Query Params, and Headers + +```python +webhook = _input.first()["json"] + +return [{ + "json": { + # POST body data + "form_data": webhook.get("body", {}), + + # Query parameters (?key=value) + "query_params": webhook.get("query", {}), + + # HTTP headers + "user_agent": webhook.get("headers", {}).get("user-agent"), + "content_type": webhook.get("headers", {}).get("content-type"), + + # Request metadata + "method": webhook.get("method"), # POST, GET, etc. + "url": webhook.get("url") + } +}] +``` + +--- + +## Choosing the Right Pattern + +### Decision Tree + +``` +Do you need ALL items from previous node? +├─ YES → Use _input.all() +│ +└─ NO → Do you need just the FIRST item? + ├─ YES → Use _input.first() + │ + └─ NO → Are you in "Each Item" mode? + ├─ YES → Use _input.item + │ + └─ NO → Do you need specific node data? + ├─ YES → Use _node["NodeName"] + └─ NO → Use _input.first() (default) +``` + +### Quick Reference Table + +| Scenario | Use This | Example | +|----------|----------|---------| +| Sum all amounts | `_input.all()` | `sum(i["json"].get("amount", 0) for i in items)` | +| Get API response | `_input.first()` | `_input.first()["json"].get("data")` | +| Process each independently | `_input.item` | `_input.item["json"]` (Each Item mode) | +| Combine two nodes | `_node["Name"]` | `_node["API"]["json"]` | +| Filter list | `_input.all()` | `[i for i in items if i["json"].get("active")]` | +| Transform single object | `_input.first()` | `{**_input.first()["json"], "new": True}` | +| Webhook data | `_input.first()` | `_input.first()["json"]["body"]` | + +--- + +## Common Mistakes + +### Mistake 1: Using _json Without Context + +```python +# ❌ RISKY: _json is ambiguous +value = _json["field"] + +# ✅ CLEAR: Be explicit +value = _input.first()["json"]["field"] +``` + +### Mistake 2: Forgetting ["json"] Property + +```python +# ❌ WRONG: Trying to access fields on item dictionary +items = _input.all() +names = [item["name"] for item in items] # KeyError! + +# ✅ CORRECT: Access via ["json"] +names = [item["json"]["name"] for item in items] +``` + +### Mistake 3: Using _input.item in All Items Mode + +```python +# ❌ WRONG: _input.item is None in "All Items" mode +data = _input.item["json"] # AttributeError! + +# ✅ CORRECT: Use appropriate method +data = _input.first()["json"] # Or _input.all() +``` + +### Mistake 4: Not Handling Empty Lists + +```python +# ❌ WRONG: Crashes if no items +first = _input.all()[0]["json"] # IndexError! + +# ✅ CORRECT: Check length first +items = _input.all() +if items: + first = items[0]["json"] +else: + return [] + +# ✅ ALSO CORRECT: Use _input.first() +first = _input.first()["json"] # Built-in safety +``` + +### Mistake 5: Direct Dictionary Access (KeyError) + +```python +# ❌ RISKY: Crashes if key missing +value = item["json"]["field"] # KeyError! + +# ✅ SAFE: Use .get() +value = item["json"].get("field", "default") +``` + +--- + +## Advanced Patterns + +### Pattern: Safe Nested Access + +```python +# Deep nested access with .get() +value = ( + _input.first()["json"] + .get("level1", {}) + .get("level2", {}) + .get("level3", "default") +) +``` + +### Pattern: List Comprehension with Filtering + +```python +items = _input.all() + +# Filter and transform in one step +result = [ + { + "json": { + "id": item["json"]["id"], + "name": item["json"]["name"].upper() + } + } + for item in items + if item["json"].get("active") and item["json"].get("verified") +] + +return result +``` + +### Pattern: Dictionary Comprehension + +```python +items = _input.all() + +# Create lookup dictionary +lookup = { + item["json"]["id"]: item["json"] + for item in items + if "id" in item["json"] +} + +return [{"json": lookup}] +``` + +--- + +## Summary + +**Most Common Patterns**: +1. `_input.all()` - Process multiple items, batch operations +2. `_input.first()` - Single item, API responses +3. `_input.item` - Each Item mode processing + +**Critical Rule**: +- Webhook data is under `["body"]` property + +**Best Practice**: +- Use `.get()` for dictionary access to avoid KeyError +- Always check for empty lists +- Be explicit: Use `_input.first()["json"]["field"]` instead of `_json["field"]` + +**See Also**: +- [SKILL.md](SKILL.md) - Overview and quick start +- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Python-specific patterns +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes diff --git a/skills/n8n-code-python/ERROR_PATTERNS.md b/skills/n8n-code-python/ERROR_PATTERNS.md new file mode 100644 index 0000000..e9e6b8c --- /dev/null +++ b/skills/n8n-code-python/ERROR_PATTERNS.md @@ -0,0 +1,601 @@ +# 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 diff --git a/skills/n8n-code-python/README.md b/skills/n8n-code-python/README.md new file mode 100644 index 0000000..623c998 --- /dev/null +++ b/skills/n8n-code-python/README.md @@ -0,0 +1,386 @@ +# n8n Code Python Skill + +Expert guidance for writing Python code in n8n Code nodes. + +--- + +## ⚠️ Important: JavaScript First + +**Use JavaScript for 95% of use cases.** + +Python in n8n has **NO external libraries** (no requests, pandas, numpy). + +**When to use Python**: +- You have complex Python-specific logic +- You need Python's standard library features +- You're more comfortable with Python than JavaScript + +**When to use JavaScript** (recommended): +- HTTP requests ($helpers.httpRequest available) +- Date/time operations (Luxon library included) +- Most data transformations +- When in doubt + +--- + +## What This Skill Teaches + +### Core Concepts + +1. **Critical Limitation**: No external libraries +2. **Data Access**: `_input.all()`, `_input.first()`, `_input.item` +3. **Webhook Gotcha**: Data is under `_json["body"]` +4. **Return Format**: Must return `[{"json": {...}}]` +5. **Standard Library**: json, datetime, re, base64, hashlib, etc. + +### Top 5 Error Prevention + +This skill emphasizes **error prevention**: + +1. **ModuleNotFoundError** (trying to import external libraries) +2. **Empty code / missing return** +3. **KeyError** (dictionary access without .get()) +4. **IndexError** (list access without bounds checking) +5. **Incorrect return format** + +These 5 errors are the most common in Python Code nodes. + +--- + +## Skill Activation + +This skill activates when you: +- Write Python in Code nodes +- Ask about Python limitations +- Need to know available standard library +- Troubleshoot Python Code node errors +- Work with Python data structures + +**Example queries**: +- "Can I use pandas in Python Code node?" +- "How do I access webhook data in Python?" +- "What Python libraries are available?" +- "Write Python code to process JSON" +- "Why is requests module not found?" + +--- + +## File Structure + +### SKILL.md (719 lines) +**Quick start** and overview +- When to use Python vs JavaScript +- Critical limitation (no external libraries) +- Mode selection (All Items vs Each Item) +- Data access overview +- Return format requirements +- Standard library overview + +### DATA_ACCESS.md (703 lines) +**Complete data access patterns** +- `_input.all()` - Process all items +- `_input.first()` - Get first item +- `_input.item` - Current item (Each Item mode) +- `_node["Name"]` - Reference other nodes +- Webhook body structure (critical gotcha!) +- Pattern selection guide + +### STANDARD_LIBRARY.md (850 lines) +**Available Python modules** +- json - JSON parsing +- datetime - Date/time operations +- re - Regular expressions +- base64 - Encoding/decoding +- hashlib - Hashing +- urllib.parse - URL operations +- math, random, statistics +- What's NOT available (requests, pandas, numpy) +- Workarounds for missing libraries + +### COMMON_PATTERNS.md (895 lines) +**10 production-tested patterns** +1. Multi-source data aggregation +2. Regex-based filtering +3. Markdown to structured data +4. JSON object comparison +5. CRM data transformation +6. Release notes processing +7. Array transformation +8. Dictionary lookup +9. Top N filtering +10. String aggregation + +### ERROR_PATTERNS.md (730 lines) +**Top 5 errors with solutions** +1. ModuleNotFoundError (external libraries) +2. Empty code / missing return +3. KeyError (dictionary access) +4. IndexError (list access) +5. Incorrect return format +- Error prevention checklist +- Quick fix reference +- Testing patterns + +--- + +## Integration with Other Skills + +This skill works with: + +### n8n Expression Syntax +- Python uses code syntax, not {{}} expressions +- Data access patterns differ ($ vs _) + +### n8n MCP Tools Expert +- Use MCP tools to validate Code node configurations +- Check node setup with `get_node_essentials` + +### n8n Workflow Patterns +- Code nodes fit into larger workflow patterns +- Often used after HTTP Request or Webhook nodes + +### n8n Code JavaScript +- Compare Python vs JavaScript approaches +- Understand when to use which language +- JavaScript recommended for 95% of cases + +### n8n Node Configuration +- Configure Code node mode (All Items vs Each Item) +- Set up proper connections + +--- + +## Success Metrics + +After using this skill, you should be able to: + +- [ ] **Know the limitation**: Python has NO external libraries +- [ ] **Choose language**: JavaScript for 95% of cases, Python when needed +- [ ] **Access data**: Use `_input.all()`, `_input.first()`, `_input.item` +- [ ] **Handle webhooks**: Access data via `_json["body"]` +- [ ] **Return properly**: Always return `[{"json": {...}}]` +- [ ] **Avoid KeyError**: Use `.get()` for dictionary access +- [ ] **Use standard library**: Know what's available (json, datetime, re, etc.) +- [ ] **Prevent errors**: Avoid top 5 common errors +- [ ] **Choose alternatives**: Use n8n nodes when libraries needed +- [ ] **Write production code**: Use proven patterns + +--- + +## Quick Reference + +### Data Access +```python +all_items = _input.all() +first_item = _input.first() +current_item = _input.item # Each Item mode only +other_node = _node["NodeName"] +``` + +### Webhook Data +```python +webhook = _input.first()["json"] +body = webhook.get("body", {}) +name = body.get("name") +``` + +### Safe Dictionary Access +```python +# ✅ Use .get() with defaults +value = data.get("field", "default") + +# ❌ Risky - may raise KeyError +value = data["field"] +``` + +### Return Format +```python +# ✅ Correct format +return [{"json": {"result": "success"}}] + +# ❌ Wrong - plain dict +return {"result": "success"} +``` + +### Standard Library +```python +# ✅ Available +import json +import datetime +import re +import base64 +import hashlib + +# ❌ NOT available +import requests # ModuleNotFoundError! +import pandas # ModuleNotFoundError! +import numpy # ModuleNotFoundError! +``` + +--- + +## Common Use Cases + +### Use Case 1: Process Webhook Data +```python +webhook = _input.first()["json"] +body = webhook.get("body", {}) + +return [{ + "json": { + "name": body.get("name"), + "email": body.get("email"), + "processed": True + } +}] +``` + +### Use Case 2: Filter and Transform +```python +all_items = _input.all() + +active = [ + {"json": {**item["json"], "filtered": True}} + for item in all_items + if item["json"].get("status") == "active" +] + +return active +``` + +### Use Case 3: Aggregate Statistics +```python +import statistics + +all_items = _input.all() +amounts = [item["json"].get("amount", 0) for item in all_items] + +return [{ + "json": { + "total": sum(amounts), + "average": statistics.mean(amounts) if amounts else 0, + "count": len(amounts) + } +}] +``` + +### Use Case 4: Parse JSON String +```python +import json + +data = _input.first()["json"]["body"] +json_string = data.get("payload", "{}") + +try: + parsed = json.loads(json_string) + return [{"json": parsed}] +except json.JSONDecodeError: + return [{"json": {"error": "Invalid JSON"}}] +``` + +--- + +## Limitations and Workarounds + +### Limitation 1: No HTTP Requests Library +**Problem**: No `requests` library +**Workaround**: Use HTTP Request node or JavaScript + +### Limitation 2: No Data Analysis Library +**Problem**: No `pandas` or `numpy` +**Workaround**: Use list comprehensions and standard library + +### Limitation 3: No Database Drivers +**Problem**: No `psycopg2`, `pymongo`, etc. +**Workaround**: Use n8n database nodes (Postgres, MySQL, MongoDB) + +### Limitation 4: No Web Scraping +**Problem**: No `beautifulsoup4` or `selenium` +**Workaround**: Use HTML Extract node + +--- + +## Best Practices + +1. **Use JavaScript for most cases** (95% recommendation) +2. **Use .get() for dictionaries** (avoid KeyError) +3. **Check lengths before indexing** (avoid IndexError) +4. **Always return proper format**: `[{"json": {...}}]` +5. **Access webhook data via ["body"]** +6. **Use standard library only** (no external imports) +7. **Handle empty input** (check `if items:`) +8. **Test both modes** (All Items and Each Item) + +--- + +## When Python is the Right Choice + +Use Python when: +- Complex text processing (re module) +- Mathematical calculations (math, statistics) +- Date/time manipulation (datetime) +- Cryptographic operations (hashlib) +- You have existing Python logic to reuse +- Team is more comfortable with Python + +Use JavaScript instead when: +- Making HTTP requests +- Working with dates (Luxon included) +- Most data transformations +- When in doubt + +--- + +## Learning Path + +**Beginner**: +1. Read SKILL.md - Understand the limitation +2. Try DATA_ACCESS.md examples - Learn `_input` patterns +3. Practice safe dictionary access with `.get()` + +**Intermediate**: +4. Study STANDARD_LIBRARY.md - Know what's available +5. Try COMMON_PATTERNS.md examples - Use proven patterns +6. Learn ERROR_PATTERNS.md - Avoid common mistakes + +**Advanced**: +7. Combine multiple patterns +8. Use standard library effectively +9. Know when to switch to JavaScript +10. Write production-ready code + +--- + +## Support + +**Questions?** +- Check ERROR_PATTERNS.md for common issues +- Review COMMON_PATTERNS.md for examples +- Consider using JavaScript instead + +**Related Skills**: +- n8n Code JavaScript - Alternative (recommended for 95% of cases) +- n8n Expression Syntax - For {{}} expressions in other nodes +- n8n Workflow Patterns - Bigger picture workflow design + +--- + +## Version + +**Version**: 1.0.0 +**Status**: Production Ready +**Compatibility**: n8n Code node (Python mode) + +--- + +## Credits + +Part of the n8n-skills project. + +**Conceived by Romuald Członkowski** +- Website: [www.aiadvisors.pl/en](https://www.aiadvisors.pl/en) +- Part of [n8n-mcp project](https://github.com/czlonkowski/n8n-mcp) + +--- + +**Remember**: JavaScript is recommended for 95% of use cases. Use Python only when you specifically need Python's standard library features. diff --git a/skills/n8n-code-python/SKILL.md b/skills/n8n-code-python/SKILL.md new file mode 100644 index 0000000..67e8816 --- /dev/null +++ b/skills/n8n-code-python/SKILL.md @@ -0,0 +1,748 @@ +--- +name: n8n-code-python +description: Write Python code in n8n Code nodes. Use when writing Python in n8n, using _input/_json/_node syntax, working with standard library, or need to understand Python limitations in n8n Code nodes. +--- + +# Python Code Node (Beta) + +Expert guidance for writing Python code in n8n Code nodes. + +--- + +## ⚠️ Important: JavaScript First + +**Recommendation**: Use **JavaScript for 95% of use cases**. Only use Python when: +- You need specific Python standard library functions +- You're significantly more comfortable with Python syntax +- You're doing data transformations better suited to Python + +**Why JavaScript is preferred:** +- Full n8n helper functions ($helpers.httpRequest, etc.) +- Luxon DateTime library for advanced date/time operations +- No external library limitations +- Better n8n documentation and community support + +--- + +## Quick Start + +```python +# Basic template for Python Code nodes +items = _input.all() + +# Process data +processed = [] +for item in items: + processed.append({ + "json": { + **item["json"], + "processed": True, + "timestamp": datetime.now().isoformat() + } + }) + +return processed +``` + +### Essential Rules + +1. **Consider JavaScript first** - Use Python only when necessary +2. **Access data**: `_input.all()`, `_input.first()`, or `_input.item` +3. **CRITICAL**: Must return `[{"json": {...}}]` format +4. **CRITICAL**: Webhook data is under `_json["body"]` (not `_json` directly) +5. **CRITICAL LIMITATION**: **No external libraries** (no requests, pandas, numpy) +6. **Standard library only**: json, datetime, re, base64, hashlib, urllib.parse, math, random, statistics + +--- + +## Mode Selection Guide + +Same as JavaScript - choose based on your use case: + +### Run Once for All Items (Recommended - Default) + +**Use this mode for:** 95% of use cases + +- **How it works**: Code executes **once** regardless of input count +- **Data access**: `_input.all()` or `_items` array (Native mode) +- **Best for**: Aggregation, filtering, batch processing, transformations +- **Performance**: Faster for multiple items (single execution) + +```python +# Example: Calculate total from all items +all_items = _input.all() +total = sum(item["json"].get("amount", 0) for item in all_items) + +return [{ + "json": { + "total": total, + "count": len(all_items), + "average": total / len(all_items) if all_items else 0 + } +}] +``` + +### Run Once for Each Item + +**Use this mode for:** Specialized cases only + +- **How it works**: Code executes **separately** for each input item +- **Data access**: `_input.item` or `_item` (Native mode) +- **Best for**: Item-specific logic, independent operations, per-item validation +- **Performance**: Slower for large datasets (multiple executions) + +```python +# Example: Add processing timestamp to each item +item = _input.item + +return [{ + "json": { + **item["json"], + "processed": True, + "processed_at": datetime.now().isoformat() + } +}] +``` + +--- + +## Python Modes: Beta vs Native + +n8n offers two Python execution modes: + +### Python (Beta) - Recommended +- **Use**: `_input`, `_json`, `_node` helper syntax +- **Best for**: Most Python use cases +- **Helpers available**: `_now`, `_today`, `_jmespath()` +- **Import**: `from datetime import datetime` + +```python +# Python (Beta) example +items = _input.all() +now = _now # Built-in datetime object + +return [{ + "json": { + "count": len(items), + "timestamp": now.isoformat() + } +}] +``` + +### Python (Native) (Beta) +- **Use**: `_items`, `_item` variables only +- **No helpers**: No `_input`, `_now`, etc. +- **More limited**: Standard Python only +- **Use when**: Need pure Python without n8n helpers + +```python +# Python (Native) example +processed = [] + +for item in _items: + processed.append({ + "json": { + "id": item["json"].get("id"), + "processed": True + } + }) + +return processed +``` + +**Recommendation**: Use **Python (Beta)** for better n8n integration. + +--- + +## Data Access Patterns + +### Pattern 1: _input.all() - Most Common + +**Use when**: Processing arrays, batch operations, aggregations + +```python +# Get all items from previous node +all_items = _input.all() + +# Filter, transform as needed +valid = [item for item in all_items if item["json"].get("status") == "active"] + +processed = [] +for item in valid: + processed.append({ + "json": { + "id": item["json"]["id"], + "name": item["json"]["name"] + } + }) + +return processed +``` + +### Pattern 2: _input.first() - Very Common + +**Use when**: Working with single objects, API responses + +```python +# Get first item only +first_item = _input.first() +data = first_item["json"] + +return [{ + "json": { + "result": process_data(data), + "processed_at": datetime.now().isoformat() + } +}] +``` + +### Pattern 3: _input.item - Each Item Mode Only + +**Use when**: In "Run Once for Each Item" mode + +```python +# Current item in loop (Each Item mode only) +current_item = _input.item + +return [{ + "json": { + **current_item["json"], + "item_processed": True + } +}] +``` + +### Pattern 4: _node - Reference Other Nodes + +**Use when**: Need data from specific nodes in workflow + +```python +# Get output from specific node +webhook_data = _node["Webhook"]["json"] +http_data = _node["HTTP Request"]["json"] + +return [{ + "json": { + "combined": { + "webhook": webhook_data, + "api": http_data + } + } +}] +``` + +**See**: [DATA_ACCESS.md](DATA_ACCESS.md) for comprehensive guide + +--- + +## Critical: Webhook Data Structure + +**MOST COMMON MISTAKE**: Webhook data is nested under `["body"]` + +```python +# ❌ WRONG - Will raise KeyError +name = _json["name"] +email = _json["email"] + +# ✅ CORRECT - Webhook data is under ["body"] +name = _json["body"]["name"] +email = _json["body"]["email"] + +# ✅ SAFER - Use .get() for safe access +webhook_data = _json.get("body", {}) +name = webhook_data.get("name") +``` + +**Why**: Webhook node wraps all request data under `body` property. This includes POST data, query parameters, and JSON payloads. + +**See**: [DATA_ACCESS.md](DATA_ACCESS.md) for full webhook structure details + +--- + +## Return Format Requirements + +**CRITICAL RULE**: Always return list of dictionaries with `"json"` key + +### Correct Return Formats + +```python +# ✅ Single result +return [{ + "json": { + "field1": value1, + "field2": value2 + } +}] + +# ✅ Multiple results +return [ + {"json": {"id": 1, "data": "first"}}, + {"json": {"id": 2, "data": "second"}} +] + +# ✅ List comprehension +transformed = [ + {"json": {"id": item["json"]["id"], "processed": True}} + for item in _input.all() + if item["json"].get("valid") +] +return transformed + +# ✅ Empty result (when no data to return) +return [] + +# ✅ Conditional return +if should_process: + return [{"json": processed_data}] +else: + return [] +``` + +### Incorrect Return Formats + +```python +# ❌ WRONG: Dictionary without list wrapper +return { + "json": {"field": value} +} + +# ❌ WRONG: List without json wrapper +return [{"field": value}] + +# ❌ WRONG: Plain string +return "processed" + +# ❌ WRONG: Incomplete structure +return [{"data": value}] # Should be {"json": value} +``` + +**Why it matters**: Next nodes expect list format. Incorrect format causes workflow execution to fail. + +**See**: [ERROR_PATTERNS.md](ERROR_PATTERNS.md) #2 for detailed error solutions + +--- + +## Critical Limitation: No External Libraries + +**MOST IMPORTANT PYTHON LIMITATION**: Cannot import external packages + +### What's NOT Available + +```python +# ❌ NOT AVAILABLE - Will raise ModuleNotFoundError +import requests # ❌ No +import pandas # ❌ No +import numpy # ❌ No +import scipy # ❌ No +from bs4 import BeautifulSoup # ❌ No +import lxml # ❌ No +``` + +### What IS Available (Standard Library) + +```python +# ✅ AVAILABLE - Standard library only +import json # ✅ JSON parsing +import datetime # ✅ Date/time operations +import re # ✅ Regular expressions +import base64 # ✅ Base64 encoding/decoding +import hashlib # ✅ Hashing functions +import urllib.parse # ✅ URL parsing +import math # ✅ Math functions +import random # ✅ Random numbers +import statistics # ✅ Statistical functions +``` + +### Workarounds + +**Need HTTP requests?** +- ✅ Use **HTTP Request node** before Code node +- ✅ Or switch to **JavaScript** and use `$helpers.httpRequest()` + +**Need data analysis (pandas/numpy)?** +- ✅ Use Python **statistics** module for basic stats +- ✅ Or switch to **JavaScript** for most operations +- ✅ Manual calculations with lists and dictionaries + +**Need web scraping (BeautifulSoup)?** +- ✅ Use **HTTP Request node** + **HTML Extract node** +- ✅ Or switch to **JavaScript** with regex/string methods + +**See**: [STANDARD_LIBRARY.md](STANDARD_LIBRARY.md) for complete reference + +--- + +## Common Patterns Overview + +Based on production workflows, here are the most useful Python patterns: + +### 1. Data Transformation +Transform all items with list comprehensions + +```python +items = _input.all() + +return [ + { + "json": { + "id": item["json"].get("id"), + "name": item["json"].get("name", "Unknown").upper(), + "processed": True + } + } + for item in items +] +``` + +### 2. Filtering & Aggregation +Sum, filter, count with built-in functions + +```python +items = _input.all() +total = sum(item["json"].get("amount", 0) for item in items) +valid_items = [item for item in items if item["json"].get("amount", 0) > 0] + +return [{ + "json": { + "total": total, + "count": len(valid_items) + } +}] +``` + +### 3. String Processing with Regex +Extract patterns from text + +```python +import re + +items = _input.all() +email_pattern = r'\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\.[A-Z|a-z]{2,}\b' + +all_emails = [] +for item in items: + text = item["json"].get("text", "") + emails = re.findall(email_pattern, text) + all_emails.extend(emails) + +# Remove duplicates +unique_emails = list(set(all_emails)) + +return [{ + "json": { + "emails": unique_emails, + "count": len(unique_emails) + } +}] +``` + +### 4. Data Validation +Validate and clean data + +```python +items = _input.all() +validated = [] + +for item in items: + data = item["json"] + errors = [] + + # Validate fields + if not data.get("email"): + errors.append("Email required") + if not data.get("name"): + errors.append("Name required") + + validated.append({ + "json": { + **data, + "valid": len(errors) == 0, + "errors": errors if errors else None + } + }) + +return validated +``` + +### 5. Statistical Analysis +Calculate statistics with statistics module + +```python +from statistics import mean, median, stdev + +items = _input.all() +values = [item["json"].get("value", 0) for item in items if "value" in item["json"]] + +if values: + return [{ + "json": { + "mean": mean(values), + "median": median(values), + "stdev": stdev(values) if len(values) > 1 else 0, + "min": min(values), + "max": max(values), + "count": len(values) + } + }] +else: + return [{"json": {"error": "No values found"}}] +``` + +**See**: [COMMON_PATTERNS.md](COMMON_PATTERNS.md) for 10 detailed Python patterns + +--- + +## Error Prevention - Top 5 Mistakes + +### #1: Importing External Libraries (Python-Specific!) + +```python +# ❌ WRONG: Trying to import external library +import requests # ModuleNotFoundError! + +# ✅ CORRECT: Use HTTP Request node or JavaScript +# Add HTTP Request node before Code node +# OR switch to JavaScript and use $helpers.httpRequest() +``` + +### #2: Empty Code or Missing Return + +```python +# ❌ WRONG: No return statement +items = _input.all() +# Processing... +# Forgot to return! + +# ✅ CORRECT: Always return data +items = _input.all() +# Processing... +return [{"json": item["json"]} for item in items] +``` + +### #3: Incorrect Return Format + +```python +# ❌ WRONG: Returning dict instead of list +return {"json": {"result": "success"}} + +# ✅ CORRECT: List wrapper required +return [{"json": {"result": "success"}}] +``` + +### #4: KeyError on Dictionary Access + +```python +# ❌ WRONG: Direct access crashes if missing +name = _json["user"]["name"] # KeyError! + +# ✅ CORRECT: Use .get() for safe access +name = _json.get("user", {}).get("name", "Unknown") +``` + +### #5: Webhook Body Nesting + +```python +# ❌ WRONG: Direct access to webhook data +email = _json["email"] # KeyError! + +# ✅ CORRECT: Webhook data under ["body"] +email = _json["body"]["email"] + +# ✅ BETTER: Safe access with .get() +email = _json.get("body", {}).get("email", "no-email") +``` + +**See**: [ERROR_PATTERNS.md](ERROR_PATTERNS.md) for comprehensive error guide + +--- + +## Standard Library Reference + +### Most Useful Modules + +```python +# JSON operations +import json +data = json.loads(json_string) +json_output = json.dumps({"key": "value"}) + +# Date/time +from datetime import datetime, timedelta +now = datetime.now() +tomorrow = now + timedelta(days=1) +formatted = now.strftime("%Y-%m-%d") + +# Regular expressions +import re +matches = re.findall(r'\d+', text) +cleaned = re.sub(r'[^\w\s]', '', text) + +# Base64 encoding +import base64 +encoded = base64.b64encode(data).decode() +decoded = base64.b64decode(encoded) + +# Hashing +import hashlib +hash_value = hashlib.sha256(text.encode()).hexdigest() + +# URL parsing +import urllib.parse +params = urllib.parse.urlencode({"key": "value"}) +parsed = urllib.parse.urlparse(url) + +# Statistics +from statistics import mean, median, stdev +average = mean([1, 2, 3, 4, 5]) +``` + +**See**: [STANDARD_LIBRARY.md](STANDARD_LIBRARY.md) for complete reference + +--- + +## Best Practices + +### 1. Always Use .get() for Dictionary Access + +```python +# ✅ SAFE: Won't crash if field missing +value = item["json"].get("field", "default") + +# ❌ RISKY: Crashes if field doesn't exist +value = item["json"]["field"] +``` + +### 2. Handle None/Null Values Explicitly + +```python +# ✅ GOOD: Default to 0 if None +amount = item["json"].get("amount") or 0 + +# ✅ GOOD: Check for None explicitly +text = item["json"].get("text") +if text is None: + text = "" +``` + +### 3. Use List Comprehensions for Filtering + +```python +# ✅ PYTHONIC: List comprehension +valid = [item for item in items if item["json"].get("active")] + +# ❌ VERBOSE: Manual loop +valid = [] +for item in items: + if item["json"].get("active"): + valid.append(item) +``` + +### 4. Return Consistent Structure + +```python +# ✅ CONSISTENT: Always list with "json" key +return [{"json": result}] # Single result +return results # Multiple results (already formatted) +return [] # No results +``` + +### 5. Debug with print() Statements + +```python +# Debug statements appear in browser console (F12) +items = _input.all() +print(f"Processing {len(items)} items") +print(f"First item: {items[0] if items else 'None'}") +``` + +--- + +## When to Use Python vs JavaScript + +### Use Python When: +- ✅ You need `statistics` module for statistical operations +- ✅ You're significantly more comfortable with Python syntax +- ✅ Your logic maps well to list comprehensions +- ✅ You need specific standard library functions + +### Use JavaScript When: +- ✅ You need HTTP requests ($helpers.httpRequest()) +- ✅ You need advanced date/time (DateTime/Luxon) +- ✅ You want better n8n integration +- ✅ **For 95% of use cases** (recommended) + +### Consider Other Nodes When: +- ❌ Simple field mapping → Use **Set** node +- ❌ Basic filtering → Use **Filter** node +- ❌ Simple conditionals → Use **IF** or **Switch** node +- ❌ HTTP requests only → Use **HTTP Request** node + +--- + +## Integration with Other Skills + +### Works With: + +**n8n Expression Syntax**: +- Expressions use `{{ }}` syntax in other nodes +- Code nodes use Python directly (no `{{ }}`) +- When to use expressions vs code + +**n8n MCP Tools Expert**: +- How to find Code node: `search_nodes({query: "code"})` +- Get configuration help: `get_node_essentials("nodes-base.code")` +- Validate code: `validate_node_operation()` + +**n8n Node Configuration**: +- Mode selection (All Items vs Each Item) +- Language selection (Python vs JavaScript) +- Understanding property dependencies + +**n8n Workflow Patterns**: +- Code nodes in transformation step +- When to use Python vs JavaScript in patterns + +**n8n Validation Expert**: +- Validate Code node configuration +- Handle validation errors +- Auto-fix common issues + +**n8n Code JavaScript**: +- When to use JavaScript instead +- Comparison of JavaScript vs Python features +- Migration from Python to JavaScript + +--- + +## Quick Reference Checklist + +Before deploying Python Code nodes, verify: + +- [ ] **Considered JavaScript first** - Using Python only when necessary +- [ ] **Code is not empty** - Must have meaningful logic +- [ ] **Return statement exists** - Must return list of dictionaries +- [ ] **Proper return format** - Each item: `{"json": {...}}` +- [ ] **Data access correct** - Using `_input.all()`, `_input.first()`, or `_input.item` +- [ ] **No external imports** - Only standard library (json, datetime, re, etc.) +- [ ] **Safe dictionary access** - Using `.get()` to avoid KeyError +- [ ] **Webhook data** - Access via `["body"]` if from webhook +- [ ] **Mode selection** - "All Items" for most cases +- [ ] **Output consistent** - All code paths return same structure + +--- + +## Additional Resources + +### Related Files +- [DATA_ACCESS.md](DATA_ACCESS.md) - Comprehensive Python data access patterns +- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - 10 Python patterns for n8n +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Top 5 errors and solutions +- [STANDARD_LIBRARY.md](STANDARD_LIBRARY.md) - Complete standard library reference + +### n8n Documentation +- Code Node Guide: https://docs.n8n.io/code/code-node/ +- Python in n8n: https://docs.n8n.io/code/builtin/python-modules/ + +--- + +**Ready to write Python in n8n Code nodes - but consider JavaScript first!** Use Python for specific needs, reference the error patterns guide to avoid common mistakes, and leverage the standard library effectively. diff --git a/skills/n8n-code-python/STANDARD_LIBRARY.md b/skills/n8n-code-python/STANDARD_LIBRARY.md new file mode 100644 index 0000000..181b515 --- /dev/null +++ b/skills/n8n-code-python/STANDARD_LIBRARY.md @@ -0,0 +1,974 @@ +# Standard Library Reference - Python Code Node + +Complete guide to available Python standard library modules in n8n Code nodes. + +--- + +## ⚠️ Critical Limitation + +**NO EXTERNAL LIBRARIES AVAILABLE** + +Python Code nodes in n8n have **ONLY** the Python standard library. No pip packages. + +```python +# ❌ NOT AVAILABLE - Will cause ModuleNotFoundError +import requests # No HTTP library! +import pandas # No data analysis! +import numpy # No numerical computing! +import bs4 # No web scraping! +import selenium # No browser automation! +import psycopg2 # No database drivers! +import pymongo # No MongoDB! +import sqlalchemy # No ORMs! + +# ✅ AVAILABLE - Standard library only +import json +import datetime +import re +import base64 +import hashlib +import urllib.parse +import urllib.request +import math +import random +import statistics +``` + +**Recommendation**: Use **JavaScript** for 95% of use cases. JavaScript has more capabilities in n8n. + +--- + +## Available Modules + +### Priority 1: Most Useful (Use These) + +1. **json** - JSON parsing and generation +2. **datetime** - Date and time operations +3. **re** - Regular expressions +4. **base64** - Base64 encoding/decoding +5. **hashlib** - Hashing (MD5, SHA256, etc.) +6. **urllib.parse** - URL parsing and encoding + +### Priority 2: Moderately Useful + +7. **math** - Mathematical functions +8. **random** - Random number generation +9. **statistics** - Statistical functions +10. **collections** - Specialized data structures + +### Priority 3: Occasionally Useful + +11. **itertools** - Iterator tools +12. **functools** - Higher-order functions +13. **operator** - Standard operators as functions +14. **string** - String constants and templates +15. **textwrap** - Text wrapping utilities + +--- + +## Module 1: json - JSON Operations + +**Most common module** - Parse and generate JSON data. + +### Parse JSON String + +```python +import json + +# Parse JSON string to Python dict +json_string = '{"name": "Alice", "age": 30}' +data = json.loads(json_string) + +return [{ + "json": { + "name": data["name"], + "age": data["age"], + "parsed": True + } +}] +``` + +### Generate JSON String + +```python +import json + +# Convert Python dict to JSON string +data = { + "users": [ + {"id": 1, "name": "Alice"}, + {"id": 2, "name": "Bob"} + ], + "total": 2 +} + +json_string = json.dumps(data, indent=2) + +return [{ + "json": { + "json_output": json_string, + "length": len(json_string) + } +}] +``` + +### Handle JSON Errors + +```python +import json + +webhook_data = _input.first()["json"]["body"] +json_string = webhook_data.get("data", "") + +try: + parsed = json.loads(json_string) + status = "valid" + error = None +except json.JSONDecodeError as e: + parsed = None + status = "invalid" + error = str(e) + +return [{ + "json": { + "status": status, + "data": parsed, + "error": error + } +}] +``` + +### Pretty Print JSON + +```python +import json + +# Format JSON with indentation +data = _input.first()["json"] + +pretty_json = json.dumps(data, indent=2, sort_keys=True) + +return [{ + "json": { + "formatted": pretty_json + } +}] +``` + +--- + +## Module 2: datetime - Date and Time + +**Very common** - Date parsing, formatting, calculations. + +### Current Date and Time + +```python +from datetime import datetime + +now = datetime.now() + +return [{ + "json": { + "timestamp": now.isoformat(), + "date": now.strftime("%Y-%m-%d"), + "time": now.strftime("%H:%M:%S"), + "formatted": now.strftime("%B %d, %Y at %I:%M %p") + } +}] +``` + +### Parse Date String + +```python +from datetime import datetime + +date_string = "2025-01-15T14:30:00" +dt = datetime.fromisoformat(date_string) + +return [{ + "json": { + "year": dt.year, + "month": dt.month, + "day": dt.day, + "hour": dt.hour, + "weekday": dt.strftime("%A") + } +}] +``` + +### Date Calculations + +```python +from datetime import datetime, timedelta + +now = datetime.now() + +# Calculate future/past dates +tomorrow = now + timedelta(days=1) +yesterday = now - timedelta(days=1) +next_week = now + timedelta(weeks=1) +one_hour_ago = now - timedelta(hours=1) + +return [{ + "json": { + "now": now.isoformat(), + "tomorrow": tomorrow.isoformat(), + "yesterday": yesterday.isoformat(), + "next_week": next_week.isoformat(), + "one_hour_ago": one_hour_ago.isoformat() + } +}] +``` + +### Compare Dates + +```python +from datetime import datetime + +date1 = datetime(2025, 1, 15) +date2 = datetime(2025, 1, 20) + +# Calculate difference +diff = date2 - date1 + +return [{ + "json": { + "days_difference": diff.days, + "seconds_difference": diff.total_seconds(), + "date1_is_earlier": date1 < date2, + "date2_is_later": date2 > date1 + } +}] +``` + +### Format Dates + +```python +from datetime import datetime + +dt = datetime.now() + +return [{ + "json": { + "iso": dt.isoformat(), + "us_format": dt.strftime("%m/%d/%Y"), + "eu_format": dt.strftime("%d/%m/%Y"), + "long_format": dt.strftime("%A, %B %d, %Y"), + "time_12h": dt.strftime("%I:%M %p"), + "time_24h": dt.strftime("%H:%M:%S") + } +}] +``` + +--- + +## Module 3: re - Regular Expressions + +**Common** - Pattern matching, text extraction, validation. + +### Pattern Matching + +```python +import re + +text = "Email: alice@example.com, Phone: 555-1234" + +# Find email +email_match = re.search(r'\b[\w.-]+@[\w.-]+\.\w+\b', text) +email = email_match.group(0) if email_match else None + +# Find phone +phone_match = re.search(r'\d{3}-\d{4}', text) +phone = phone_match.group(0) if phone_match else None + +return [{ + "json": { + "email": email, + "phone": phone + } +}] +``` + +### Extract All Matches + +```python +import re + +text = "Tags: #python #automation #workflow #n8n" + +# Find all hashtags +hashtags = re.findall(r'#(\w+)', text) + +return [{ + "json": { + "tags": hashtags, + "count": len(hashtags) + } +}] +``` + +### Replace Patterns + +```python +import re + +text = "Price: $99.99, Discount: $10.00" + +# Remove dollar signs +cleaned = re.sub(r'\$', '', text) + +# Replace multiple spaces with single space +normalized = re.sub(r'\s+', ' ', cleaned) + +return [{ + "json": { + "original": text, + "cleaned": cleaned, + "normalized": normalized + } +}] +``` + +### Validate Format + +```python +import re + +email = _input.first()["json"]["body"].get("email", "") + +# Email validation pattern +email_pattern = r'^[\w.-]+@[\w.-]+\.\w+$' +is_valid = bool(re.match(email_pattern, email)) + +return [{ + "json": { + "email": email, + "valid": is_valid + } +}] +``` + +### Split on Pattern + +```python +import re + +text = "apple,banana;orange|grape" + +# Split on multiple delimiters +items = re.split(r'[,;|]', text) + +# Clean up whitespace +items = [item.strip() for item in items] + +return [{ + "json": { + "items": items, + "count": len(items) + } +}] +``` + +--- + +## Module 4: base64 - Encoding/Decoding + +**Common** - Encode binary data, API authentication. + +### Encode String to Base64 + +```python +import base64 + +text = "Hello, World!" + +# Encode to base64 +encoded_bytes = base64.b64encode(text.encode('utf-8')) +encoded_string = encoded_bytes.decode('utf-8') + +return [{ + "json": { + "original": text, + "encoded": encoded_string + } +}] +``` + +### Decode Base64 to String + +```python +import base64 + +encoded = "SGVsbG8sIFdvcmxkIQ==" + +# Decode from base64 +decoded_bytes = base64.b64decode(encoded) +decoded_string = decoded_bytes.decode('utf-8') + +return [{ + "json": { + "encoded": encoded, + "decoded": decoded_string + } +}] +``` + +### Basic Auth Header + +```python +import base64 + +username = "admin" +password = "secret123" + +# Create Basic Auth header +credentials = f"{username}:{password}" +encoded = base64.b64encode(credentials.encode('utf-8')).decode('utf-8') +auth_header = f"Basic {encoded}" + +return [{ + "json": { + "authorization": auth_header + } +}] +``` + +--- + +## Module 5: hashlib - Hashing + +**Common** - Generate checksums, hash passwords, create IDs. + +### MD5 Hash + +```python +import hashlib + +text = "Hello, World!" + +# Generate MD5 hash +md5_hash = hashlib.md5(text.encode('utf-8')).hexdigest() + +return [{ + "json": { + "original": text, + "md5": md5_hash + } +}] +``` + +### SHA256 Hash + +```python +import hashlib + +data = _input.first()["json"]["body"] +text = data.get("password", "") + +# Generate SHA256 hash (more secure than MD5) +sha256_hash = hashlib.sha256(text.encode('utf-8')).hexdigest() + +return [{ + "json": { + "hashed": sha256_hash + } +}] +``` + +### Generate Unique ID + +```python +import hashlib +from datetime import datetime + +# Create unique ID from multiple values +unique_string = f"{datetime.now().isoformat()}-{_json.get('user_id', 'unknown')}" +unique_id = hashlib.sha256(unique_string.encode('utf-8')).hexdigest()[:16] + +return [{ + "json": { + "id": unique_id, + "generated_at": datetime.now().isoformat() + } +}] +``` + +--- + +## Module 6: urllib.parse - URL Operations + +**Common** - Parse URLs, encode parameters. + +### Parse URL + +```python +from urllib.parse import urlparse + +url = "https://example.com/path?key=value&foo=bar#section" + +parsed = urlparse(url) + +return [{ + "json": { + "scheme": parsed.scheme, # "https" + "netloc": parsed.netloc, # "example.com" + "path": parsed.path, # "/path" + "query": parsed.query, # "key=value&foo=bar" + "fragment": parsed.fragment # "section" + } +}] +``` + +### URL Encode Parameters + +```python +from urllib.parse import urlencode + +params = { + "name": "Alice Smith", + "email": "alice@example.com", + "message": "Hello, World!" +} + +# Encode parameters for URL +encoded = urlencode(params) + +return [{ + "json": { + "query_string": encoded, + "full_url": f"https://api.example.com/submit?{encoded}" + } +}] +``` + +### Parse Query String + +```python +from urllib.parse import parse_qs + +query_string = "name=Alice&age=30&tags=python&tags=n8n" + +# Parse query string +params = parse_qs(query_string) + +return [{ + "json": { + "name": params.get("name", [""])[0], + "age": int(params.get("age", ["0"])[0]), + "tags": params.get("tags", []) + } +}] +``` + +### URL Encode/Decode Strings + +```python +from urllib.parse import quote, unquote + +text = "Hello, World! 你好" + +# URL encode +encoded = quote(text) + +# URL decode +decoded = unquote(encoded) + +return [{ + "json": { + "original": text, + "encoded": encoded, + "decoded": decoded + } +}] +``` + +--- + +## Module 7: math - Mathematical Operations + +**Moderately useful** - Advanced math functions. + +### Basic Math Functions + +```python +import math + +number = 16.7 + +return [{ + "json": { + "ceiling": math.ceil(number), # 17 + "floor": math.floor(number), # 16 + "rounded": round(number), # 17 + "square_root": math.sqrt(16), # 4.0 + "power": math.pow(2, 3), # 8.0 + "absolute": math.fabs(-5.5) # 5.5 + } +}] +``` + +### Trigonometry + +```python +import math + +angle_degrees = 45 +angle_radians = math.radians(angle_degrees) + +return [{ + "json": { + "sine": math.sin(angle_radians), + "cosine": math.cos(angle_radians), + "tangent": math.tan(angle_radians), + "pi": math.pi, + "e": math.e + } +}] +``` + +### Logarithms + +```python +import math + +number = 100 + +return [{ + "json": { + "log10": math.log10(number), # 2.0 + "natural_log": math.log(number), # 4.605... + "log2": math.log2(number) # 6.644... + } +}] +``` + +--- + +## Module 8: random - Random Numbers + +**Moderately useful** - Generate random data, sampling. + +### Random Numbers + +```python +import random + +return [{ + "json": { + "random_float": random.random(), # 0.0 to 1.0 + "random_int": random.randint(1, 100), # 1 to 100 + "random_range": random.randrange(0, 100, 5) # 0, 5, 10, ..., 95 + } +}] +``` + +### Random Choice + +```python +import random + +colors = ["red", "green", "blue", "yellow"] +users = [{"id": 1, "name": "Alice"}, {"id": 2, "name": "Bob"}] + +return [{ + "json": { + "random_color": random.choice(colors), + "random_user": random.choice(users) + } +}] +``` + +### Shuffle List + +```python +import random + +items = [1, 2, 3, 4, 5] +shuffled = items.copy() +random.shuffle(shuffled) + +return [{ + "json": { + "original": items, + "shuffled": shuffled + } +}] +``` + +### Random Sample + +```python +import random + +items = list(range(1, 101)) + +# Get 10 random items without replacement +sample = random.sample(items, 10) + +return [{ + "json": { + "sample": sample, + "count": len(sample) + } +}] +``` + +--- + +## Module 9: statistics - Statistical Functions + +**Moderately useful** - Calculate stats from data. + +### Basic Statistics + +```python +import statistics + +numbers = [10, 20, 30, 40, 50, 60, 70, 80, 90, 100] + +return [{ + "json": { + "mean": statistics.mean(numbers), # 55.0 + "median": statistics.median(numbers), # 55.0 + "mode": statistics.mode([1, 2, 2, 3]), # 2 + "stdev": statistics.stdev(numbers), # 30.28... + "variance": statistics.variance(numbers) # 916.67... + } +}] +``` + +### Aggregate from Items + +```python +import statistics + +all_items = _input.all() + +# Extract amounts +amounts = [item["json"].get("amount", 0) for item in all_items] + +if amounts: + return [{ + "json": { + "count": len(amounts), + "total": sum(amounts), + "average": statistics.mean(amounts), + "median": statistics.median(amounts), + "min": min(amounts), + "max": max(amounts), + "range": max(amounts) - min(amounts) + } + }] +else: + return [{"json": {"error": "No data"}}] +``` + +--- + +## Workarounds for Missing Libraries + +### HTTP Requests (No requests library) + +```python +# ❌ Can't use requests library +# import requests # ModuleNotFoundError! + +# ✅ Use HTTP Request node instead +# Add HTTP Request node BEFORE Code node +# Access the response in Code node + +response_data = _input.first()["json"] + +return [{ + "json": { + "status": response_data.get("status"), + "data": response_data.get("body"), + "processed": True + } +}] +``` + +### Data Processing (No pandas) + +```python +# ❌ Can't use pandas +# import pandas as pd # ModuleNotFoundError! + +# ✅ Use Python's built-in list comprehensions +all_items = _input.all() + +# Filter +active_items = [ + item for item in all_items + if item["json"].get("status") == "active" +] + +# Group by +from collections import defaultdict +grouped = defaultdict(list) + +for item in all_items: + category = item["json"].get("category", "other") + grouped[category].append(item["json"]) + +# Aggregate +import statistics +amounts = [item["json"].get("amount", 0) for item in all_items] +total = sum(amounts) +average = statistics.mean(amounts) if amounts else 0 + +return [{ + "json": { + "active_count": len(active_items), + "grouped": dict(grouped), + "total": total, + "average": average + } +}] +``` + +### Database Operations (No drivers) + +```python +# ❌ Can't use database drivers +# import psycopg2 # ModuleNotFoundError! +# import pymongo # ModuleNotFoundError! + +# ✅ Use n8n database nodes instead +# Add Postgres/MySQL/MongoDB node BEFORE Code node +# Process results in Code node + +db_results = _input.first()["json"] + +return [{ + "json": { + "record_count": len(db_results) if isinstance(db_results, list) else 1, + "processed": True + } +}] +``` + +--- + +## Complete Standard Library List + +**Available** (commonly useful): +- json +- datetime, time +- re +- base64 +- hashlib +- urllib.parse, urllib.request, urllib.error +- math +- random +- statistics +- collections (defaultdict, Counter, namedtuple) +- itertools +- functools +- operator +- string +- textwrap + +**Available** (less common): +- os.path (path operations only) +- copy +- typing +- enum +- decimal +- fractions + +**NOT Available** (external libraries): +- requests (HTTP) +- pandas (data analysis) +- numpy (numerical computing) +- bs4/beautifulsoup4 (HTML parsing) +- selenium (browser automation) +- psycopg2, pymongo, sqlalchemy (databases) +- flask, fastapi (web frameworks) +- pillow (image processing) +- openpyxl, xlsxwriter (Excel) + +--- + +## Best Practices + +### 1. Use Standard Library When Possible + +```python +# ✅ GOOD: Use standard library +import json +import datetime +import re + +data = _input.first()["json"] +processed = json.loads(data.get("json_string", "{}")) + +return [{"json": processed}] +``` + +### 2. Fall Back to n8n Nodes + +```python +# For operations requiring external libraries, +# use n8n nodes instead: +# - HTTP Request for API calls +# - Postgres/MySQL for databases +# - Extract from File for parsing + +# Then process results in Code node +result = _input.first()["json"] +return [{"json": {"processed": result}}] +``` + +### 3. Combine Multiple Modules + +```python +import json +import base64 +import hashlib +from datetime import datetime + +# Combine modules for complex operations +data = _input.first()["json"]["body"] + +# Hash sensitive data +user_id = hashlib.sha256(data.get("email", "").encode()).hexdigest()[:16] + +# Encode for storage +encoded_data = base64.b64encode(json.dumps(data).encode()).decode() + +return [{ + "json": { + "user_id": user_id, + "encoded_data": encoded_data, + "timestamp": datetime.now().isoformat() + } +}] +``` + +--- + +## Summary + +**Most Useful Modules**: +1. json - Parse/generate JSON +2. datetime - Date operations +3. re - Regular expressions +4. base64 - Encoding +5. hashlib - Hashing +6. urllib.parse - URL operations + +**Critical Limitation**: +- NO external libraries (requests, pandas, numpy, etc.) + +**Recommended Approach**: +- Use **JavaScript** for 95% of use cases +- Use Python only when specifically needed +- Use n8n nodes for operations requiring external libraries + +**See Also**: +- [SKILL.md](SKILL.md) - Python Code overview +- [DATA_ACCESS.md](DATA_ACCESS.md) - Data access patterns +- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Production patterns +- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes