stepflow-py 0.2.1__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.
@@ -0,0 +1,58 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with this work for
3
+ # additional information regarding copyright ownership. The ASF licenses this
4
+ # file to you under the Apache License, Version 2.0 (the "License"); you may not
5
+ # use this file except in compliance with the License. You may obtain a copy of
6
+ # the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ from .context import StepflowContext
17
+ from .flow_builder import FlowBuilder, StepHandle
18
+ from .generated_protocol import (
19
+ ErrorAction,
20
+ OnErrorDefault,
21
+ OnErrorFail,
22
+ OnErrorRetry,
23
+ OnErrorSkip,
24
+ OnSkipDefault,
25
+ OnSkipSkip,
26
+ SkipAction,
27
+ )
28
+ from .stdio_server import StepflowStdioServer
29
+ from .value import JsonPath, StepReference, Valuable, Value, WorkflowInput
30
+
31
+ __all__ = [
32
+ # Core classes
33
+ "StepflowStdioServer",
34
+ "StepflowContext",
35
+ "FlowBuilder",
36
+ # Value API for cleaner workflow definitions
37
+ "Value",
38
+ "Valuable",
39
+ # Helper classes for type hints and intermediate objects
40
+ "JsonPath",
41
+ "StepHandle",
42
+ "StepReference",
43
+ "WorkflowInput",
44
+ # Error and Skip Action types
45
+ "ErrorAction",
46
+ "OnErrorFail",
47
+ "OnErrorSkip",
48
+ "OnErrorRetry",
49
+ "OnErrorDefault",
50
+ "SkipAction",
51
+ "OnSkipSkip",
52
+ "OnSkipDefault",
53
+ ]
54
+
55
+ if __name__ == "__main__":
56
+ from . import main
57
+
58
+ main.main()
stepflow_py/context.py ADDED
@@ -0,0 +1,182 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with this work for
3
+ # additional information regarding copyright ownership. The ASF licenses this
4
+ # file to you under the Apache License, Version 2.0 (the "License"); you may not
5
+ # use this file except in compliance with the License. You may obtain a copy of
6
+ # the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ from __future__ import annotations
17
+
18
+ import asyncio
19
+ import sys
20
+ from typing import Any, TypeVar
21
+ from uuid import uuid4
22
+
23
+ from stepflow_py.generated_protocol import (
24
+ EvaluateFlowResult,
25
+ Flow,
26
+ FlowResultFailed,
27
+ FlowResultSkipped,
28
+ FlowResultSuccess,
29
+ GetBlobResult,
30
+ Message,
31
+ Method,
32
+ MethodError,
33
+ MethodSuccess,
34
+ PutBlobResult,
35
+ )
36
+ from stepflow_py.message_decoder import MessageDecoder
37
+
38
+ """
39
+ Context API for stepflow components to interact with the runtime.
40
+ """
41
+
42
+ T = TypeVar("T")
43
+
44
+
45
+ class StepflowContext:
46
+ """Context for stepflow components to make calls back to the runtime.
47
+
48
+ This allows components to store/retrieve blobs and perform other
49
+ runtime operations through bidirectional communication.
50
+ """
51
+
52
+ def __init__(
53
+ self,
54
+ outgoing_queue: asyncio.Queue,
55
+ message_decoder: MessageDecoder[asyncio.Future[Message]],
56
+ session_id: str | None = None,
57
+ ):
58
+ self._outgoing_queue = outgoing_queue
59
+ self._message_decoder = message_decoder
60
+ self._session_id = session_id
61
+
62
+ async def _send_request(
63
+ self, method: Method, params: Any, result_type: type[T]
64
+ ) -> T:
65
+ """Send a request to the stepflow runtime and wait for response."""
66
+ request_id = str(uuid4())
67
+ request = {
68
+ "jsonrpc": "2.0",
69
+ "id": request_id,
70
+ "method": method.value,
71
+ "params": params,
72
+ }
73
+
74
+ # Create future for response
75
+ future: asyncio.Future[Message] = asyncio.Future()
76
+
77
+ # Register pending request with message decoder using method registration
78
+ self._message_decoder.register_request_for_method(request_id, method, future)
79
+
80
+ # Send request via queue
81
+ await self._outgoing_queue.put(request)
82
+
83
+ # Wait for response - the MessageDecoder will resolve this future
84
+ # when the response is received
85
+ response_message = await future
86
+
87
+ # Extract the result from the response message
88
+ if isinstance(response_message, MethodSuccess):
89
+ result = response_message.result
90
+ assert isinstance(result, result_type), (
91
+ f"Expected {result_type}, got {type(result)}"
92
+ )
93
+ return result
94
+ elif isinstance(response_message, MethodError):
95
+ # Handle error case
96
+ raise Exception(f"Request failed: {response_message.error}")
97
+ else:
98
+ raise Exception(
99
+ f"Unexpected response type: {type(response_message)} {response_message}"
100
+ )
101
+
102
+ async def put_blob(self, data: Any) -> str:
103
+ """Store JSON data as a blob and return its content-based ID.
104
+
105
+ Args:
106
+ data: The JSON-serializable data to store
107
+
108
+ Returns:
109
+ The blob ID (SHA-256 hash) for the stored data
110
+ """
111
+ params = {"data": data}
112
+ response = await self._send_request(Method.blobs_put, params, PutBlobResult)
113
+ return response.blob_id
114
+
115
+ async def get_blob(self, blob_id: str) -> Any:
116
+ """Retrieve JSON data by blob ID.
117
+
118
+ Args:
119
+ blob_id: The blob ID to retrieve
120
+
121
+ Returns:
122
+ The JSON data associated with the blob ID
123
+ """
124
+ params = {"blob_id": blob_id}
125
+ response = await self._send_request(Method.blobs_get, params, GetBlobResult)
126
+ return response.data
127
+
128
+ @property
129
+ def session_id(self) -> str | None:
130
+ """Get the session ID for HTTP mode, or None for STDIO mode."""
131
+ return self._session_id
132
+
133
+ async def evaluate_flow(self, flow: Flow | dict, input: Any) -> Any:
134
+ """Evaluate a flow with the given input.
135
+
136
+ Args:
137
+ flow: The flow definition (as a Flow object or dictionary)
138
+ input: The input to provide to the flow
139
+
140
+ Returns:
141
+ The result value on success
142
+
143
+ Raises:
144
+ StepflowSkipped: If the flow execution was skipped
145
+ StepflowFailed: If the flow execution failed with a business logic error
146
+ Exception: For system/runtime errors
147
+ """
148
+ from stepflow_py.exceptions import StepflowFailed, StepflowSkipped
149
+
150
+ # Convert Flow object to dict if needed
151
+ if isinstance(flow, Flow):
152
+ import msgspec
153
+
154
+ flow_dict = msgspec.to_builtins(flow)
155
+ else:
156
+ flow_dict = flow
157
+
158
+ params = {"flow": flow_dict, "input": input}
159
+
160
+ evaluate_result = await self._send_request(
161
+ Method.flows_evaluate, params, EvaluateFlowResult
162
+ )
163
+ flow_result = evaluate_result.result
164
+
165
+ # Check the outcome and either return the result or raise appropriate exception
166
+ if isinstance(flow_result, FlowResultSuccess):
167
+ return flow_result.result
168
+ elif isinstance(flow_result, FlowResultSkipped):
169
+ raise StepflowSkipped("Flow execution was skipped")
170
+ elif isinstance(flow_result, FlowResultFailed):
171
+ error = flow_result.error
172
+ raise StepflowFailed(
173
+ error_code=error.code,
174
+ message=error.message,
175
+ data=error.data,
176
+ )
177
+ else:
178
+ raise Exception(f"Unexpected flow result type: {type(flow_result)}")
179
+
180
+ def log(self, message):
181
+ """Log a message."""
182
+ print(f"PYTHON: {message}", file=sys.stderr)
@@ -0,0 +1,161 @@
1
+ # Licensed to the Apache Software Foundation (ASF) under one or more contributor
2
+ # license agreements. See the NOTICE file distributed with this work for
3
+ # additional information regarding copyright ownership. The ASF licenses this
4
+ # file to you under the Apache License, Version 2.0 (the "License"); you may not
5
+ # use this file except in compliance with the License. You may obtain a copy of
6
+ # the License at
7
+ #
8
+ # http://www.apache.org/licenses/LICENSE-2.0
9
+ #
10
+ # Unless required by applicable law or agreed to in writing, software
11
+ # distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
12
+ # WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
13
+ # License for the specific language governing permissions and limitations under
14
+ # the License.
15
+
16
+ from enum import IntEnum
17
+ from typing import Any
18
+
19
+
20
+ class ErrorCode(IntEnum):
21
+ GENERIC_ERROR = -32000
22
+ NOT_INITIALIZED = -32001
23
+
24
+ INVALID_REQUEST = -32600
25
+ COMPONENT_ERROR = -32001
26
+ VALIDATION_ERROR = -32002
27
+ EXECUTION_ERROR = -32003
28
+ RUNTIME_ERROR = -32004
29
+ VALUE_ERROR = -32005
30
+
31
+
32
+ class StepflowError(Exception):
33
+ """Base exception for all StepFlow SDK errors."""
34
+
35
+ def __init__(self, message: str, code: ErrorCode = None, data: dict = None):
36
+ super().__init__(message)
37
+ self.message = message
38
+ self.code = code or self.default_code
39
+ self.data = data or {}
40
+
41
+ @property
42
+ def default_code(self) -> ErrorCode:
43
+ """Default error code for this exception type."""
44
+ return ErrorCode.GENERIC_ERROR
45
+
46
+ def to_json_rpc_error(self) -> dict:
47
+ """Convert to JSON-RPC error format."""
48
+ return {"code": self.code.value, "message": self.message, "data": self.data}
49
+
50
+
51
+ class StepflowProtocolError(StepflowError):
52
+ """Errors related to JSON-RPC protocol violations."""
53
+
54
+ @property
55
+ def default_code(self) -> ErrorCode:
56
+ return ErrorCode.INVALID_REQUEST # Invalid Request
57
+
58
+
59
+ class StepflowComponentError(StepflowError):
60
+ """Errors related to component operations."""
61
+
62
+ @property
63
+ def default_code(self) -> ErrorCode:
64
+ return ErrorCode.COMPONENT_ERROR
65
+
66
+
67
+ class StepflowValidationError(StepflowError):
68
+ """Errors related to input/output validation."""
69
+
70
+ @property
71
+ def default_code(self) -> ErrorCode:
72
+ return ErrorCode.VALIDATION_ERROR
73
+
74
+
75
+ class StepflowValueError(StepflowError):
76
+ """Errors related to invalid values."""
77
+
78
+ @property
79
+ def default_code(self) -> ErrorCode:
80
+ return ErrorCode.VALUE_ERROR
81
+
82
+
83
+ class StepflowExecutionError(StepflowError):
84
+ """Errors during component execution."""
85
+
86
+ @property
87
+ def default_code(self) -> ErrorCode:
88
+ return ErrorCode.EXECUTION_ERROR
89
+
90
+
91
+ class StepflowRuntimeError(StepflowError):
92
+ """Errors from the StepFlow runtime."""
93
+
94
+ @property
95
+ def default_code(self) -> ErrorCode:
96
+ return ErrorCode.RUNTIME_ERROR
97
+
98
+
99
+ class ComponentNotFoundError(StepflowComponentError):
100
+ """Component was not found."""
101
+
102
+ def __init__(self, component_name: str):
103
+ super().__init__(f"Component '{component_name}' not found")
104
+ self.component_name = component_name
105
+ self.data = {"component": component_name}
106
+
107
+
108
+ class ServerNotInitializedError(StepflowProtocolError):
109
+ """Server hasn't been initialized yet."""
110
+
111
+ def __init__(self):
112
+ super().__init__("Server not initialized")
113
+
114
+ @property
115
+ def default_code(self) -> ErrorCode:
116
+ return ErrorCode.NOT_INITIALIZED
117
+
118
+
119
+ class InputValidationError(StepflowValidationError):
120
+ """Input validation failed."""
121
+
122
+ def __init__(self, validation_error: str, input_data: dict | None = None):
123
+ super().__init__(f"Input validation failed: {validation_error}")
124
+ if input_data:
125
+ self.data = {"input": input_data}
126
+
127
+
128
+ class BlobNotFoundError(StepflowRuntimeError):
129
+ """Blob was not found in storage."""
130
+
131
+ def __init__(self, blob_id: str):
132
+ super().__init__(f"Blob '{blob_id}' not found")
133
+ self.blob_id = blob_id
134
+ self.data = {"blob_id": blob_id}
135
+
136
+
137
+ class CodeCompilationError(StepflowExecutionError):
138
+ """User code compilation failed."""
139
+
140
+ def __init__(self, compilation_error: str, code: str | None = None):
141
+ super().__init__(f"Code compilation failed: {compilation_error}")
142
+ if code:
143
+ self.data = {"code": code}
144
+
145
+
146
+ class StepflowSkipped(Exception):
147
+ """Exception raised when a step or flow is skipped."""
148
+
149
+ def __init__(self, message: str = "Flow execution was skipped"):
150
+ super().__init__(message)
151
+ self.message = message
152
+
153
+
154
+ class StepflowFailed(Exception):
155
+ """Exception raised when a step or flow fails with a business logic error."""
156
+
157
+ def __init__(self, error_code: int, message: str, data: Any = None):
158
+ super().__init__(message)
159
+ self.error_code = error_code
160
+ self.message = message
161
+ self.data = data