kailash 0.1.4__py3-none-any.whl → 0.1.5__py3-none-any.whl

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.
kailash/nodes/api/http.py CHANGED
@@ -57,33 +57,114 @@ class HTTPResponse(BaseModel):
57
57
 
58
58
  @register_node()
59
59
  class HTTPRequestNode(Node):
60
- """Enhanced node for making HTTP requests to external APIs.
61
-
62
- This node provides a flexible interface for making HTTP requests with support for:
63
- * All common HTTP methods (GET, POST, PUT, PATCH, DELETE, HEAD, OPTIONS)
64
- * Multiple authentication methods (Bearer, Basic, API Key, OAuth2)
65
- * JSON, form, and multipart request bodies
66
- * Custom headers and query parameters
67
- * Response parsing (JSON, text, binary)
68
- * Error handling and retries with recovery suggestions
69
- * Rate limiting support
70
- * Request/response logging
71
-
72
- Design Purpose:
73
- * Enable workflow integration with external HTTP APIs
74
- * Provide a consistent interface for HTTP operations
75
- * Support common authentication patterns
76
- * Handle response parsing and error handling
77
- * Offer enterprise-grade features like rate limiting
78
-
79
- Upstream Usage:
80
- * Workflow: Creates and configures node for API integration
81
- * Specialized API nodes: May extend this node for specific APIs
60
+ """
61
+ Enhanced node for making HTTP requests to external APIs.
62
+
63
+ This node provides a comprehensive HTTP client with enterprise-grade features for
64
+ integrating external APIs into Kailash workflows. It supports all common HTTP
65
+ operations with built-in authentication, error handling, and response parsing,
66
+ making it the foundation for API integration in the SDK.
67
+
68
+ Design Philosophy:
69
+ The HTTPRequestNode embodies the principle of "API integration made simple."
70
+ It abstracts the complexity of HTTP operations behind a clean interface while
71
+ providing advanced features when needed. The design prioritizes flexibility,
72
+ reliability, and ease of use, supporting everything from simple REST calls
73
+ to complex authentication flows and multipart uploads.
74
+
75
+ Upstream Dependencies:
76
+ - Workflow orchestrators configuring API endpoints
77
+ - Authentication nodes providing credentials
78
+ - Configuration systems supplying API settings
79
+ - Data transformation nodes preparing request payloads
80
+ - Rate limiting controllers managing API quotas
82
81
 
83
82
  Downstream Consumers:
84
- * Data processing nodes: Consume API response data
85
- * Decision nodes: Route workflow based on API responses
86
- * Custom nodes: Process API-specific data formats
83
+ - Data processing nodes consuming API responses
84
+ - Decision nodes routing based on HTTP status
85
+ - Error handling nodes managing failures
86
+ - Caching nodes storing API results
87
+ - Analytics nodes tracking API usage
88
+
89
+ Configuration:
90
+ The node supports extensive configuration options:
91
+ - URL with template variable support
92
+ - All standard HTTP methods
93
+ - Custom headers and query parameters
94
+ - Multiple body formats (JSON, form, multipart)
95
+ - Authentication methods (Bearer, Basic, API Key, OAuth2)
96
+ - Timeout and retry settings
97
+ - Response format preferences
98
+
99
+ Implementation Details:
100
+ - Uses requests library for synchronous operations
101
+ - Automatic response format detection based on Content-Type
102
+ - Built-in JSON parsing with error handling
103
+ - Support for binary responses (files, images)
104
+ - Connection pooling for performance
105
+ - Comprehensive error messages with recovery hints
106
+ - Optional request/response logging
107
+ - Metrics collection for monitoring
108
+
109
+ Error Handling:
110
+ - Connection errors with retry suggestions
111
+ - Timeout handling with configurable limits
112
+ - HTTP error status codes with detailed messages
113
+ - JSON parsing errors with fallback to text
114
+ - Authentication failures with setup guidance
115
+ - Rate limit detection and backoff
116
+
117
+ Side Effects:
118
+ - Makes external HTTP requests
119
+ - May consume API rate limits
120
+ - Logs requests/responses when enabled
121
+ - Updates internal metrics
122
+ - May modify external resources (POST/PUT/DELETE)
123
+
124
+ Examples:
125
+ >>> # Simple GET request
126
+ >>> node = HTTPRequestNode()
127
+ >>> result = node.run(
128
+ ... url="https://api.example.com/users",
129
+ ... method="GET",
130
+ ... headers={"Accept": "application/json"}
131
+ ... )
132
+ >>> assert result["status_code"] == 200
133
+ >>> assert isinstance(result["content"], dict)
134
+ >>>
135
+ >>> # POST request with JSON body
136
+ >>> result = node.run(
137
+ ... url="https://api.example.com/users",
138
+ ... method="POST",
139
+ ... json_data={"name": "John", "email": "john@example.com"},
140
+ ... headers={"Authorization": "Bearer token123"}
141
+ ... )
142
+ >>> assert result["status_code"] in [200, 201]
143
+ >>> assert result["headers"]["content-type"].startswith("application/json")
144
+ >>>
145
+ >>> # Form data submission
146
+ >>> result = node.run(
147
+ ... url="https://api.example.com/form",
148
+ ... method="POST",
149
+ ... data={"field1": "value1", "field2": "value2"},
150
+ ... headers={"Content-Type": "application/x-www-form-urlencoded"}
151
+ ... )
152
+ >>>
153
+ >>> # File upload with multipart
154
+ >>> result = node.run(
155
+ ... url="https://api.example.com/upload",
156
+ ... method="POST",
157
+ ... files={"file": ("data.csv", b"col1,col2\\n1,2", "text/csv")},
158
+ ... data={"description": "Sample data"}
159
+ ... )
160
+ >>>
161
+ >>> # Error handling example
162
+ >>> result = node.run(
163
+ ... url="https://api.example.com/protected",
164
+ ... method="GET"
165
+ ... )
166
+ >>> if result["status_code"] == 401:
167
+ ... print("Authentication required")
87
168
  """
88
169
 
89
170
  def __init__(self, **kwargs):
kailash/nodes/api/rest.py CHANGED
@@ -20,29 +20,124 @@ from kailash.sdk_exceptions import NodeExecutionError, NodeValidationError
20
20
 
21
21
  @register_node()
22
22
  class RESTClientNode(Node):
23
- """Node for interacting with REST APIs.
24
-
25
- This node provides a higher-level interface for interacting with REST APIs,
26
- with built-in support for:
27
- * Resource-based operations (e.g., GET /users/{id})
28
- * Common REST patterns (list, get, create, update, delete)
29
- * Pagination handling
30
- * Response schema validation
31
- * Error response handling
32
-
33
- Design Purpose:
34
- * Simplify REST API integration in workflows
35
- * Provide consistent interfaces for common REST operations
36
- * Support standard REST conventions and patterns
37
- * Handle common REST-specific error cases
38
-
39
- Upstream Usage:
40
- * Workflow: Creates and configures for specific REST APIs
41
- * API integration workflows: Uses for external service integration
23
+ """
24
+ Node for interacting with REST APIs using resource-oriented patterns.
25
+
26
+ This node provides a higher-level abstraction over HTTP operations, specifically
27
+ designed for REST APIs. It understands REST conventions and provides convenient
28
+ methods for resource-based operations, making it easier to integrate RESTful
29
+ services into Kailash workflows.
30
+
31
+ Design Philosophy:
32
+ The RESTClientNode embraces REST principles and conventions, providing an
33
+ intuitive interface for resource manipulation. It abstracts common patterns
34
+ like path parameter substitution, pagination, and error handling while
35
+ maintaining flexibility for API-specific requirements. The design promotes
36
+ clean, maintainable API integration code.
37
+
38
+ Upstream Dependencies:
39
+ - Workflow orchestrators defining API endpoints
40
+ - Configuration nodes providing API credentials
41
+ - Data transformation nodes preparing resources
42
+ - Authentication nodes managing tokens
43
+ - Schema validation nodes defining expected formats
42
44
 
43
45
  Downstream Consumers:
44
- * Data processing nodes: Consume API response data
45
- * Custom nodes: Process API-specific data formats
46
+ - Data processing nodes working with API responses
47
+ - Pagination handlers managing result sets
48
+ - Error recovery nodes handling failures
49
+ - Caching nodes storing resource data
50
+ - Analytics nodes tracking API usage patterns
51
+
52
+ Configuration:
53
+ The node supports REST-specific configuration:
54
+ - Base URL for API endpoints
55
+ - Resource paths with parameter placeholders
56
+ - Default headers and authentication
57
+ - API versioning strategies
58
+ - Pagination parameters
59
+ - Response format expectations
60
+
61
+ Implementation Details:
62
+ - Built on HTTPRequestNode for core functionality
63
+ - Automatic URL construction from base + resource
64
+ - Path parameter substitution (e.g., /users/{id})
65
+ - Query parameter handling with encoding
66
+ - Standard REST method mapping
67
+ - Response format negotiation
68
+ - Error response parsing for API-specific errors
69
+ - Link header parsing for pagination
70
+
71
+ Error Handling:
72
+ - 404 errors for missing resources
73
+ - 422 validation errors with field details
74
+ - 401/403 authentication/authorization errors
75
+ - Rate limiting (429) with retry headers
76
+ - 5xx server errors with backoff
77
+ - Network failures with retry logic
78
+ - Malformed response handling
79
+
80
+ Side Effects:
81
+ - Performs HTTP requests to external APIs
82
+ - May modify remote resources (POST/PUT/DELETE)
83
+ - Consumes API rate limits
84
+ - May trigger webhooks or notifications
85
+ - Updates internal request metrics
86
+
87
+ Examples:
88
+ >>> # Initialize REST client
89
+ >>> client = RESTClientNode()
90
+ >>>
91
+ >>> # Get a single resource
92
+ >>> result = client.run(
93
+ ... base_url="https://api.example.com/v1",
94
+ ... resource="users/{id}",
95
+ ... method="GET",
96
+ ... path_params={"id": 123},
97
+ ... headers={"Authorization": "Bearer token"}
98
+ ... )
99
+ >>> assert result["status_code"] == 200
100
+ >>> user = result["content"]
101
+ >>> assert user["id"] == 123
102
+ >>>
103
+ >>> # List resources with pagination
104
+ >>> result = client.run(
105
+ ... base_url="https://api.example.com/v1",
106
+ ... resource="products",
107
+ ... method="GET",
108
+ ... query_params={"page": 1, "per_page": 20, "category": "electronics"}
109
+ ... )
110
+ >>> assert len(result["content"]) <= 20
111
+ >>>
112
+ >>> # Create a new resource
113
+ >>> result = client.run(
114
+ ... base_url="https://api.example.com/v1",
115
+ ... resource="posts",
116
+ ... method="POST",
117
+ ... data={"title": "New Post", "content": "Post content"},
118
+ ... headers={"Content-Type": "application/json"}
119
+ ... )
120
+ >>> assert result["status_code"] == 201
121
+ >>> assert "id" in result["content"]
122
+ >>>
123
+ >>> # Update a resource
124
+ >>> result = client.run(
125
+ ... base_url="https://api.example.com/v1",
126
+ ... resource="users/{id}",
127
+ ... method="PATCH",
128
+ ... path_params={"id": 123},
129
+ ... data={"email": "newemail@example.com"}
130
+ ... )
131
+ >>> assert result["status_code"] == 200
132
+ >>>
133
+ >>> # Delete a resource
134
+ >>> result = client.run(
135
+ ... base_url="https://api.example.com/v1",
136
+ ... resource="comments/{id}",
137
+ ... method="DELETE",
138
+ ... path_params={"id": 456}
139
+ ... )
140
+ >>> assert result["status_code"] in [200, 204]
46
141
  """
47
142
 
48
143
  def __init__(self, **kwargs):
@@ -37,59 +37,112 @@ from kailash.nodes.base import Node, NodeParameter, register_node
37
37
 
38
38
  @register_node()
39
39
  class CSVReaderNode(Node):
40
- """Reads data from a CSV file.
41
-
42
- This node provides robust CSV file reading capabilities with support for
43
- various delimiters, header detection, and encoding options. It's designed
44
- to handle common CSV formats and edge cases.
45
-
46
- Design Features:
47
- 1. Automatic header detection
48
- 2. Configurable delimiters
49
- 3. Memory-efficient line-by-line reading
50
- 4. Consistent dictionary output format
51
- 5. Unicode support through encoding parameter
52
-
53
- Data Flow:
54
- - Input: File path and configuration parameters
55
- - Processing: Reads CSV line by line, converting to dictionaries
56
- - Output: List of dictionaries (with headers) or list of lists
57
-
58
- Common Usage Patterns:
59
- 1. Reading data exports from databases
60
- 2. Processing spreadsheet data
61
- 3. Loading configuration from CSV
62
- 4. Ingesting sensor data logs
63
-
64
- Upstream Sources:
65
- - File system paths from user input
66
- - Output paths from previous nodes
67
- - Configuration management systems
40
+ """
41
+ Reads data from CSV files with automatic header detection and type inference.
42
+
43
+ This node provides comprehensive CSV file reading capabilities, handling various
44
+ formats, encodings, and edge cases. It automatically detects headers, infers data
45
+ types, and provides consistent structured output for downstream processing in
46
+ Kailash workflows.
47
+
48
+ Design Philosophy:
49
+ The CSVReaderNode embodies the principle of "data accessibility without
50
+ complexity." It abstracts the intricacies of CSV parsing while providing
51
+ flexibility for various formats. The design prioritizes memory efficiency,
52
+ automatic format detection, and consistent output structure, making it easy
53
+ to integrate diverse CSV data sources into workflows.
54
+
55
+ Upstream Dependencies:
56
+ - File system providing CSV files
57
+ - Workflow orchestrators specifying file paths
58
+ - Configuration systems providing parsing options
59
+ - Previous nodes generating CSV file paths
60
+ - User inputs defining data sources
68
61
 
69
62
  Downstream Consumers:
70
- - DataTransformer: Processes tabular data
71
- - Aggregator: Summarizes data
72
- - CSVWriter: Reformats and saves
73
- - Visualizer: Creates charts from data
63
+ - DataTransformNode: Processes tabular data
64
+ - FilterNode: Applies row/column filtering
65
+ - AggregatorNode: Summarizes data
66
+ - PythonCodeNode: Custom data processing
67
+ - WriterNodes: Exports to other formats
68
+ - Visualization nodes: Creates charts
69
+ - ML nodes: Uses as training data
70
+
71
+ Configuration:
72
+ The node supports extensive CSV parsing options:
73
+ - Delimiter detection (comma, tab, pipe, etc.)
74
+ - Header row identification
75
+ - Encoding specification (UTF-8, Latin-1, etc.)
76
+ - Quote character handling
77
+ - Skip rows/comments functionality
78
+ - Column type inference
79
+ - Missing value handling
80
+
81
+ Implementation Details:
82
+ - Uses Python's csv module for robust parsing
83
+ - Implements streaming for large files
84
+ - Automatic delimiter detection when not specified
85
+ - Header detection based on first row analysis
86
+ - Type inference for numeric/date columns
87
+ - Memory-efficient processing with generators
88
+ - Unicode normalization for consistent encoding
74
89
 
75
90
  Error Handling:
76
- - FileNotFoundError: Invalid file path
77
- - PermissionError: Insufficient read permissions
78
- - UnicodeDecodeError: Encoding mismatch
79
- - csv.Error: Malformed CSV data
80
-
81
- Example:
82
- >>> # Read customer data with headers
83
- >>> reader = CSVReaderNode(
84
- ... file_path='customers.csv',
85
- ... headers=True,
86
- ... delimiter=','
91
+ - FileNotFoundError: Clear message with path
92
+ - PermissionError: Access rights guidance
93
+ - UnicodeDecodeError: Encoding detection hints
94
+ - csv.Error: Malformed data diagnostics
95
+ - EmptyFileError: Handles zero-byte files
96
+ - Partial read recovery for corrupted files
97
+
98
+ Side Effects:
99
+ - Reads from file system
100
+ - May consume significant memory for large files
101
+ - Creates file handles (properly closed)
102
+ - Updates internal read statistics
103
+
104
+ Examples:
105
+ >>> # Basic CSV reading with headers
106
+ >>> reader = CSVReaderNode()
107
+ >>> result = reader.run(
108
+ ... file_path="customers.csv",
109
+ ... headers=True
87
110
  ... )
88
- >>> result = reader.execute()
89
- >>> # result['data'] = [
90
- >>> # {'id': '1', 'name': 'John', 'age': '30'},
91
- >>> # {'id': '2', 'name': 'Jane', 'age': '25'}
111
+ >>> assert isinstance(result["data"], list)
112
+ >>> assert all(isinstance(row, dict) for row in result["data"])
113
+ >>> # Example output:
114
+ >>> # result["data"] = [
115
+ >>> # {"id": "1", "name": "John Doe", "age": "30"},
116
+ >>> # {"id": "2", "name": "Jane Smith", "age": "25"}
92
117
  >>> # ]
118
+ >>>
119
+ >>> # Reading with custom delimiter
120
+ >>> result = reader.run(
121
+ ... file_path="data.tsv",
122
+ ... delimiter="\\t",
123
+ ... headers=True
124
+ ... )
125
+ >>>
126
+ >>> # Reading without headers (returns list of lists)
127
+ >>> result = reader.run(
128
+ ... file_path="data.csv",
129
+ ... headers=False
130
+ ... )
131
+ >>> assert all(isinstance(row, list) for row in result["data"])
132
+ >>>
133
+ >>> # Reading with specific encoding
134
+ >>> result = reader.run(
135
+ ... file_path="european_data.csv",
136
+ ... encoding="iso-8859-1",
137
+ ... headers=True
138
+ ... )
139
+ >>>
140
+ >>> # Handling quoted fields
141
+ >>> result = reader.run(
142
+ ... file_path="complex.csv",
143
+ ... headers=True,
144
+ ... quotechar='"'
145
+ ... )
93
146
  """
94
147
 
95
148
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -32,15 +32,25 @@ class AsyncMergeNode(AsyncNode):
32
32
  concat (list concatenation), zip (parallel iteration), and merge_dict
33
33
  (dictionary merging with optional key-based joining).
34
34
 
35
- Usage example:
36
- # Create an AsyncMergeNode in a workflow
37
- async_merge = AsyncMergeNode(merge_type="merge_dict", key="id")
38
- workflow.add_node("data_combine", async_merge)
39
-
40
- # Connect multiple data sources
41
- workflow.connect("api_results", "data_combine", {"output": "data1"})
42
- workflow.connect("database_query", "data_combine", {"results": "data2"})
43
- workflow.connect("file_processor", "data_combine", {"processed_data": "data3"})
35
+ Example usage:
36
+ >>> # Create an AsyncMergeNode
37
+ >>> async_merge = AsyncMergeNode(merge_type="merge_dict", key="id")
38
+ >>> async_merge.metadata.name
39
+ 'AsyncMergeNode'
40
+
41
+ >>> # Using in a workflow
42
+ >>> from kailash.workflow.graph import Workflow
43
+ >>> workflow = Workflow("wf-001", "async_example")
44
+ >>> workflow.add_node("data_combine", async_merge)
45
+ >>> "data_combine" in workflow.nodes
46
+ True
47
+
48
+ >>> # Async execution with concat
49
+ >>> import asyncio
50
+ >>> async_merge = AsyncMergeNode(merge_type="concat")
51
+ >>> result = asyncio.run(async_merge.execute_async(data1=[1, 2], data2=[3, 4]))
52
+ >>> result['merged_data']
53
+ [1, 2, 3, 4]
44
54
  """
45
55
 
46
56
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -364,6 +374,35 @@ class AsyncSwitchNode(AsyncNode):
364
374
 
365
375
  The basic functionality is the same as the synchronous SwitchNode but optimized
366
376
  for asynchronous execution.
377
+
378
+ Example usage:
379
+ >>> # Boolean condition routing
380
+ >>> import asyncio
381
+ >>> async_switch = AsyncSwitchNode(
382
+ ... condition_field="status",
383
+ ... operator="==",
384
+ ... value="active"
385
+ ... )
386
+ >>> result = asyncio.run(async_switch.execute_async(
387
+ ... input_data={"status": "active", "data": "test"}
388
+ ... ))
389
+ >>> result['true_output']
390
+ {'status': 'active', 'data': 'test'}
391
+ >>> result['false_output'] is None
392
+ True
393
+
394
+ >>> # Multi-case switching
395
+ >>> async_switch = AsyncSwitchNode(
396
+ ... condition_field="priority",
397
+ ... cases=["high", "medium", "low"]
398
+ ... )
399
+ >>> result = asyncio.run(async_switch.execute_async(
400
+ ... input_data={"priority": "high", "task": "urgent"}
401
+ ... ))
402
+ >>> result['case_high']
403
+ {'priority': 'high', 'task': 'urgent'}
404
+ >>> result['default']
405
+ {'priority': 'high', 'task': 'urgent'}
367
406
  """
368
407
 
369
408
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -370,6 +370,31 @@ class MergeNode(Node):
370
370
  The merge operation is determined by the merge_type parameter, which supports
371
371
  concat (list concatenation), zip (parallel iteration), and merge_dict (dictionary
372
372
  merging with optional key-based joining for lists of dictionaries).
373
+
374
+ Example usage:
375
+ >>> # Simple list concatenation
376
+ >>> merge_node = MergeNode(merge_type="concat")
377
+ >>> result = merge_node.execute(data1=[1, 2], data2=[3, 4])
378
+ >>> result['merged_data']
379
+ [1, 2, 3, 4]
380
+
381
+ >>> # Dictionary merging
382
+ >>> merge_node = MergeNode(merge_type="merge_dict")
383
+ >>> result = merge_node.execute(
384
+ ... data1={"a": 1, "b": 2},
385
+ ... data2={"b": 3, "c": 4}
386
+ ... )
387
+ >>> result['merged_data']
388
+ {'a': 1, 'b': 3, 'c': 4}
389
+
390
+ >>> # List of dicts merging by key
391
+ >>> merge_node = MergeNode(merge_type="merge_dict", key="id")
392
+ >>> result = merge_node.execute(
393
+ ... data1=[{"id": 1, "name": "Alice"}],
394
+ ... data2=[{"id": 1, "age": 30}]
395
+ ... )
396
+ >>> result['merged_data']
397
+ [{'id': 1, 'name': 'Alice', 'age': 30}]
373
398
  """
374
399
 
375
400
  def get_parameters(self) -> Dict[str, NodeParameter]:
@@ -52,24 +52,32 @@ class WorkflowNode(Node):
52
52
  - Runtime executing the inner workflow
53
53
  - Results passed to subsequent nodes
54
54
 
55
- Usage Patterns:
56
- 1. Direct workflow wrapping:
57
- ```python
58
- inner_workflow = Workflow("data_processing")
59
- # ... build workflow ...
60
- node = WorkflowNode(workflow=inner_workflow)
61
- ```
62
-
63
- 2. Loading from file:
64
- ```python
65
- node = WorkflowNode(workflow_path="workflows/processor.yaml")
66
- ```
67
-
68
- 3. Loading from dictionary:
69
- ```python
70
- workflow_dict = {"nodes": {...}, "connections": [...]}
71
- node = WorkflowNode(workflow_dict=workflow_dict)
72
- ```
55
+ Example usage:
56
+ >>> # Direct workflow wrapping
57
+ >>> from kailash.workflow.graph import Workflow
58
+ >>> from kailash.nodes.data.readers import CSVReaderNode
59
+ >>> inner_workflow = Workflow("wf-001", "data_processing")
60
+ >>> inner_workflow.add_node("reader", CSVReaderNode(file_path="data.csv"))
61
+ >>> node = WorkflowNode(workflow=inner_workflow)
62
+ >>> node.metadata.name
63
+ 'WorkflowNode'
64
+
65
+ >>> # Get parameters from wrapped workflow
66
+ >>> params = node.get_parameters()
67
+ >>> 'reader_file_path' in params
68
+ True
69
+ >>> 'inputs' in params
70
+ True
71
+
72
+ >>> # Loading from dictionary
73
+ >>> workflow_dict = {
74
+ ... "name": "simple",
75
+ ... "nodes": {"node1": {"type": "CSVReaderNode", "config": {"file_path": "test.csv"}}},
76
+ ... "connections": []
77
+ ... }
78
+ >>> node = WorkflowNode(workflow_dict=workflow_dict)
79
+ >>> node._workflow.name
80
+ 'simple'
73
81
 
74
82
  Implementation Details:
75
83
  - Parameters derived from workflow entry nodes
@@ -6,10 +6,17 @@ from kailash.nodes.transform.formatters import (
6
6
  ContextFormatterNode,
7
7
  QueryTextWrapperNode,
8
8
  )
9
- from kailash.nodes.transform.processors import DataTransformer, Filter, Map, Sort
9
+ from kailash.nodes.transform.processors import (
10
+ DataTransformer,
11
+ Filter,
12
+ FilterNode,
13
+ Map,
14
+ Sort,
15
+ )
10
16
 
11
17
  __all__ = [
12
18
  "Filter",
19
+ "FilterNode",
13
20
  "Map",
14
21
  "Sort",
15
22
  "DataTransformer",