feldera 0.118.0__tar.gz → 0.179.0__tar.gz

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.

Potentially problematic release.


This version of feldera might be problematic. Click here for more details.

Files changed (42) hide show
  1. feldera-0.179.0/PKG-INFO +97 -0
  2. feldera-0.179.0/README.md +73 -0
  3. feldera-0.179.0/feldera/_callback_runner.py +95 -0
  4. {feldera-0.118.0 → feldera-0.179.0}/feldera/_helpers.py +8 -2
  5. {feldera-0.118.0 → feldera-0.179.0}/feldera/enums.py +145 -92
  6. {feldera-0.118.0 → feldera-0.179.0}/feldera/output_handler.py +16 -4
  7. {feldera-0.118.0 → feldera-0.179.0}/feldera/pipeline.py +514 -148
  8. {feldera-0.118.0 → feldera-0.179.0}/feldera/pipeline_builder.py +15 -8
  9. feldera-0.179.0/feldera/rest/_helpers.py +40 -0
  10. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/_httprequests.py +43 -20
  11. feldera-0.179.0/feldera/rest/config.py +51 -0
  12. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/errors.py +16 -0
  13. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/feldera_client.py +507 -136
  14. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/pipeline.py +15 -0
  15. {feldera-0.118.0 → feldera-0.179.0}/feldera/runtime_config.py +4 -0
  16. {feldera-0.118.0 → feldera-0.179.0}/feldera/stats.py +8 -5
  17. feldera-0.179.0/feldera/tests/test_datafusionize.py +38 -0
  18. feldera-0.179.0/feldera/testutils.py +390 -0
  19. feldera-0.179.0/feldera/testutils_oidc.py +368 -0
  20. feldera-0.179.0/feldera.egg-info/PKG-INFO +97 -0
  21. {feldera-0.118.0 → feldera-0.179.0}/feldera.egg-info/SOURCES.txt +3 -5
  22. {feldera-0.118.0 → feldera-0.179.0}/feldera.egg-info/requires.txt +1 -0
  23. {feldera-0.118.0 → feldera-0.179.0}/pyproject.toml +7 -2
  24. feldera-0.118.0/PKG-INFO +0 -152
  25. feldera-0.118.0/README.md +0 -129
  26. feldera-0.118.0/feldera/_callback_runner.py +0 -119
  27. feldera-0.118.0/feldera/rest/_helpers.py +0 -9
  28. feldera-0.118.0/feldera/rest/config.py +0 -30
  29. feldera-0.118.0/feldera.egg-info/PKG-INFO +0 -152
  30. feldera-0.118.0/tests/test_pipeline_builder.py +0 -53
  31. feldera-0.118.0/tests/test_shared_pipeline0.py +0 -565
  32. feldera-0.118.0/tests/test_shared_pipeline1.py +0 -124
  33. feldera-0.118.0/tests/test_shared_pipeline_stress.py +0 -26
  34. feldera-0.118.0/tests/test_udf.py +0 -311
  35. {feldera-0.118.0 → feldera-0.179.0}/feldera/__init__.py +0 -0
  36. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/__init__.py +0 -0
  37. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/feldera_config.py +0 -0
  38. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/sql_table.py +0 -0
  39. {feldera-0.118.0 → feldera-0.179.0}/feldera/rest/sql_view.py +0 -0
  40. {feldera-0.118.0 → feldera-0.179.0}/feldera.egg-info/dependency_links.txt +0 -0
  41. {feldera-0.118.0 → feldera-0.179.0}/feldera.egg-info/top_level.txt +0 -0
  42. {feldera-0.118.0 → feldera-0.179.0}/setup.cfg +0 -0
@@ -0,0 +1,97 @@
1
+ Metadata-Version: 2.4
2
+ Name: feldera
3
+ Version: 0.179.0
4
+ Summary: The feldera python client
5
+ Author-email: Feldera Team <dev@feldera.com>
6
+ License: MIT
7
+ Project-URL: Homepage, https://www.feldera.com
8
+ Project-URL: Documentation, https://docs.feldera.com/python
9
+ Project-URL: Repository, https://github.com/feldera/feldera
10
+ Project-URL: Issues, https://github.com/feldera/feldera/issues
11
+ Keywords: feldera,python
12
+ Classifier: License :: OSI Approved :: MIT License
13
+ Classifier: Programming Language :: Python :: 3.10
14
+ Classifier: Operating System :: OS Independent
15
+ Requires-Python: >=3.10
16
+ Description-Content-Type: text/markdown
17
+ Requires-Dist: requests
18
+ Requires-Dist: pandas>=2.1.2
19
+ Requires-Dist: typing-extensions
20
+ Requires-Dist: numpy>=2.2.4
21
+ Requires-Dist: pretty-errors
22
+ Requires-Dist: ruff>=0.6.9
23
+ Requires-Dist: PyJWT>=2.8.0
24
+
25
+ # Feldera Python SDK
26
+
27
+ Feldera Python is the Feldera SDK for Python developers.
28
+
29
+ ## Installation
30
+
31
+ ```bash
32
+ uv pip install feldera
33
+ ```
34
+
35
+ ### Installing from Github
36
+
37
+ ```bash
38
+ uv pip install git+https://github.com/feldera/feldera#subdirectory=python
39
+ ```
40
+
41
+ Similarly, to install from a specific branch:
42
+
43
+ ```bash
44
+ uv pip install git+https://github.com/feldera/feldera@{BRANCH_NAME}#subdirectory=python
45
+ ```
46
+
47
+ Replace `{BRANCH_NAME}` with the name of the branch you want to install from.
48
+
49
+ ### Installing from Local Directory
50
+
51
+ If you have cloned the Feldera repo, you can install the python SDK as follows:
52
+
53
+ ```bash
54
+ # the Feldera Python SDK is present inside the python/ directory
55
+ cd python
56
+ # If you don't have a virtual environment, create one
57
+ uv venv
58
+ source .venv/activate
59
+ # Install the SDK in editable mode
60
+ uv pip install .
61
+ ```
62
+
63
+ ## Documentation
64
+
65
+ The Python SDK documentation is available at
66
+ [Feldera Python SDK Docs](https://docs.feldera.com/python).
67
+
68
+ To build the html documentation run:
69
+
70
+ Ensure that you have sphinx installed. If not, install it using `uv pip install sphinx`.
71
+
72
+ Then run the following commands:
73
+
74
+ ```bash
75
+ cd docs
76
+ sphinx-apidoc -o . ../feldera
77
+ make html
78
+ ```
79
+
80
+ To clean the build, run `make clean`.
81
+
82
+ ## Linting and formatting
83
+
84
+ Use [Ruff] to run the lint checks that will be executed by the
85
+ precommit hook when a PR is submitted:
86
+
87
+ ```bash
88
+ ruff check python/
89
+ ```
90
+
91
+ To reformat the code in the same way as the precommit hook:
92
+
93
+ ```bash
94
+ ruff format
95
+ ```
96
+
97
+ [Ruff]: https://github.com/astral-sh/ruff
@@ -0,0 +1,73 @@
1
+ # Feldera Python SDK
2
+
3
+ Feldera Python is the Feldera SDK for Python developers.
4
+
5
+ ## Installation
6
+
7
+ ```bash
8
+ uv pip install feldera
9
+ ```
10
+
11
+ ### Installing from Github
12
+
13
+ ```bash
14
+ uv pip install git+https://github.com/feldera/feldera#subdirectory=python
15
+ ```
16
+
17
+ Similarly, to install from a specific branch:
18
+
19
+ ```bash
20
+ uv pip install git+https://github.com/feldera/feldera@{BRANCH_NAME}#subdirectory=python
21
+ ```
22
+
23
+ Replace `{BRANCH_NAME}` with the name of the branch you want to install from.
24
+
25
+ ### Installing from Local Directory
26
+
27
+ If you have cloned the Feldera repo, you can install the python SDK as follows:
28
+
29
+ ```bash
30
+ # the Feldera Python SDK is present inside the python/ directory
31
+ cd python
32
+ # If you don't have a virtual environment, create one
33
+ uv venv
34
+ source .venv/activate
35
+ # Install the SDK in editable mode
36
+ uv pip install .
37
+ ```
38
+
39
+ ## Documentation
40
+
41
+ The Python SDK documentation is available at
42
+ [Feldera Python SDK Docs](https://docs.feldera.com/python).
43
+
44
+ To build the html documentation run:
45
+
46
+ Ensure that you have sphinx installed. If not, install it using `uv pip install sphinx`.
47
+
48
+ Then run the following commands:
49
+
50
+ ```bash
51
+ cd docs
52
+ sphinx-apidoc -o . ../feldera
53
+ make html
54
+ ```
55
+
56
+ To clean the build, run `make clean`.
57
+
58
+ ## Linting and formatting
59
+
60
+ Use [Ruff] to run the lint checks that will be executed by the
61
+ precommit hook when a PR is submitted:
62
+
63
+ ```bash
64
+ ruff check python/
65
+ ```
66
+
67
+ To reformat the code in the same way as the precommit hook:
68
+
69
+ ```bash
70
+ ruff format
71
+ ```
72
+
73
+ [Ruff]: https://github.com/astral-sh/ruff
@@ -0,0 +1,95 @@
1
+ from threading import Thread, Event
2
+ from typing import Callable, List, Optional, Mapping, Any
3
+
4
+ import pandas as pd
5
+ from feldera import FelderaClient
6
+ from feldera._helpers import dataframe_from_response
7
+ from feldera.enums import PipelineFieldSelector
8
+ from feldera.rest.sql_table import SQLTable
9
+ from feldera.rest.sql_view import SQLView
10
+ from feldera.rest.pipeline import Pipeline
11
+
12
+
13
+ class CallbackRunner(Thread):
14
+ def __init__(
15
+ self,
16
+ client: FelderaClient,
17
+ pipeline_name: str,
18
+ view_name: str,
19
+ callback: Callable[[pd.DataFrame, int], None],
20
+ exception_callback: Callable[[BaseException], None],
21
+ event: Event,
22
+ ):
23
+ """
24
+ :param client: The :class:`.FelderaClient` to use.
25
+ :param pipeline_name: The name of the current pipeline.
26
+ :param view_name: The name of the view we are listening to.
27
+ :param callback: The callback function to call on the data we receive.
28
+ :param exception_callback: The callback function to call when an exception occurs.
29
+ :param event: The event to wait for before starting the callback runner.
30
+ """
31
+
32
+ super().__init__()
33
+ self.daemon = True
34
+ self.client: FelderaClient = client
35
+ self.pipeline_name: str = pipeline_name
36
+ self.view_name: str = view_name
37
+ self.callback: Callable[[pd.DataFrame, int], None] = callback
38
+ self.exception_callback: Callable[[BaseException], None] = exception_callback
39
+ self.event: Event = event
40
+
41
+ self.pipeline: Pipeline = self.client.get_pipeline(
42
+ self.pipeline_name, PipelineFieldSelector.ALL
43
+ )
44
+
45
+ view_schema = None
46
+
47
+ schemas: List[SQLTable | SQLView] = self.pipeline.tables + self.pipeline.views
48
+ for schema in schemas:
49
+ if schema.name == self.view_name:
50
+ view_schema = schema
51
+ break
52
+
53
+ if view_schema is None:
54
+ raise ValueError(
55
+ f"Table or View {self.view_name} not found in the pipeline schema."
56
+ )
57
+
58
+ self.schema: SQLTable | SQLView = view_schema
59
+
60
+ def to_callback(self, chunk: Mapping[str, Any]):
61
+ data: Optional[list[Mapping[str, Any]]] = chunk.get("json_data")
62
+ seq_no: Optional[int] = chunk.get("sequence_number")
63
+ if data is not None and seq_no is not None:
64
+ self.callback(dataframe_from_response([data], self.schema.fields), seq_no)
65
+
66
+ def run(self):
67
+ """
68
+ The main loop of the thread. Listens for data and calls the callback function on each chunk of data received.
69
+
70
+ :meta private:
71
+ """
72
+
73
+ try:
74
+ gen_obj = self.client.listen_to_pipeline(
75
+ self.pipeline_name,
76
+ self.view_name,
77
+ format="json",
78
+ case_sensitive=self.schema.case_sensitive,
79
+ )
80
+
81
+ iterator = gen_obj()
82
+
83
+ # Trigger the HTTP call
84
+ chunk = next(iterator)
85
+
86
+ # Unblock the main thread
87
+ self.event.set()
88
+
89
+ self.to_callback(chunk)
90
+
91
+ for chunk in iterator:
92
+ self.to_callback(chunk)
93
+
94
+ except BaseException as e:
95
+ self.exception_callback(e)
@@ -2,6 +2,7 @@ import uuid
2
2
 
3
3
  import pandas as pd
4
4
  from decimal import Decimal
5
+ from typing import Mapping, Any
5
6
 
6
7
 
7
8
  def sql_type_to_pandas_type(sql_type: str):
@@ -60,9 +61,14 @@ def ensure_dataframe_has_columns(df: pd.DataFrame):
60
61
  )
61
62
 
62
63
 
63
- def dataframe_from_response(buffer: list[list[dict]], schema: dict):
64
+ def dataframe_from_response(
65
+ buffer: list[list[Mapping[str, Any]]], fields: list[Mapping[str, Any]]
66
+ ):
64
67
  """
65
68
  Converts the response from Feldera to a pandas DataFrame.
69
+
70
+ :param buffer: A buffer of a list of JSON formatted output of the view you are listening to.
71
+ :param fields: The schema (list of fields) of the view you are listening to.
66
72
  """
67
73
 
68
74
  pd_schema = {}
@@ -70,7 +76,7 @@ def dataframe_from_response(buffer: list[list[dict]], schema: dict):
70
76
  decimal_col = []
71
77
  uuid_col = []
72
78
 
73
- for column in schema["fields"]:
79
+ for column in fields:
74
80
  column_name = column["name"]
75
81
  if not column["case_sensitive"]:
76
82
  column_name = column_name.lower()
@@ -34,137 +34,164 @@ class BuildMode(Enum):
34
34
  GET_OR_CREATE = 3
35
35
 
36
36
 
37
- class PipelineStatus(Enum):
37
+ class DeploymentDesiredStatus(Enum):
38
+ """
39
+ Deployment desired status of the pipeline.
38
40
  """
39
- Represents the state that this pipeline is currently in.
40
41
 
41
- .. code-block:: text
42
+ STOPPED = 0
43
+ UNAVAILABLE = 1
44
+ STANDBY = 2
45
+ PAUSED = 3
46
+ RUNNING = 4
47
+ SUSPENDED = 5
42
48
 
43
- Stopped ◄─────────── Stopping ◄───── All states can transition
44
- │ ▲ to Stopping by either:
45
- /start or /pause │ │ (1) user calling /stop?force=true, or;
46
- ▼ │ (2) pipeline encountering a fatal
47
- ⌛Provisioning Suspending resource or runtime error,
48
- │ ▲ having the system call /stop?force=true
49
- ▼ │ /stop effectively
50
- ⌛Initializing ─────────────┤ ?force=false
51
- │ │
52
- ┌─────────┼────────────────────┴─────┐
53
- │ ▼ │
54
- │ Paused ◄──────► Unavailable │
55
- │ │ ▲ ▲ │
56
- │ /start │ │ /pause │ │
57
- │ ▼ │ │ │
58
- │ Running ◄─────────────┘ │
59
- └────────────────────────────────────┘
49
+ @staticmethod
50
+ def from_str(value):
51
+ for member in DeploymentDesiredStatus:
52
+ if member.name.lower() == value.lower():
53
+ return member
54
+ raise ValueError(
55
+ f"Unknown value '{value}' for enum {DeploymentDesiredStatus.__name__}"
56
+ )
60
57
 
61
- """
62
58
 
63
- NOT_FOUND = 0
59
+ class DeploymentResourcesDesiredStatus(Enum):
64
60
  """
65
- The pipeline has not been created yet.
61
+ The desired status of deployment resources of the pipeline.
66
62
  """
67
63
 
68
- STOPPED = 1
69
- """
70
- The pipeline has not (yet) been started or has been stopped either
71
- manually by the user or automatically by the system due to a
72
- resource or runtime error.
64
+ STOPPED = 0
65
+ PROVISIONED = 1
73
66
 
74
- The pipeline remains in this state until:
67
+ @staticmethod
68
+ def from_str(value):
69
+ for member in DeploymentResourcesDesiredStatus:
70
+ if member.name.lower() == value.lower():
71
+ return member
72
+ raise ValueError(
73
+ f"Unknown value '{value}' for enum {DeploymentResourcesDesiredStatus.__name__}"
74
+ )
75
75
 
76
- 1. The user starts it via `/start` or `/pause`, transitioning to `PROVISIONING`.
77
- 2. Early start fails (e.g., compilation failure), transitioning to `STOPPING`.
78
- """
79
76
 
80
- PROVISIONING = 2
77
+ class DeploymentResourcesStatus(Enum):
81
78
  """
82
- Compute (and optionally storage) resources needed for running the pipeline
83
- are being provisioned.
84
-
85
- The pipeline remains in this state until:
86
-
87
- 1. Resources are provisioned successfully, transitioning to `INITIALIZING`.
88
- 2. Provisioning fails or times out, transitioning to `STOPPING`.
89
- 3. The user cancels the pipeline via `/stop`, transitioning to `STOPPING`.
79
+ The desired status of deployment resources of the pipeline.
90
80
  """
91
81
 
92
- INITIALIZING = 3
93
- """
94
- The pipeline is initializing its internal state and connectors.
82
+ STOPPED = 0
83
+ PROVISIONING = 1
84
+ PROVISIONED = 2
85
+ STOPPING = 3
95
86
 
96
- The pipeline remains in this state until:
87
+ @staticmethod
88
+ def from_str(value):
89
+ for member in DeploymentResourcesStatus:
90
+ if member.name.lower() == value.lower():
91
+ return member
92
+ raise ValueError(
93
+ f"Unknown value '{value}' for enum {DeploymentResourcesStatus.__name__}"
94
+ )
97
95
 
98
- 1. Initialization succeeds, transitioning to `PAUSED`.
99
- 2. Initialization fails or times out, transitioning to `STOPPING`.
100
- 3. The user suspends the pipeline via `/suspend`, transitioning to `SUSPENDING`.
101
- 4. The user stops the pipeline via `/stop`, transitioning to `STOPPING`.
102
- """
103
96
 
104
- PAUSED = 4
97
+ class DeploymentRuntimeDesiredStatus(Enum):
105
98
  """
106
- The pipeline is initialized but data processing is paused.
107
-
108
- The pipeline remains in this state until:
109
-
110
- 1. The user starts it via `/start`, transitioning to `RUNNING`.
111
- 2. A runtime error occurs, transitioning to `STOPPING`.
112
- 3. The user suspends it via `/suspend`, transitioning to `SUSPENDING`.
113
- 4. The user stops it via `/stop`, transitioning to `STOPPING`.
99
+ Deployment runtime desired status of the pipeline.
114
100
  """
115
101
 
116
- RUNNING = 5
117
- """
118
- The pipeline is processing data.
102
+ UNAVAILABLE = 0
103
+ STANDBY = 1
104
+ PAUSED = 2
105
+ RUNNING = 3
106
+ SUSPENDED = 4
119
107
 
120
- The pipeline remains in this state until:
108
+ @staticmethod
109
+ def from_str(value):
110
+ for member in DeploymentRuntimeDesiredStatus:
111
+ if member.name.lower() == value.lower():
112
+ return member
113
+ raise ValueError(
114
+ f"Unknown value '{value}' for enum {DeploymentRuntimeDesiredStatus.__name__}"
115
+ )
121
116
 
122
- 1. The user pauses it via `/pause`, transitioning to `PAUSED`.
123
- 2. A runtime error occurs, transitioning to `STOPPING`.
124
- 3. The user suspends it via `/suspend`, transitioning to `SUSPENDING`.
125
- 4. The user stops it via `/stop`, transitioning to `STOPPING`.
126
- """
127
117
 
128
- UNAVAILABLE = 6
118
+ class DeploymentRuntimeStatus(Enum):
129
119
  """
130
- The pipeline was initialized at least once but is currently unreachable
131
- or not ready.
120
+ Deployment runtime status of the pipeline.
121
+ """
122
+
123
+ UNAVAILABLE = 0
124
+ STANDBY = 1
125
+ AWAITINGAPPROVAL = 2
126
+ INITIALIZING = 3
127
+ BOOTSTRAPPING = 4
128
+ REPLAYING = 5
129
+ PAUSED = 6
130
+ RUNNING = 7
131
+ SUSPENDED = 8
132
132
 
133
- The pipeline remains in this state until:
133
+ @staticmethod
134
+ def from_str(value):
135
+ for member in DeploymentRuntimeStatus:
136
+ if member.name.lower() == value.lower():
137
+ return member
138
+ raise ValueError(
139
+ f"Unknown value '{value}' for enum {DeploymentRuntimeStatus.__name__}"
140
+ )
134
141
 
135
- 1. A successful status check transitions it back to `PAUSED` or `RUNNING`.
136
- 2. A runtime error occurs, transitioning to `STOPPING`.
137
- 3. The user suspends it via `/suspend`, transitioning to `SUSPENDING`.
138
- 4. The user stops it via `/stop`, transitioning to `STOPPING`.
139
142
 
140
- Note: While in this state, `/start` or `/pause` express desired state but
141
- are only applied once the pipeline becomes reachable.
143
+ class PipelineStatus(Enum):
142
144
  """
143
-
144
- SUSPENDING = 7
145
+ Represents the state that this pipeline is currently in.
145
146
  """
146
- The pipeline is being suspended to storage.
147
147
 
148
- The pipeline remains in this state until:
148
+ NOT_FOUND = 0
149
+ STOPPED = 1
150
+ PROVISIONING = 2
151
+ UNAVAILABLE = 3
152
+ STANDBY = 4
153
+ AWAITINGAPPROVAL = 5
154
+ INITIALIZING = 6
155
+ BOOTSTRAPPING = 7
156
+ REPLAYING = 8
157
+ PAUSED = 9
158
+ RUNNING = 10
159
+ SUSPENDED = 11
160
+ STOPPING = 12
149
161
 
150
- 1. Suspension succeeds, transitioning to `STOPPING`.
151
- 2. A runtime error occurs, transitioning to `STOPPING`.
152
- """
162
+ @staticmethod
163
+ def from_str(value):
164
+ for member in PipelineStatus:
165
+ if member.name.lower() == value.lower():
166
+ return member
167
+ raise ValueError(f"Unknown value '{value}' for enum {PipelineStatus.__name__}")
168
+
169
+ def __eq__(self, other):
170
+ return self.value == other.value
153
171
 
154
- STOPPING = 8
155
- """
156
- The pipeline's compute resources are being scaled down to zero.
157
172
 
158
- The pipeline remains in this state until deallocation completes,
159
- transitioning to `STOPPED`.
173
+ class TransactionStatus(Enum):
174
+ """
175
+ Represents the transaction handling status of a pipeline.
160
176
  """
161
177
 
178
+ NoTransaction = 1
179
+ """There is currently no active transaction."""
180
+
181
+ TransactionInProgress = 2
182
+ """There is an active transaction in progress."""
183
+
184
+ CommitInProgress = 3
185
+ """A commit is currently in progress."""
186
+
162
187
  @staticmethod
163
188
  def from_str(value):
164
- for member in PipelineStatus:
189
+ for member in TransactionStatus:
165
190
  if member.name.lower() == value.lower():
166
191
  return member
167
- raise ValueError(f"Unknown value '{value}' for enum {PipelineStatus.__name__}")
192
+ raise ValueError(
193
+ f"Unknown value '{value}' for enum {TransactionStatus.__name__}"
194
+ )
168
195
 
169
196
  def __eq__(self, other):
170
197
  return self.value == other.value
@@ -311,3 +338,29 @@ class FaultToleranceModel(Enum):
311
338
  raise ValueError(
312
339
  f"Unknown value '{value}' for enum {FaultToleranceModel.__name__}"
313
340
  )
341
+
342
+
343
+ class PipelineFieldSelector(Enum):
344
+ ALL = "all"
345
+ """Select all fields of a pipeline."""
346
+
347
+ STATUS = "status"
348
+ """Select only the fields required to know the status of a pipeline."""
349
+
350
+
351
+ class BootstrapPolicy(Enum):
352
+ AWAIT_APPROVAL = "await_approval"
353
+ ALLOW = "allow"
354
+ REJECT = "reject"
355
+
356
+
357
+ class CompletionTokenStatus(Enum):
358
+ COMPLETE = "complete"
359
+ """
360
+ Feldera has completed processing all inputs represented by this token.
361
+ """
362
+
363
+ IN_PROGRESS = "inprogress"
364
+ """
365
+ Feldera is still processing the inputs represented by this token.
366
+ """
@@ -1,7 +1,8 @@
1
1
  import pandas as pd
2
+
2
3
  from typing import Optional
4
+ from threading import Event
3
5
 
4
- from queue import Queue
5
6
  from feldera import FelderaClient
6
7
  from feldera._callback_runner import CallbackRunner
7
8
 
@@ -12,7 +13,6 @@ class OutputHandler:
12
13
  client: FelderaClient,
13
14
  pipeline_name: str,
14
15
  view_name: str,
15
- queue: Optional[Queue],
16
16
  ):
17
17
  """
18
18
  Initializes the output handler, but doesn't start it.
@@ -22,17 +22,26 @@ class OutputHandler:
22
22
  self.client: FelderaClient = client
23
23
  self.pipeline_name: str = pipeline_name
24
24
  self.view_name: str = view_name
25
- self.queue: Optional[Queue] = queue
26
25
  self.buffer: list[pd.DataFrame] = []
26
+ self.exception: Optional[BaseException] = None
27
+ self.event = Event()
27
28
 
28
29
  # the callback that is passed to the `CallbackRunner`
29
30
  def callback(df: pd.DataFrame, _: int):
30
31
  if not df.empty:
31
32
  self.buffer.append(df)
32
33
 
34
+ def exception_callback(exception: BaseException):
35
+ self.exception = exception
36
+
33
37
  # sets up the callback runner
34
38
  self.handler = CallbackRunner(
35
- self.client, self.pipeline_name, self.view_name, callback, queue
39
+ self.client,
40
+ self.pipeline_name,
41
+ self.view_name,
42
+ callback,
43
+ exception_callback,
44
+ self.event,
36
45
  )
37
46
 
38
47
  def start(self):
@@ -41,6 +50,7 @@ class OutputHandler:
41
50
  """
42
51
 
43
52
  self.handler.start()
53
+ _ = self.event.wait()
44
54
 
45
55
  def to_pandas(self, clear_buffer: bool = True):
46
56
  """
@@ -49,6 +59,8 @@ class OutputHandler:
49
59
  :param clear_buffer: Whether to clear the buffer after getting the output.
50
60
  """
51
61
 
62
+ if self.exception is not None:
63
+ raise self.exception
52
64
  if len(self.buffer) == 0:
53
65
  return pd.DataFrame()
54
66
  res = pd.concat(self.buffer, ignore_index=True)