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,702 @@
|
|
|
1
|
+
# Data Access Patterns - Python Code Node
|
|
2
|
+
|
|
3
|
+
Complete guide to accessing data in n8n Code nodes using Python.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Overview
|
|
8
|
+
|
|
9
|
+
In n8n Python Code nodes, you access data using **underscore-prefixed** variables: `_input`, `_json`, `_node`.
|
|
10
|
+
|
|
11
|
+
**Data Access Priority** (by common usage):
|
|
12
|
+
1. **`_input.all()`** - Most common - Batch operations, aggregations
|
|
13
|
+
2. **`_input.first()`** - Very common - Single item operations
|
|
14
|
+
3. **`_input.item`** - Common - Each Item mode only
|
|
15
|
+
4. **`_node["NodeName"]["json"]`** - Specific node references
|
|
16
|
+
5. **`_json`** - Direct current item (use `_input` instead)
|
|
17
|
+
|
|
18
|
+
**Python vs JavaScript**:
|
|
19
|
+
| JavaScript | Python (Beta) | Python (Native) |
|
|
20
|
+
|------------|---------------|-----------------|
|
|
21
|
+
| `$input.all()` | `_input.all()` | `_items` |
|
|
22
|
+
| `$input.first()` | `_input.first()` | `_items[0]` |
|
|
23
|
+
| `$input.item` | `_input.item` | `_item` |
|
|
24
|
+
| `$json` | `_json` | `_item["json"]` |
|
|
25
|
+
| `$node["Name"]` | `_node["Name"]` | Not available |
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Pattern 1: _input.all() - Process All Items
|
|
30
|
+
|
|
31
|
+
**Usage**: Most common pattern for batch processing
|
|
32
|
+
|
|
33
|
+
**When to use:**
|
|
34
|
+
- Processing multiple records
|
|
35
|
+
- Aggregating data (sum, count, average)
|
|
36
|
+
- Filtering lists
|
|
37
|
+
- Transforming datasets
|
|
38
|
+
|
|
39
|
+
### Basic Usage
|
|
40
|
+
|
|
41
|
+
```python
|
|
42
|
+
# Get all items from previous node
|
|
43
|
+
all_items = _input.all()
|
|
44
|
+
|
|
45
|
+
# all_items is a list of dictionaries like:
|
|
46
|
+
# [
|
|
47
|
+
# {"json": {"id": 1, "name": "Alice"}},
|
|
48
|
+
# {"json": {"id": 2, "name": "Bob"}}
|
|
49
|
+
# ]
|
|
50
|
+
|
|
51
|
+
print(f"Received {len(all_items)} items")
|
|
52
|
+
|
|
53
|
+
return all_items
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
### Example 1: Filter Active Items
|
|
57
|
+
|
|
58
|
+
```python
|
|
59
|
+
all_items = _input.all()
|
|
60
|
+
|
|
61
|
+
# Filter only active items
|
|
62
|
+
active_items = [
|
|
63
|
+
item for item in all_items
|
|
64
|
+
if item["json"].get("status") == "active"
|
|
65
|
+
]
|
|
66
|
+
|
|
67
|
+
return active_items
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
### Example 2: Transform All Items
|
|
71
|
+
|
|
72
|
+
```python
|
|
73
|
+
all_items = _input.all()
|
|
74
|
+
|
|
75
|
+
# Transform to new structure
|
|
76
|
+
transformed = []
|
|
77
|
+
for item in all_items:
|
|
78
|
+
transformed.append({
|
|
79
|
+
"json": {
|
|
80
|
+
"id": item["json"].get("id"),
|
|
81
|
+
"full_name": f"{item['json'].get('first_name', '')} {item['json'].get('last_name', '')}",
|
|
82
|
+
"email": item["json"].get("email"),
|
|
83
|
+
"processed_at": datetime.now().isoformat()
|
|
84
|
+
}
|
|
85
|
+
})
|
|
86
|
+
|
|
87
|
+
return transformed
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Example 3: Aggregate Data
|
|
91
|
+
|
|
92
|
+
```python
|
|
93
|
+
all_items = _input.all()
|
|
94
|
+
|
|
95
|
+
# Calculate total
|
|
96
|
+
total = sum(item["json"].get("amount", 0) for item in all_items)
|
|
97
|
+
|
|
98
|
+
return [{
|
|
99
|
+
"json": {
|
|
100
|
+
"total": total,
|
|
101
|
+
"count": len(all_items),
|
|
102
|
+
"average": total / len(all_items) if all_items else 0
|
|
103
|
+
}
|
|
104
|
+
}]
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Example 4: Sort and Limit
|
|
108
|
+
|
|
109
|
+
```python
|
|
110
|
+
all_items = _input.all()
|
|
111
|
+
|
|
112
|
+
# Get top 5 by score
|
|
113
|
+
sorted_items = sorted(
|
|
114
|
+
all_items,
|
|
115
|
+
key=lambda item: item["json"].get("score", 0),
|
|
116
|
+
reverse=True
|
|
117
|
+
)
|
|
118
|
+
top_five = sorted_items[:5]
|
|
119
|
+
|
|
120
|
+
return [{"json": item["json"]} for item in top_five]
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
### Example 5: Group By Category
|
|
124
|
+
|
|
125
|
+
```python
|
|
126
|
+
all_items = _input.all()
|
|
127
|
+
|
|
128
|
+
# Group items by category
|
|
129
|
+
grouped = {}
|
|
130
|
+
for item in all_items:
|
|
131
|
+
category = item["json"].get("category", "Uncategorized")
|
|
132
|
+
|
|
133
|
+
if category not in grouped:
|
|
134
|
+
grouped[category] = []
|
|
135
|
+
|
|
136
|
+
grouped[category].append(item["json"])
|
|
137
|
+
|
|
138
|
+
# Convert to list format
|
|
139
|
+
return [
|
|
140
|
+
{
|
|
141
|
+
"json": {
|
|
142
|
+
"category": category,
|
|
143
|
+
"items": items,
|
|
144
|
+
"count": len(items)
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
for category, items in grouped.items()
|
|
148
|
+
]
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
### Example 6: Deduplicate by ID
|
|
152
|
+
|
|
153
|
+
```python
|
|
154
|
+
all_items = _input.all()
|
|
155
|
+
|
|
156
|
+
# Remove duplicates by ID
|
|
157
|
+
seen = set()
|
|
158
|
+
unique = []
|
|
159
|
+
|
|
160
|
+
for item in all_items:
|
|
161
|
+
item_id = item["json"].get("id")
|
|
162
|
+
|
|
163
|
+
if item_id and item_id not in seen:
|
|
164
|
+
seen.add(item_id)
|
|
165
|
+
unique.append(item)
|
|
166
|
+
|
|
167
|
+
return unique
|
|
168
|
+
```
|
|
169
|
+
|
|
170
|
+
---
|
|
171
|
+
|
|
172
|
+
## Pattern 2: _input.first() - Get First Item
|
|
173
|
+
|
|
174
|
+
**Usage**: Very common for single-item operations
|
|
175
|
+
|
|
176
|
+
**When to use:**
|
|
177
|
+
- Previous node returns single object
|
|
178
|
+
- Working with API responses
|
|
179
|
+
- Getting initial/first data point
|
|
180
|
+
|
|
181
|
+
### Basic Usage
|
|
182
|
+
|
|
183
|
+
```python
|
|
184
|
+
# Get first item from previous node
|
|
185
|
+
first_item = _input.first()
|
|
186
|
+
|
|
187
|
+
# Access the JSON data
|
|
188
|
+
data = first_item["json"]
|
|
189
|
+
|
|
190
|
+
print(f"First item: {data}")
|
|
191
|
+
|
|
192
|
+
return [{"json": data}]
|
|
193
|
+
```
|
|
194
|
+
|
|
195
|
+
### Example 1: Process Single API Response
|
|
196
|
+
|
|
197
|
+
```python
|
|
198
|
+
# Get API response (typically single object)
|
|
199
|
+
response = _input.first()["json"]
|
|
200
|
+
|
|
201
|
+
# Extract what you need
|
|
202
|
+
return [{
|
|
203
|
+
"json": {
|
|
204
|
+
"user_id": response.get("data", {}).get("user", {}).get("id"),
|
|
205
|
+
"user_name": response.get("data", {}).get("user", {}).get("name"),
|
|
206
|
+
"status": response.get("status"),
|
|
207
|
+
"fetched_at": datetime.now().isoformat()
|
|
208
|
+
}
|
|
209
|
+
}]
|
|
210
|
+
```
|
|
211
|
+
|
|
212
|
+
### Example 2: Transform Single Object
|
|
213
|
+
|
|
214
|
+
```python
|
|
215
|
+
data = _input.first()["json"]
|
|
216
|
+
|
|
217
|
+
# Transform structure
|
|
218
|
+
return [{
|
|
219
|
+
"json": {
|
|
220
|
+
"id": data.get("id"),
|
|
221
|
+
"contact": {
|
|
222
|
+
"email": data.get("email"),
|
|
223
|
+
"phone": data.get("phone")
|
|
224
|
+
},
|
|
225
|
+
"address": {
|
|
226
|
+
"street": data.get("street"),
|
|
227
|
+
"city": data.get("city"),
|
|
228
|
+
"zip": data.get("zip")
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
}]
|
|
232
|
+
```
|
|
233
|
+
|
|
234
|
+
### Example 3: Validate Single Item
|
|
235
|
+
|
|
236
|
+
```python
|
|
237
|
+
item = _input.first()["json"]
|
|
238
|
+
|
|
239
|
+
# Validation logic
|
|
240
|
+
is_valid = bool(item.get("email") and "@" in item.get("email", ""))
|
|
241
|
+
|
|
242
|
+
return [{
|
|
243
|
+
"json": {
|
|
244
|
+
**item,
|
|
245
|
+
"valid": is_valid,
|
|
246
|
+
"validated_at": datetime.now().isoformat()
|
|
247
|
+
}
|
|
248
|
+
}]
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
### Example 4: Extract Nested Data
|
|
252
|
+
|
|
253
|
+
```python
|
|
254
|
+
response = _input.first()["json"]
|
|
255
|
+
|
|
256
|
+
# Navigate nested structure
|
|
257
|
+
users = response.get("data", {}).get("users", [])
|
|
258
|
+
|
|
259
|
+
return [
|
|
260
|
+
{
|
|
261
|
+
"json": {
|
|
262
|
+
"id": user.get("id"),
|
|
263
|
+
"name": user.get("profile", {}).get("name", "Unknown"),
|
|
264
|
+
"email": user.get("contact", {}).get("email", "no-email")
|
|
265
|
+
}
|
|
266
|
+
}
|
|
267
|
+
for user in users
|
|
268
|
+
]
|
|
269
|
+
```
|
|
270
|
+
|
|
271
|
+
---
|
|
272
|
+
|
|
273
|
+
## Pattern 3: _input.item - Current Item (Each Item Mode)
|
|
274
|
+
|
|
275
|
+
**Usage**: Common in "Run Once for Each Item" mode
|
|
276
|
+
|
|
277
|
+
**When to use:**
|
|
278
|
+
- Mode is set to "Run Once for Each Item"
|
|
279
|
+
- Need to process items independently
|
|
280
|
+
- Per-item API calls or validations
|
|
281
|
+
|
|
282
|
+
**IMPORTANT**: Only use in "Each Item" mode. Will be undefined in "All Items" mode.
|
|
283
|
+
|
|
284
|
+
### Basic Usage
|
|
285
|
+
|
|
286
|
+
```python
|
|
287
|
+
# In "Run Once for Each Item" mode
|
|
288
|
+
current_item = _input.item
|
|
289
|
+
data = current_item["json"]
|
|
290
|
+
|
|
291
|
+
print(f"Processing item: {data.get('id')}")
|
|
292
|
+
|
|
293
|
+
return [{
|
|
294
|
+
"json": {
|
|
295
|
+
**data,
|
|
296
|
+
"processed": True
|
|
297
|
+
}
|
|
298
|
+
}]
|
|
299
|
+
```
|
|
300
|
+
|
|
301
|
+
### Example 1: Add Processing Metadata
|
|
302
|
+
|
|
303
|
+
```python
|
|
304
|
+
item = _input.item
|
|
305
|
+
|
|
306
|
+
return [{
|
|
307
|
+
"json": {
|
|
308
|
+
**item["json"],
|
|
309
|
+
"processed": True,
|
|
310
|
+
"processed_at": datetime.now().isoformat(),
|
|
311
|
+
"processing_duration": random.random() * 1000 # Simulated
|
|
312
|
+
}
|
|
313
|
+
}]
|
|
314
|
+
```
|
|
315
|
+
|
|
316
|
+
### Example 2: Per-Item Validation
|
|
317
|
+
|
|
318
|
+
```python
|
|
319
|
+
item = _input.item
|
|
320
|
+
data = item["json"]
|
|
321
|
+
|
|
322
|
+
# Validate this specific item
|
|
323
|
+
errors = []
|
|
324
|
+
|
|
325
|
+
if not data.get("email"):
|
|
326
|
+
errors.append("Email required")
|
|
327
|
+
if not data.get("name"):
|
|
328
|
+
errors.append("Name required")
|
|
329
|
+
if data.get("age") and data["age"] < 18:
|
|
330
|
+
errors.append("Must be 18+")
|
|
331
|
+
|
|
332
|
+
return [{
|
|
333
|
+
"json": {
|
|
334
|
+
**data,
|
|
335
|
+
"valid": len(errors) == 0,
|
|
336
|
+
"errors": errors if errors else None
|
|
337
|
+
}
|
|
338
|
+
}]
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
### Example 3: Conditional Processing
|
|
342
|
+
|
|
343
|
+
```python
|
|
344
|
+
item = _input.item
|
|
345
|
+
data = item["json"]
|
|
346
|
+
|
|
347
|
+
# Process based on item type
|
|
348
|
+
if data.get("type") == "premium":
|
|
349
|
+
return [{
|
|
350
|
+
"json": {
|
|
351
|
+
**data,
|
|
352
|
+
"discount": 0.20,
|
|
353
|
+
"tier": "premium"
|
|
354
|
+
}
|
|
355
|
+
}]
|
|
356
|
+
else:
|
|
357
|
+
return [{
|
|
358
|
+
"json": {
|
|
359
|
+
**data,
|
|
360
|
+
"discount": 0.05,
|
|
361
|
+
"tier": "standard"
|
|
362
|
+
}
|
|
363
|
+
}]
|
|
364
|
+
```
|
|
365
|
+
|
|
366
|
+
---
|
|
367
|
+
|
|
368
|
+
## Pattern 4: _node - Reference Other Nodes
|
|
369
|
+
|
|
370
|
+
**Usage**: Less common, but powerful for specific scenarios
|
|
371
|
+
|
|
372
|
+
**When to use:**
|
|
373
|
+
- Need data from specific named node
|
|
374
|
+
- Combining data from multiple nodes
|
|
375
|
+
|
|
376
|
+
### Basic Usage
|
|
377
|
+
|
|
378
|
+
```python
|
|
379
|
+
# Get output from specific node
|
|
380
|
+
webhook_data = _node["Webhook"]["json"]
|
|
381
|
+
api_data = _node["HTTP Request"]["json"]
|
|
382
|
+
|
|
383
|
+
return [{
|
|
384
|
+
"json": {
|
|
385
|
+
"from_webhook": webhook_data,
|
|
386
|
+
"from_api": api_data
|
|
387
|
+
}
|
|
388
|
+
}]
|
|
389
|
+
```
|
|
390
|
+
|
|
391
|
+
### Example 1: Combine Multiple Sources
|
|
392
|
+
|
|
393
|
+
```python
|
|
394
|
+
# Reference multiple nodes
|
|
395
|
+
webhook = _node["Webhook"]["json"]
|
|
396
|
+
database = _node["Postgres"]["json"]
|
|
397
|
+
api = _node["HTTP Request"]["json"]
|
|
398
|
+
|
|
399
|
+
return [{
|
|
400
|
+
"json": {
|
|
401
|
+
"combined": {
|
|
402
|
+
"webhook": webhook.get("body", {}),
|
|
403
|
+
"db_records": len(database) if isinstance(database, list) else 1,
|
|
404
|
+
"api_response": api.get("status")
|
|
405
|
+
},
|
|
406
|
+
"processed_at": datetime.now().isoformat()
|
|
407
|
+
}
|
|
408
|
+
}]
|
|
409
|
+
```
|
|
410
|
+
|
|
411
|
+
### Example 2: Compare Across Nodes
|
|
412
|
+
|
|
413
|
+
```python
|
|
414
|
+
old_data = _node["Get Old Data"]["json"]
|
|
415
|
+
new_data = _node["Get New Data"]["json"]
|
|
416
|
+
|
|
417
|
+
# Simple comparison
|
|
418
|
+
changes = {
|
|
419
|
+
"added": [n for n in new_data if n.get("id") not in [o.get("id") for o in old_data]],
|
|
420
|
+
"removed": [o for o in old_data if o.get("id") not in [n.get("id") for n in new_data]]
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return [{
|
|
424
|
+
"json": {
|
|
425
|
+
"changes": changes,
|
|
426
|
+
"summary": {
|
|
427
|
+
"added": len(changes["added"]),
|
|
428
|
+
"removed": len(changes["removed"])
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
}]
|
|
432
|
+
```
|
|
433
|
+
|
|
434
|
+
---
|
|
435
|
+
|
|
436
|
+
## Critical: Webhook Data Structure
|
|
437
|
+
|
|
438
|
+
**MOST COMMON MISTAKE**: Forgetting webhook data is nested under `["body"]`
|
|
439
|
+
|
|
440
|
+
### The Problem
|
|
441
|
+
|
|
442
|
+
Webhook node wraps all incoming data under a `"body"` property.
|
|
443
|
+
|
|
444
|
+
### Structure
|
|
445
|
+
|
|
446
|
+
```python
|
|
447
|
+
# Webhook node output structure:
|
|
448
|
+
{
|
|
449
|
+
"headers": {
|
|
450
|
+
"content-type": "application/json",
|
|
451
|
+
"user-agent": "..."
|
|
452
|
+
},
|
|
453
|
+
"params": {},
|
|
454
|
+
"query": {},
|
|
455
|
+
"body": {
|
|
456
|
+
# ← YOUR DATA IS HERE
|
|
457
|
+
"name": "Alice",
|
|
458
|
+
"email": "alice@example.com",
|
|
459
|
+
"message": "Hello!"
|
|
460
|
+
}
|
|
461
|
+
}
|
|
462
|
+
```
|
|
463
|
+
|
|
464
|
+
### Wrong vs Right
|
|
465
|
+
|
|
466
|
+
```python
|
|
467
|
+
# ❌ WRONG: Trying to access directly
|
|
468
|
+
name = _json["name"] # KeyError!
|
|
469
|
+
email = _json["email"] # KeyError!
|
|
470
|
+
|
|
471
|
+
# ✅ CORRECT: Access via ["body"]
|
|
472
|
+
name = _json["body"]["name"] # "Alice"
|
|
473
|
+
email = _json["body"]["email"] # "alice@example.com"
|
|
474
|
+
|
|
475
|
+
# ✅ SAFER: Use .get() for safe access
|
|
476
|
+
webhook_data = _json.get("body", {})
|
|
477
|
+
name = webhook_data.get("name") # None if missing
|
|
478
|
+
email = webhook_data.get("email", "no-email") # Default value
|
|
479
|
+
```
|
|
480
|
+
|
|
481
|
+
### Example: Full Webhook Processing
|
|
482
|
+
|
|
483
|
+
```python
|
|
484
|
+
# Get webhook data from previous node
|
|
485
|
+
webhook_output = _input.first()["json"]
|
|
486
|
+
|
|
487
|
+
# Access the actual payload
|
|
488
|
+
payload = webhook_output.get("body", {})
|
|
489
|
+
|
|
490
|
+
# Access headers if needed
|
|
491
|
+
content_type = webhook_output.get("headers", {}).get("content-type")
|
|
492
|
+
|
|
493
|
+
# Access query parameters if needed
|
|
494
|
+
api_key = webhook_output.get("query", {}).get("api_key")
|
|
495
|
+
|
|
496
|
+
# Process the actual data
|
|
497
|
+
return [{
|
|
498
|
+
"json": {
|
|
499
|
+
# Data from webhook body
|
|
500
|
+
"user_name": payload.get("name"),
|
|
501
|
+
"user_email": payload.get("email"),
|
|
502
|
+
"message": payload.get("message"),
|
|
503
|
+
|
|
504
|
+
# Metadata
|
|
505
|
+
"received_at": datetime.now().isoformat(),
|
|
506
|
+
"content_type": content_type,
|
|
507
|
+
"authenticated": bool(api_key)
|
|
508
|
+
}
|
|
509
|
+
}]
|
|
510
|
+
```
|
|
511
|
+
|
|
512
|
+
### POST Data, Query Params, and Headers
|
|
513
|
+
|
|
514
|
+
```python
|
|
515
|
+
webhook = _input.first()["json"]
|
|
516
|
+
|
|
517
|
+
return [{
|
|
518
|
+
"json": {
|
|
519
|
+
# POST body data
|
|
520
|
+
"form_data": webhook.get("body", {}),
|
|
521
|
+
|
|
522
|
+
# Query parameters (?key=value)
|
|
523
|
+
"query_params": webhook.get("query", {}),
|
|
524
|
+
|
|
525
|
+
# HTTP headers
|
|
526
|
+
"user_agent": webhook.get("headers", {}).get("user-agent"),
|
|
527
|
+
"content_type": webhook.get("headers", {}).get("content-type"),
|
|
528
|
+
|
|
529
|
+
# Request metadata
|
|
530
|
+
"method": webhook.get("method"), # POST, GET, etc.
|
|
531
|
+
"url": webhook.get("url")
|
|
532
|
+
}
|
|
533
|
+
}]
|
|
534
|
+
```
|
|
535
|
+
|
|
536
|
+
---
|
|
537
|
+
|
|
538
|
+
## Choosing the Right Pattern
|
|
539
|
+
|
|
540
|
+
### Decision Tree
|
|
541
|
+
|
|
542
|
+
```
|
|
543
|
+
Do you need ALL items from previous node?
|
|
544
|
+
├─ YES → Use _input.all()
|
|
545
|
+
│
|
|
546
|
+
└─ NO → Do you need just the FIRST item?
|
|
547
|
+
├─ YES → Use _input.first()
|
|
548
|
+
│
|
|
549
|
+
└─ NO → Are you in "Each Item" mode?
|
|
550
|
+
├─ YES → Use _input.item
|
|
551
|
+
│
|
|
552
|
+
└─ NO → Do you need specific node data?
|
|
553
|
+
├─ YES → Use _node["NodeName"]
|
|
554
|
+
└─ NO → Use _input.first() (default)
|
|
555
|
+
```
|
|
556
|
+
|
|
557
|
+
### Quick Reference Table
|
|
558
|
+
|
|
559
|
+
| Scenario | Use This | Example |
|
|
560
|
+
|----------|----------|---------|
|
|
561
|
+
| Sum all amounts | `_input.all()` | `sum(i["json"].get("amount", 0) for i in items)` |
|
|
562
|
+
| Get API response | `_input.first()` | `_input.first()["json"].get("data")` |
|
|
563
|
+
| Process each independently | `_input.item` | `_input.item["json"]` (Each Item mode) |
|
|
564
|
+
| Combine two nodes | `_node["Name"]` | `_node["API"]["json"]` |
|
|
565
|
+
| Filter list | `_input.all()` | `[i for i in items if i["json"].get("active")]` |
|
|
566
|
+
| Transform single object | `_input.first()` | `{**_input.first()["json"], "new": True}` |
|
|
567
|
+
| Webhook data | `_input.first()` | `_input.first()["json"]["body"]` |
|
|
568
|
+
|
|
569
|
+
---
|
|
570
|
+
|
|
571
|
+
## Common Mistakes
|
|
572
|
+
|
|
573
|
+
### Mistake 1: Using _json Without Context
|
|
574
|
+
|
|
575
|
+
```python
|
|
576
|
+
# ❌ RISKY: _json is ambiguous
|
|
577
|
+
value = _json["field"]
|
|
578
|
+
|
|
579
|
+
# ✅ CLEAR: Be explicit
|
|
580
|
+
value = _input.first()["json"]["field"]
|
|
581
|
+
```
|
|
582
|
+
|
|
583
|
+
### Mistake 2: Forgetting ["json"] Property
|
|
584
|
+
|
|
585
|
+
```python
|
|
586
|
+
# ❌ WRONG: Trying to access fields on item dictionary
|
|
587
|
+
items = _input.all()
|
|
588
|
+
names = [item["name"] for item in items] # KeyError!
|
|
589
|
+
|
|
590
|
+
# ✅ CORRECT: Access via ["json"]
|
|
591
|
+
names = [item["json"]["name"] for item in items]
|
|
592
|
+
```
|
|
593
|
+
|
|
594
|
+
### Mistake 3: Using _input.item in All Items Mode
|
|
595
|
+
|
|
596
|
+
```python
|
|
597
|
+
# ❌ WRONG: _input.item is None in "All Items" mode
|
|
598
|
+
data = _input.item["json"] # AttributeError!
|
|
599
|
+
|
|
600
|
+
# ✅ CORRECT: Use appropriate method
|
|
601
|
+
data = _input.first()["json"] # Or _input.all()
|
|
602
|
+
```
|
|
603
|
+
|
|
604
|
+
### Mistake 4: Not Handling Empty Lists
|
|
605
|
+
|
|
606
|
+
```python
|
|
607
|
+
# ❌ WRONG: Crashes if no items
|
|
608
|
+
first = _input.all()[0]["json"] # IndexError!
|
|
609
|
+
|
|
610
|
+
# ✅ CORRECT: Check length first
|
|
611
|
+
items = _input.all()
|
|
612
|
+
if items:
|
|
613
|
+
first = items[0]["json"]
|
|
614
|
+
else:
|
|
615
|
+
return []
|
|
616
|
+
|
|
617
|
+
# ✅ ALSO CORRECT: Use _input.first()
|
|
618
|
+
first = _input.first()["json"] # Built-in safety
|
|
619
|
+
```
|
|
620
|
+
|
|
621
|
+
### Mistake 5: Direct Dictionary Access (KeyError)
|
|
622
|
+
|
|
623
|
+
```python
|
|
624
|
+
# ❌ RISKY: Crashes if key missing
|
|
625
|
+
value = item["json"]["field"] # KeyError!
|
|
626
|
+
|
|
627
|
+
# ✅ SAFE: Use .get()
|
|
628
|
+
value = item["json"].get("field", "default")
|
|
629
|
+
```
|
|
630
|
+
|
|
631
|
+
---
|
|
632
|
+
|
|
633
|
+
## Advanced Patterns
|
|
634
|
+
|
|
635
|
+
### Pattern: Safe Nested Access
|
|
636
|
+
|
|
637
|
+
```python
|
|
638
|
+
# Deep nested access with .get()
|
|
639
|
+
value = (
|
|
640
|
+
_input.first()["json"]
|
|
641
|
+
.get("level1", {})
|
|
642
|
+
.get("level2", {})
|
|
643
|
+
.get("level3", "default")
|
|
644
|
+
)
|
|
645
|
+
```
|
|
646
|
+
|
|
647
|
+
### Pattern: List Comprehension with Filtering
|
|
648
|
+
|
|
649
|
+
```python
|
|
650
|
+
items = _input.all()
|
|
651
|
+
|
|
652
|
+
# Filter and transform in one step
|
|
653
|
+
result = [
|
|
654
|
+
{
|
|
655
|
+
"json": {
|
|
656
|
+
"id": item["json"]["id"],
|
|
657
|
+
"name": item["json"]["name"].upper()
|
|
658
|
+
}
|
|
659
|
+
}
|
|
660
|
+
for item in items
|
|
661
|
+
if item["json"].get("active") and item["json"].get("verified")
|
|
662
|
+
]
|
|
663
|
+
|
|
664
|
+
return result
|
|
665
|
+
```
|
|
666
|
+
|
|
667
|
+
### Pattern: Dictionary Comprehension
|
|
668
|
+
|
|
669
|
+
```python
|
|
670
|
+
items = _input.all()
|
|
671
|
+
|
|
672
|
+
# Create lookup dictionary
|
|
673
|
+
lookup = {
|
|
674
|
+
item["json"]["id"]: item["json"]
|
|
675
|
+
for item in items
|
|
676
|
+
if "id" in item["json"]
|
|
677
|
+
}
|
|
678
|
+
|
|
679
|
+
return [{"json": lookup}]
|
|
680
|
+
```
|
|
681
|
+
|
|
682
|
+
---
|
|
683
|
+
|
|
684
|
+
## Summary
|
|
685
|
+
|
|
686
|
+
**Most Common Patterns**:
|
|
687
|
+
1. `_input.all()` - Process multiple items, batch operations
|
|
688
|
+
2. `_input.first()` - Single item, API responses
|
|
689
|
+
3. `_input.item` - Each Item mode processing
|
|
690
|
+
|
|
691
|
+
**Critical Rule**:
|
|
692
|
+
- Webhook data is under `["body"]` property
|
|
693
|
+
|
|
694
|
+
**Best Practice**:
|
|
695
|
+
- Use `.get()` for dictionary access to avoid KeyError
|
|
696
|
+
- Always check for empty lists
|
|
697
|
+
- Be explicit: Use `_input.first()["json"]["field"]` instead of `_json["field"]`
|
|
698
|
+
|
|
699
|
+
**See Also**:
|
|
700
|
+
- [SKILL.md](SKILL.md) - Overview and quick start
|
|
701
|
+
- [COMMON_PATTERNS.md](COMMON_PATTERNS.md) - Python-specific patterns
|
|
702
|
+
- [ERROR_PATTERNS.md](ERROR_PATTERNS.md) - Avoid common mistakes
|