bps-kit 1.2.2 → 1.3.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.bps-kit.json +4 -4
- package/README.md +3 -0
- package/implementation_plan.md.resolved +37 -0
- package/package.json +2 -2
- package/templates/agents-template/ARCHITECTURE.md +21 -9
- package/templates/agents-template/agents/automation-specialist.md +157 -0
- package/templates/agents-template/rules/GEMINI.md +2 -10
- package/templates/agents-template/workflows/automate.md +153 -0
- package/templates/skills_normal/n8n-code-javascript/BUILTIN_FUNCTIONS.md +764 -0
- package/templates/skills_normal/n8n-code-javascript/COMMON_PATTERNS.md +1110 -0
- package/templates/skills_normal/n8n-code-javascript/DATA_ACCESS.md +782 -0
- package/templates/skills_normal/n8n-code-javascript/ERROR_PATTERNS.md +763 -0
- package/templates/skills_normal/n8n-code-javascript/README.md +350 -0
- package/templates/skills_normal/n8n-code-javascript/SKILL.md +699 -0
- package/templates/skills_normal/n8n-code-python/COMMON_PATTERNS.md +794 -0
- package/templates/skills_normal/n8n-code-python/DATA_ACCESS.md +702 -0
- package/templates/skills_normal/n8n-code-python/ERROR_PATTERNS.md +601 -0
- package/templates/skills_normal/n8n-code-python/README.md +386 -0
- package/templates/skills_normal/n8n-code-python/SKILL.md +748 -0
- package/templates/skills_normal/n8n-code-python/STANDARD_LIBRARY.md +974 -0
- package/templates/skills_normal/n8n-expression-syntax/COMMON_MISTAKES.md +393 -0
- package/templates/skills_normal/n8n-expression-syntax/EXAMPLES.md +483 -0
- package/templates/skills_normal/n8n-expression-syntax/README.md +93 -0
- package/templates/skills_normal/n8n-expression-syntax/SKILL.md +516 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/README.md +99 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/SEARCH_GUIDE.md +374 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/SKILL.md +642 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/VALIDATION_GUIDE.md +442 -0
- package/templates/skills_normal/n8n-mcp-tools-expert/WORKFLOW_GUIDE.md +618 -0
- package/templates/skills_normal/n8n-node-configuration/DEPENDENCIES.md +789 -0
- package/templates/skills_normal/n8n-node-configuration/OPERATION_PATTERNS.md +913 -0
- package/templates/skills_normal/n8n-node-configuration/README.md +364 -0
- package/templates/skills_normal/n8n-node-configuration/SKILL.md +785 -0
- package/templates/skills_normal/n8n-validation-expert/ERROR_CATALOG.md +943 -0
- package/templates/skills_normal/n8n-validation-expert/FALSE_POSITIVES.md +720 -0
- package/templates/skills_normal/n8n-validation-expert/README.md +290 -0
- package/templates/skills_normal/n8n-validation-expert/SKILL.md +689 -0
- package/templates/skills_normal/n8n-workflow-patterns/README.md +251 -0
- package/templates/skills_normal/n8n-workflow-patterns/SKILL.md +411 -0
- package/templates/skills_normal/n8n-workflow-patterns/ai_agent_workflow.md +784 -0
- package/templates/skills_normal/n8n-workflow-patterns/database_operations.md +785 -0
- package/templates/skills_normal/n8n-workflow-patterns/http_api_integration.md +734 -0
- package/templates/skills_normal/n8n-workflow-patterns/scheduled_tasks.md +773 -0
- package/templates/skills_normal/n8n-workflow-patterns/webhook_processing.md +545 -0
- package/templates/vault/n8n-code-javascript/SKILL.md +10 -10
- package/templates/vault/n8n-code-python/SKILL.md +11 -11
- package/templates/vault/n8n-expression-syntax/SKILL.md +4 -4
- package/templates/vault/n8n-mcp-tools-expert/SKILL.md +9 -9
- package/templates/vault/n8n-node-configuration/SKILL.md +2 -2
- package/templates/vault/n8n-validation-expert/SKILL.md +3 -3
- package/templates/vault/n8n-workflow-patterns/SKILL.md +11 -11
|
@@ -0,0 +1,601 @@
|
|
|
1
|
+
# Error Patterns - Python Code Node
|
|
2
|
+
|
|
3
|
+
Common Python Code node errors and how to fix them.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Error Overview
|
|
8
|
+
|
|
9
|
+
**Top 5 Python Code Node Errors**:
|
|
10
|
+
|
|
11
|
+
1. **ModuleNotFoundError** - Trying to import external libraries (Python-specific)
|
|
12
|
+
2. **Empty Code / Missing Return** - No code or return statement
|
|
13
|
+
3. **KeyError** - Dictionary access without .get()
|
|
14
|
+
4. **IndexError** - List access without bounds checking
|
|
15
|
+
5. **Incorrect Return Format** - Wrong data structure returned
|
|
16
|
+
|
|
17
|
+
These 5 errors cover the majority of Python Code node failures.
|
|
18
|
+
|
|
19
|
+
---
|
|
20
|
+
|
|
21
|
+
## Error #1: ModuleNotFoundError (MOST CRITICAL)
|
|
22
|
+
|
|
23
|
+
**Frequency**: Very common in Python Code nodes
|
|
24
|
+
|
|
25
|
+
**What it is**: Attempting to import external libraries that aren't available.
|
|
26
|
+
|
|
27
|
+
### The Problem
|
|
28
|
+
|
|
29
|
+
```python
|
|
30
|
+
# ❌ WRONG: External libraries not available
|
|
31
|
+
import requests # ModuleNotFoundError: No module named 'requests'
|
|
32
|
+
import pandas # ModuleNotFoundError: No module named 'pandas'
|
|
33
|
+
import numpy # ModuleNotFoundError: No module named 'numpy'
|
|
34
|
+
import bs4 # ModuleNotFoundError: No module named 'bs4'
|
|
35
|
+
import pymongo # ModuleNotFoundError: No module named 'pymongo'
|
|
36
|
+
import psycopg2 # ModuleNotFoundError: No module named 'psycopg2'
|
|
37
|
+
|
|
38
|
+
# This code will FAIL - these libraries are not installed!
|
|
39
|
+
response = requests.get("https://api.example.com/data")
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
### The Solution
|
|
43
|
+
|
|
44
|
+
**Option 1: Use JavaScript Instead** (Recommended for 95% of cases)
|
|
45
|
+
|
|
46
|
+
```javascript
|
|
47
|
+
// ✅ JavaScript Code node with $helpers.httpRequest()
|
|
48
|
+
const response = await $helpers.httpRequest({
|
|
49
|
+
method: 'GET',
|
|
50
|
+
url: 'https://api.example.com/data'
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
return [{json: response}];
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
**Option 2: Use n8n HTTP Request Node**
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
# ✅ Add HTTP Request node BEFORE Python Code node
|
|
60
|
+
# Access the response in Python Code node
|
|
61
|
+
|
|
62
|
+
response = _input.first()["json"]
|
|
63
|
+
|
|
64
|
+
return [{
|
|
65
|
+
"json": {
|
|
66
|
+
"status": response.get("status"),
|
|
67
|
+
"data": response.get("body"),
|
|
68
|
+
"processed": True
|
|
69
|
+
}
|
|
70
|
+
}]
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
**Option 3: Use Standard Library Only**
|
|
74
|
+
|
|
75
|
+
```python
|
|
76
|
+
# ✅ Use urllib from standard library (limited functionality)
|
|
77
|
+
from urllib.request import urlopen
|
|
78
|
+
from urllib.parse import urlencode
|
|
79
|
+
import json
|
|
80
|
+
|
|
81
|
+
# Simple GET request (no headers, no auth)
|
|
82
|
+
url = "https://api.example.com/data"
|
|
83
|
+
with urlopen(url) as response:
|
|
84
|
+
data = json.loads(response.read())
|
|
85
|
+
|
|
86
|
+
return [{"json": data}]
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Common Library Replacements
|
|
90
|
+
|
|
91
|
+
| Need | ❌ External Library | ✅ Alternative |
|
|
92
|
+
|------|-------------------|----------------|
|
|
93
|
+
| HTTP requests | `requests` | Use HTTP Request node or JavaScript |
|
|
94
|
+
| Data analysis | `pandas` | Use Python list comprehensions |
|
|
95
|
+
| Database | `psycopg2`, `pymongo` | Use n8n database nodes |
|
|
96
|
+
| Web scraping | `beautifulsoup4` | Use HTML Extract node |
|
|
97
|
+
| Excel | `openpyxl` | Use Spreadsheet File node |
|
|
98
|
+
| Image processing | `pillow` | Use external API or node |
|
|
99
|
+
|
|
100
|
+
### Available Standard Library Modules
|
|
101
|
+
|
|
102
|
+
```python
|
|
103
|
+
# ✅ THESE WORK - Standard library only
|
|
104
|
+
import json # JSON parsing
|
|
105
|
+
import datetime # Date/time operations
|
|
106
|
+
import re # Regular expressions
|
|
107
|
+
import base64 # Base64 encoding
|
|
108
|
+
import hashlib # Hashing (MD5, SHA256)
|
|
109
|
+
import urllib.parse # URL parsing and encoding
|
|
110
|
+
import math # Math functions
|
|
111
|
+
import random # Random numbers
|
|
112
|
+
import statistics # Statistical functions
|
|
113
|
+
import collections # defaultdict, Counter, etc.
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Error #2: Empty Code / Missing Return
|
|
119
|
+
|
|
120
|
+
**Frequency**: Common across all Code nodes
|
|
121
|
+
|
|
122
|
+
**What it is**: Code node has no code or no return statement.
|
|
123
|
+
|
|
124
|
+
### The Problem
|
|
125
|
+
|
|
126
|
+
```python
|
|
127
|
+
# ❌ WRONG: Empty code
|
|
128
|
+
# (nothing here)
|
|
129
|
+
|
|
130
|
+
# ❌ WRONG: Code but no return
|
|
131
|
+
items = _input.all()
|
|
132
|
+
processed = [item for item in items if item["json"].get("active")]
|
|
133
|
+
# Forgot to return!
|
|
134
|
+
|
|
135
|
+
# ❌ WRONG: Return in wrong scope
|
|
136
|
+
if _input.all():
|
|
137
|
+
return [{"json": {"result": "success"}}]
|
|
138
|
+
# Return is inside if block - may not execute!
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
### The Solution
|
|
142
|
+
|
|
143
|
+
```python
|
|
144
|
+
# ✅ CORRECT: Always return
|
|
145
|
+
all_items = _input.all()
|
|
146
|
+
|
|
147
|
+
if not all_items:
|
|
148
|
+
# Return empty array or error
|
|
149
|
+
return [{"json": {"error": "No items"}}]
|
|
150
|
+
|
|
151
|
+
# Process items
|
|
152
|
+
processed = [item for item in all_items if item["json"].get("active")]
|
|
153
|
+
|
|
154
|
+
# Always return at the end
|
|
155
|
+
return processed if processed else [{"json": {"message": "No active items"}}]
|
|
156
|
+
```
|
|
157
|
+
|
|
158
|
+
### Best Practice
|
|
159
|
+
|
|
160
|
+
```python
|
|
161
|
+
# ✅ GOOD: Return at end of function (unconditional)
|
|
162
|
+
def process_items():
|
|
163
|
+
items = _input.all()
|
|
164
|
+
|
|
165
|
+
if not items:
|
|
166
|
+
return [{"json": {"error": "Empty input"}}]
|
|
167
|
+
|
|
168
|
+
# Process
|
|
169
|
+
result = []
|
|
170
|
+
for item in items:
|
|
171
|
+
result.append({"json": item["json"]})
|
|
172
|
+
|
|
173
|
+
return result
|
|
174
|
+
|
|
175
|
+
# Call function and return result
|
|
176
|
+
return process_items()
|
|
177
|
+
```
|
|
178
|
+
|
|
179
|
+
---
|
|
180
|
+
|
|
181
|
+
## Error #3: KeyError
|
|
182
|
+
|
|
183
|
+
**Frequency**: Very common in Python Code nodes
|
|
184
|
+
|
|
185
|
+
**What it is**: Accessing dictionary key that doesn't exist.
|
|
186
|
+
|
|
187
|
+
### The Problem
|
|
188
|
+
|
|
189
|
+
```python
|
|
190
|
+
# ❌ WRONG: Direct key access
|
|
191
|
+
item = _input.first()["json"]
|
|
192
|
+
|
|
193
|
+
name = item["name"] # KeyError if "name" doesn't exist!
|
|
194
|
+
email = item["email"] # KeyError if "email" doesn't exist!
|
|
195
|
+
age = item["age"] # KeyError if "age" doesn't exist!
|
|
196
|
+
|
|
197
|
+
return [{
|
|
198
|
+
"json": {
|
|
199
|
+
"name": name,
|
|
200
|
+
"email": email,
|
|
201
|
+
"age": age
|
|
202
|
+
}
|
|
203
|
+
}]
|
|
204
|
+
```
|
|
205
|
+
|
|
206
|
+
### Error Message
|
|
207
|
+
|
|
208
|
+
```
|
|
209
|
+
KeyError: 'name'
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### The Solution
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
# ✅ CORRECT: Use .get() with defaults
|
|
216
|
+
item = _input.first()["json"]
|
|
217
|
+
|
|
218
|
+
name = item.get("name", "Unknown")
|
|
219
|
+
email = item.get("email", "no-email@example.com")
|
|
220
|
+
age = item.get("age", 0)
|
|
221
|
+
|
|
222
|
+
return [{
|
|
223
|
+
"json": {
|
|
224
|
+
"name": name,
|
|
225
|
+
"email": email,
|
|
226
|
+
"age": age
|
|
227
|
+
}
|
|
228
|
+
}]
|
|
229
|
+
```
|
|
230
|
+
|
|
231
|
+
### Nested Dictionary Access
|
|
232
|
+
|
|
233
|
+
```python
|
|
234
|
+
# ❌ WRONG: Nested key access
|
|
235
|
+
webhook = _input.first()["json"]
|
|
236
|
+
name = webhook["body"]["user"]["name"] # Multiple KeyErrors possible!
|
|
237
|
+
|
|
238
|
+
# ✅ CORRECT: Safe nested access
|
|
239
|
+
webhook = _input.first()["json"]
|
|
240
|
+
body = webhook.get("body", {})
|
|
241
|
+
user = body.get("user", {})
|
|
242
|
+
name = user.get("name", "Unknown")
|
|
243
|
+
|
|
244
|
+
# ✅ ALSO CORRECT: Chained .get()
|
|
245
|
+
name = (
|
|
246
|
+
webhook
|
|
247
|
+
.get("body", {})
|
|
248
|
+
.get("user", {})
|
|
249
|
+
.get("name", "Unknown")
|
|
250
|
+
)
|
|
251
|
+
|
|
252
|
+
return [{"json": {"name": name}}]
|
|
253
|
+
```
|
|
254
|
+
|
|
255
|
+
### Webhook Body Access (Critical!)
|
|
256
|
+
|
|
257
|
+
```python
|
|
258
|
+
# ❌ WRONG: Forgetting webhook data is under "body"
|
|
259
|
+
webhook = _input.first()["json"]
|
|
260
|
+
name = webhook["name"] # KeyError!
|
|
261
|
+
email = webhook["email"] # KeyError!
|
|
262
|
+
|
|
263
|
+
# ✅ CORRECT: Access via ["body"]
|
|
264
|
+
webhook = _input.first()["json"]
|
|
265
|
+
body = webhook.get("body", {})
|
|
266
|
+
name = body.get("name", "Unknown")
|
|
267
|
+
email = body.get("email", "no-email")
|
|
268
|
+
|
|
269
|
+
return [{
|
|
270
|
+
"json": {
|
|
271
|
+
"name": name,
|
|
272
|
+
"email": email
|
|
273
|
+
}
|
|
274
|
+
}]
|
|
275
|
+
```
|
|
276
|
+
|
|
277
|
+
---
|
|
278
|
+
|
|
279
|
+
## Error #4: IndexError
|
|
280
|
+
|
|
281
|
+
**Frequency**: Common when processing arrays/lists
|
|
282
|
+
|
|
283
|
+
**What it is**: Accessing list index that doesn't exist.
|
|
284
|
+
|
|
285
|
+
### The Problem
|
|
286
|
+
|
|
287
|
+
```python
|
|
288
|
+
# ❌ WRONG: Assuming items exist
|
|
289
|
+
all_items = _input.all()
|
|
290
|
+
first_item = all_items[0] # IndexError if list is empty!
|
|
291
|
+
second_item = all_items[1] # IndexError if only 1 item!
|
|
292
|
+
|
|
293
|
+
return [{
|
|
294
|
+
"json": {
|
|
295
|
+
"first": first_item["json"],
|
|
296
|
+
"second": second_item["json"]
|
|
297
|
+
}
|
|
298
|
+
}]
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Error Message
|
|
302
|
+
|
|
303
|
+
```
|
|
304
|
+
IndexError: list index out of range
|
|
305
|
+
```
|
|
306
|
+
|
|
307
|
+
### The Solution
|
|
308
|
+
|
|
309
|
+
```python
|
|
310
|
+
# ✅ CORRECT: Check length first
|
|
311
|
+
all_items = _input.all()
|
|
312
|
+
|
|
313
|
+
if len(all_items) >= 2:
|
|
314
|
+
first_item = all_items[0]["json"]
|
|
315
|
+
second_item = all_items[1]["json"]
|
|
316
|
+
|
|
317
|
+
return [{
|
|
318
|
+
"json": {
|
|
319
|
+
"first": first_item,
|
|
320
|
+
"second": second_item
|
|
321
|
+
}
|
|
322
|
+
}]
|
|
323
|
+
else:
|
|
324
|
+
return [{
|
|
325
|
+
"json": {
|
|
326
|
+
"error": f"Expected 2+ items, got {len(all_items)}"
|
|
327
|
+
}
|
|
328
|
+
}]
|
|
329
|
+
```
|
|
330
|
+
|
|
331
|
+
### Safe First Item Access
|
|
332
|
+
|
|
333
|
+
```python
|
|
334
|
+
# ✅ CORRECT: Use _input.first() instead of [0]
|
|
335
|
+
# This is safer than manual indexing
|
|
336
|
+
first_item = _input.first()["json"]
|
|
337
|
+
|
|
338
|
+
return [{"json": first_item}]
|
|
339
|
+
|
|
340
|
+
# ✅ ALSO CORRECT: Check before accessing
|
|
341
|
+
all_items = _input.all()
|
|
342
|
+
if all_items:
|
|
343
|
+
first_item = all_items[0]["json"]
|
|
344
|
+
else:
|
|
345
|
+
first_item = {}
|
|
346
|
+
|
|
347
|
+
return [{"json": first_item}]
|
|
348
|
+
```
|
|
349
|
+
|
|
350
|
+
### Slice Instead of Index
|
|
351
|
+
|
|
352
|
+
```python
|
|
353
|
+
# ✅ CORRECT: Use slicing (never raises IndexError)
|
|
354
|
+
all_items = _input.all()
|
|
355
|
+
|
|
356
|
+
# Get first 5 items (won't fail if fewer than 5)
|
|
357
|
+
first_five = all_items[:5]
|
|
358
|
+
|
|
359
|
+
# Get items after first (won't fail if empty)
|
|
360
|
+
rest = all_items[1:]
|
|
361
|
+
|
|
362
|
+
return [{"json": item["json"]} for item in first_five]
|
|
363
|
+
```
|
|
364
|
+
|
|
365
|
+
---
|
|
366
|
+
|
|
367
|
+
## Error #5: Incorrect Return Format
|
|
368
|
+
|
|
369
|
+
**Frequency**: Common for new users
|
|
370
|
+
|
|
371
|
+
**What it is**: Returning data in wrong format (n8n expects array of objects with "json" key).
|
|
372
|
+
|
|
373
|
+
### The Problem
|
|
374
|
+
|
|
375
|
+
```python
|
|
376
|
+
# ❌ WRONG: Returning plain dictionary
|
|
377
|
+
return {"name": "Alice", "age": 30}
|
|
378
|
+
|
|
379
|
+
# ❌ WRONG: Returning array without "json" wrapper
|
|
380
|
+
return [{"name": "Alice"}, {"name": "Bob"}]
|
|
381
|
+
|
|
382
|
+
# ❌ WRONG: Returning None
|
|
383
|
+
return None
|
|
384
|
+
|
|
385
|
+
# ❌ WRONG: Returning string
|
|
386
|
+
return "success"
|
|
387
|
+
|
|
388
|
+
# ❌ WRONG: Returning single item (not array)
|
|
389
|
+
return {"json": {"name": "Alice"}}
|
|
390
|
+
```
|
|
391
|
+
|
|
392
|
+
### The Solution
|
|
393
|
+
|
|
394
|
+
```python
|
|
395
|
+
# ✅ CORRECT: Array of objects with "json" key
|
|
396
|
+
return [{"json": {"name": "Alice", "age": 30}}]
|
|
397
|
+
|
|
398
|
+
# ✅ CORRECT: Multiple items
|
|
399
|
+
return [
|
|
400
|
+
{"json": {"name": "Alice"}},
|
|
401
|
+
{"json": {"name": "Bob"}}
|
|
402
|
+
]
|
|
403
|
+
|
|
404
|
+
# ✅ CORRECT: Transform items
|
|
405
|
+
all_items = _input.all()
|
|
406
|
+
return [
|
|
407
|
+
{"json": item["json"]}
|
|
408
|
+
for item in all_items
|
|
409
|
+
]
|
|
410
|
+
|
|
411
|
+
# ✅ CORRECT: Empty array (valid)
|
|
412
|
+
return []
|
|
413
|
+
|
|
414
|
+
# ✅ CORRECT: Single item still needs array wrapper
|
|
415
|
+
return [{"json": {"result": "success"}}]
|
|
416
|
+
```
|
|
417
|
+
|
|
418
|
+
### Common Scenarios
|
|
419
|
+
|
|
420
|
+
**Scenario 1: Aggregation (Return Single Result)**
|
|
421
|
+
|
|
422
|
+
```python
|
|
423
|
+
# Calculate total
|
|
424
|
+
all_items = _input.all()
|
|
425
|
+
total = sum(item["json"].get("amount", 0) for item in all_items)
|
|
426
|
+
|
|
427
|
+
# ✅ CORRECT: Wrap in array with "json"
|
|
428
|
+
return [{
|
|
429
|
+
"json": {
|
|
430
|
+
"total": total,
|
|
431
|
+
"count": len(all_items)
|
|
432
|
+
}
|
|
433
|
+
}]
|
|
434
|
+
```
|
|
435
|
+
|
|
436
|
+
**Scenario 2: Filtering (Return Multiple Results)**
|
|
437
|
+
|
|
438
|
+
```python
|
|
439
|
+
# Filter active items
|
|
440
|
+
all_items = _input.all()
|
|
441
|
+
active = [item for item in all_items if item["json"].get("active")]
|
|
442
|
+
|
|
443
|
+
# ✅ CORRECT: Already in correct format
|
|
444
|
+
return active
|
|
445
|
+
|
|
446
|
+
# ✅ ALSO CORRECT: If transforming
|
|
447
|
+
return [
|
|
448
|
+
{"json": {**item["json"], "filtered": True}}
|
|
449
|
+
for item in active
|
|
450
|
+
]
|
|
451
|
+
```
|
|
452
|
+
|
|
453
|
+
**Scenario 3: No Results**
|
|
454
|
+
|
|
455
|
+
```python
|
|
456
|
+
# ✅ CORRECT: Return empty array
|
|
457
|
+
return []
|
|
458
|
+
|
|
459
|
+
# ✅ ALSO CORRECT: Return error message
|
|
460
|
+
return [{"json": {"error": "No results found"}}]
|
|
461
|
+
```
|
|
462
|
+
|
|
463
|
+
---
|
|
464
|
+
|
|
465
|
+
## Bonus Error: AttributeError
|
|
466
|
+
|
|
467
|
+
**What it is**: Using _input.item in wrong mode.
|
|
468
|
+
|
|
469
|
+
### The Problem
|
|
470
|
+
|
|
471
|
+
```python
|
|
472
|
+
# ❌ WRONG: Using _input.item in "All Items" mode
|
|
473
|
+
current = _input.item # None in "All Items" mode
|
|
474
|
+
data = current["json"] # AttributeError: 'NoneType' object has no attribute '__getitem__'
|
|
475
|
+
```
|
|
476
|
+
|
|
477
|
+
### The Solution
|
|
478
|
+
|
|
479
|
+
```python
|
|
480
|
+
# ✅ CORRECT: Check mode or use appropriate method
|
|
481
|
+
# In "All Items" mode, use:
|
|
482
|
+
all_items = _input.all()
|
|
483
|
+
|
|
484
|
+
# In "Each Item" mode, use:
|
|
485
|
+
current_item = _input.item
|
|
486
|
+
|
|
487
|
+
# ✅ SAFE: Check if item exists
|
|
488
|
+
current = _input.item
|
|
489
|
+
if current:
|
|
490
|
+
data = current["json"]
|
|
491
|
+
return [{"json": data}]
|
|
492
|
+
else:
|
|
493
|
+
# Running in "All Items" mode
|
|
494
|
+
return _input.all()
|
|
495
|
+
```
|
|
496
|
+
|
|
497
|
+
---
|
|
498
|
+
|
|
499
|
+
## Error Prevention Checklist
|
|
500
|
+
|
|
501
|
+
Before running your Python Code node, verify:
|
|
502
|
+
|
|
503
|
+
- [ ] **No external imports**: Only standard library (json, datetime, re, etc.)
|
|
504
|
+
- [ ] **Code returns data**: Every code path ends with `return`
|
|
505
|
+
- [ ] **Correct format**: Returns `[{"json": {...}}]` (array with "json" key)
|
|
506
|
+
- [ ] **Safe dictionary access**: Uses `.get()` instead of `[]` for dictionaries
|
|
507
|
+
- [ ] **Safe list access**: Checks length before indexing or uses slicing
|
|
508
|
+
- [ ] **Webhook body access**: Accesses webhook data via `_json["body"]`
|
|
509
|
+
- [ ] **No None returns**: Returns empty array `[]` instead of `None`
|
|
510
|
+
- [ ] **Mode awareness**: Uses `_input.all()`, `_input.first()`, or `_input.item` appropriately
|
|
511
|
+
|
|
512
|
+
---
|
|
513
|
+
|
|
514
|
+
## Quick Fix Reference
|
|
515
|
+
|
|
516
|
+
| Error | Quick Fix |
|
|
517
|
+
|-------|-----------|
|
|
518
|
+
| `ModuleNotFoundError` | Use JavaScript or HTTP Request node instead |
|
|
519
|
+
| `KeyError: 'field'` | Change `data["field"]` to `data.get("field", default)` |
|
|
520
|
+
| `IndexError: list index out of range` | Check `if len(items) > 0:` before `items[0]` |
|
|
521
|
+
| Empty output | Add `return [{"json": {...}}]` at end |
|
|
522
|
+
| `AttributeError: 'NoneType'` | Check mode setting or verify `_input.item` exists |
|
|
523
|
+
| Wrong format error | Wrap result: `return [{"json": result}]` |
|
|
524
|
+
| Webhook KeyError | Access via `_json.get("body", {})` |
|
|
525
|
+
|
|
526
|
+
---
|
|
527
|
+
|
|
528
|
+
## Testing Your Code
|
|
529
|
+
|
|
530
|
+
### Test Pattern 1: Handle Empty Input
|
|
531
|
+
|
|
532
|
+
```python
|
|
533
|
+
# ✅ Always test with empty input
|
|
534
|
+
all_items = _input.all()
|
|
535
|
+
|
|
536
|
+
if not all_items:
|
|
537
|
+
return [{"json": {"message": "No items to process"}}]
|
|
538
|
+
|
|
539
|
+
# Continue with processing
|
|
540
|
+
# ...
|
|
541
|
+
```
|
|
542
|
+
|
|
543
|
+
### Test Pattern 2: Test with Missing Fields
|
|
544
|
+
|
|
545
|
+
```python
|
|
546
|
+
# ✅ Use .get() with defaults
|
|
547
|
+
item = _input.first()["json"]
|
|
548
|
+
|
|
549
|
+
# These won't fail even if fields missing
|
|
550
|
+
name = item.get("name", "Unknown")
|
|
551
|
+
email = item.get("email", "no-email")
|
|
552
|
+
age = item.get("age", 0)
|
|
553
|
+
|
|
554
|
+
return [{"json": {"name": name, "email": email, "age": age}}]
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Test Pattern 3: Test Both Modes
|
|
558
|
+
|
|
559
|
+
```python
|
|
560
|
+
# ✅ Code that works in both modes
|
|
561
|
+
try:
|
|
562
|
+
# Try "Each Item" mode first
|
|
563
|
+
current = _input.item
|
|
564
|
+
if current:
|
|
565
|
+
return [{"json": current["json"]}]
|
|
566
|
+
except:
|
|
567
|
+
pass
|
|
568
|
+
|
|
569
|
+
# Fall back to "All Items" mode
|
|
570
|
+
all_items = _input.all()
|
|
571
|
+
return all_items if all_items else [{"json": {"message": "No data"}}]
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
---
|
|
575
|
+
|
|
576
|
+
## Summary
|
|
577
|
+
|
|
578
|
+
**Top 5 Errors to Avoid**:
|
|
579
|
+
1. **ModuleNotFoundError** - Use JavaScript or n8n nodes instead
|
|
580
|
+
2. **Missing return** - Always end with `return [{"json": {...}}]`
|
|
581
|
+
3. **KeyError** - Use `.get()` for dictionary access
|
|
582
|
+
4. **IndexError** - Check length before indexing
|
|
583
|
+
5. **Wrong format** - Return `[{"json": {...}}]`, not plain objects
|
|
584
|
+
|
|
585
|
+
**Golden Rules**:
|
|
586
|
+
- NO external libraries (use JavaScript instead)
|
|
587
|
+
- ALWAYS use `.get()` for dictionaries
|
|
588
|
+
- ALWAYS return `[{"json": {...}}]` format
|
|
589
|
+
- CHECK lengths before list access
|
|
590
|
+
- ACCESS webhook data via `["body"]`
|
|
591
|
+
|
|
592
|
+
**Remember**:
|
|
593
|
+
- JavaScript is recommended for 95% of use cases
|
|
594
|
+
- Python has limitations (no requests, pandas, numpy)
|
|
595
|
+
- Use n8n nodes for complex operations
|
|
596
|
+
|
|
597
|
+
**See Also**:
|
|
598
|
+
- [SKILL.md](SKILL.md) - Python Code overview
|
|
599
|
+
- [DATA_ACCESS.md](DATA_ACCESS.md) - Data access patterns
|
|
600
|
+
- [STANDARD_LIBRARY.md](STANDARD_LIBRARY.md) - Available modules
|
|
601
|
+
- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Production patterns
|