feldera 0.153.0__tar.gz → 0.186.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.
Files changed (37) hide show
  1. feldera-0.186.0/PKG-INFO +163 -0
  2. feldera-0.186.0/README.md +139 -0
  3. {feldera-0.153.0 → feldera-0.186.0}/feldera/__init__.py +2 -2
  4. feldera-0.186.0/feldera/_callback_runner.py +95 -0
  5. {feldera-0.153.0 → feldera-0.186.0}/feldera/_helpers.py +7 -1
  6. {feldera-0.153.0 → feldera-0.186.0}/feldera/enums.py +33 -13
  7. {feldera-0.153.0 → feldera-0.186.0}/feldera/output_handler.py +16 -4
  8. {feldera-0.153.0 → feldera-0.186.0}/feldera/pipeline.py +233 -117
  9. {feldera-0.153.0 → feldera-0.186.0}/feldera/pipeline_builder.py +11 -7
  10. feldera-0.186.0/feldera/rest/_helpers.py +40 -0
  11. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/_httprequests.py +365 -224
  12. feldera-0.186.0/feldera/rest/config.py +44 -0
  13. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/feldera_client.py +272 -124
  14. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/pipeline.py +2 -0
  15. {feldera-0.153.0 → feldera-0.186.0}/feldera/runtime_config.py +2 -0
  16. {feldera-0.153.0 → feldera-0.186.0}/feldera/testutils.py +3 -16
  17. feldera-0.186.0/feldera.egg-info/PKG-INFO +163 -0
  18. {feldera-0.153.0 → feldera-0.186.0}/pyproject.toml +1 -2
  19. feldera-0.153.0/PKG-INFO +0 -103
  20. feldera-0.153.0/README.md +0 -79
  21. feldera-0.153.0/feldera/_callback_runner.py +0 -123
  22. feldera-0.153.0/feldera/rest/_helpers.py +0 -9
  23. feldera-0.153.0/feldera/rest/config.py +0 -47
  24. feldera-0.153.0/feldera.egg-info/PKG-INFO +0 -103
  25. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/__init__.py +0 -0
  26. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/errors.py +0 -0
  27. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/feldera_config.py +0 -0
  28. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/sql_table.py +0 -0
  29. {feldera-0.153.0 → feldera-0.186.0}/feldera/rest/sql_view.py +0 -0
  30. {feldera-0.153.0 → feldera-0.186.0}/feldera/stats.py +0 -0
  31. {feldera-0.153.0 → feldera-0.186.0}/feldera/tests/test_datafusionize.py +0 -0
  32. {feldera-0.153.0 → feldera-0.186.0}/feldera/testutils_oidc.py +0 -0
  33. {feldera-0.153.0 → feldera-0.186.0}/feldera.egg-info/SOURCES.txt +0 -0
  34. {feldera-0.153.0 → feldera-0.186.0}/feldera.egg-info/dependency_links.txt +0 -0
  35. {feldera-0.153.0 → feldera-0.186.0}/feldera.egg-info/requires.txt +0 -0
  36. {feldera-0.153.0 → feldera-0.186.0}/feldera.egg-info/top_level.txt +0 -0
  37. {feldera-0.153.0 → feldera-0.186.0}/setup.cfg +0 -0
@@ -0,0 +1,163 @@
1
+ Metadata-Version: 2.4
2
+ Name: feldera
3
+ Version: 0.186.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
+ The `feldera` Python package is the Python client for the Feldera HTTP API.
28
+
29
+ The Python SDK documentation is available at: https://docs.feldera.com/python
30
+
31
+ ## Getting started
32
+
33
+ ### Installation
34
+
35
+ ```bash
36
+ uv pip install feldera
37
+ ```
38
+
39
+ ### Example usage
40
+
41
+ The Python client interacts with the API server of the Feldera instance.
42
+
43
+ ```python
44
+ # File: example.py
45
+ from feldera import FelderaClient, PipelineBuilder, Pipeline
46
+
47
+ # Instantiate client
48
+ client = FelderaClient() # Default: http://localhost:8080 without authentication
49
+ # client = FelderaClient(url="https://localhost:8080", api_key="apikey:...", requests_verify="/path/to/tls.crt")
50
+
51
+ # (Re)create pipeline
52
+ name = "example"
53
+ sql = """
54
+ CREATE TABLE t1 (i1 INT) WITH ('materialized' = 'true');
55
+ CREATE MATERIALIZED VIEW v1 AS SELECT * FROM t1;
56
+ """
57
+ print("(Re)creating pipeline...")
58
+ pipeline = PipelineBuilder(client, name, sql).create_or_replace()
59
+ pipeline.start()
60
+ print(f"Pipeline status: {pipeline.status()}")
61
+ pipeline.pause()
62
+ print(f"Pipeline status: {pipeline.status()}")
63
+ pipeline.stop(force=True)
64
+
65
+ # Find existing pipeline
66
+ pipeline = Pipeline.get(name, client)
67
+ pipeline.start()
68
+ print(f"Pipeline status: {pipeline.status()}")
69
+ pipeline.stop(force=True)
70
+ pipeline.clear_storage()
71
+ ```
72
+
73
+ Run using:
74
+ ```bash
75
+ uv run python example.py
76
+ ```
77
+
78
+ ### Environment variables
79
+
80
+ Some default parameter values in the Python SDK can be overridden via environment variables.
81
+
82
+ **Environment variables for `FelderaClient(...)`**
83
+
84
+ ```bash
85
+ export FELDERA_HOST="https://localhost:8080" # Overrides default for `url`
86
+ export FELDERA_API_KEY="apikey:..." # Overrides default for `api_key`
87
+
88
+ # The following together override default for `requests_verify`
89
+ # export FELDERA_TLS_INSECURE="false" # If set to "1", "true" or "yes" (all case-insensitive), disables TLS certificate verification
90
+ # export FELDERA_HTTPS_TLS_CERT="/path/to/tls.crt" # Custom TLS certificate
91
+ ```
92
+
93
+ **Environment variables for `PipelineBuilder(...)`**
94
+
95
+ ```bash
96
+ export FELDERA_RUNTIME_VERSION="..." # Overrides default for `runtime_version`
97
+ ```
98
+
99
+ ## Development
100
+
101
+ Development assumes you have cloned the Feldera code repository.
102
+
103
+ ### Installation
104
+
105
+ ```bash
106
+ cd python
107
+ # Optional: create and activate virtual environment if you don't have one
108
+ uv venv
109
+ source .venv/bin/activate
110
+ # Install in editable mode
111
+ uv pip install -e .
112
+ ```
113
+
114
+ ### Formatting
115
+
116
+ Formatting requires the `ruff` package: `uv pip install ruff`
117
+
118
+ ```bash
119
+ cd python
120
+ ruff check
121
+ ruff format
122
+ ```
123
+
124
+ ### Tests
125
+
126
+ Running the test requires the `pytest` package: `uv pip install pytest`
127
+
128
+ ```bash
129
+ # All tests
130
+ cd python
131
+ uv run python -m pytest tests/
132
+
133
+ # Specific tests directory
134
+ uv run python -m pytest tests/platform/
135
+
136
+ # Specific test file
137
+ uv run python -m pytest tests/platform/test_pipeline_crud.py
138
+ ```
139
+
140
+ For further information about the tests, please see `tests/README.md`.
141
+
142
+ ### Documentation
143
+
144
+ Building documentation requires the `sphinx` package: `uv pip install sphinx`
145
+
146
+ ```bash
147
+ cd python/docs
148
+ sphinx-apidoc -o . ../feldera
149
+ make html
150
+ make clean # Cleanup afterwards
151
+ ```
152
+
153
+ ### Installation from GitHub
154
+
155
+ Latest `main` branch:
156
+ ```bash
157
+ uv pip install git+https://github.com/feldera/feldera#subdirectory=python
158
+ ```
159
+
160
+ Different branch (replace `BRANCH_NAME`):
161
+ ```bash
162
+ uv pip install git+https://github.com/feldera/feldera@BRANCH_NAME#subdirectory=python
163
+ ```
@@ -0,0 +1,139 @@
1
+ # Feldera Python SDK
2
+
3
+ The `feldera` Python package is the Python client for the Feldera HTTP API.
4
+
5
+ The Python SDK documentation is available at: https://docs.feldera.com/python
6
+
7
+ ## Getting started
8
+
9
+ ### Installation
10
+
11
+ ```bash
12
+ uv pip install feldera
13
+ ```
14
+
15
+ ### Example usage
16
+
17
+ The Python client interacts with the API server of the Feldera instance.
18
+
19
+ ```python
20
+ # File: example.py
21
+ from feldera import FelderaClient, PipelineBuilder, Pipeline
22
+
23
+ # Instantiate client
24
+ client = FelderaClient() # Default: http://localhost:8080 without authentication
25
+ # client = FelderaClient(url="https://localhost:8080", api_key="apikey:...", requests_verify="/path/to/tls.crt")
26
+
27
+ # (Re)create pipeline
28
+ name = "example"
29
+ sql = """
30
+ CREATE TABLE t1 (i1 INT) WITH ('materialized' = 'true');
31
+ CREATE MATERIALIZED VIEW v1 AS SELECT * FROM t1;
32
+ """
33
+ print("(Re)creating pipeline...")
34
+ pipeline = PipelineBuilder(client, name, sql).create_or_replace()
35
+ pipeline.start()
36
+ print(f"Pipeline status: {pipeline.status()}")
37
+ pipeline.pause()
38
+ print(f"Pipeline status: {pipeline.status()}")
39
+ pipeline.stop(force=True)
40
+
41
+ # Find existing pipeline
42
+ pipeline = Pipeline.get(name, client)
43
+ pipeline.start()
44
+ print(f"Pipeline status: {pipeline.status()}")
45
+ pipeline.stop(force=True)
46
+ pipeline.clear_storage()
47
+ ```
48
+
49
+ Run using:
50
+ ```bash
51
+ uv run python example.py
52
+ ```
53
+
54
+ ### Environment variables
55
+
56
+ Some default parameter values in the Python SDK can be overridden via environment variables.
57
+
58
+ **Environment variables for `FelderaClient(...)`**
59
+
60
+ ```bash
61
+ export FELDERA_HOST="https://localhost:8080" # Overrides default for `url`
62
+ export FELDERA_API_KEY="apikey:..." # Overrides default for `api_key`
63
+
64
+ # The following together override default for `requests_verify`
65
+ # export FELDERA_TLS_INSECURE="false" # If set to "1", "true" or "yes" (all case-insensitive), disables TLS certificate verification
66
+ # export FELDERA_HTTPS_TLS_CERT="/path/to/tls.crt" # Custom TLS certificate
67
+ ```
68
+
69
+ **Environment variables for `PipelineBuilder(...)`**
70
+
71
+ ```bash
72
+ export FELDERA_RUNTIME_VERSION="..." # Overrides default for `runtime_version`
73
+ ```
74
+
75
+ ## Development
76
+
77
+ Development assumes you have cloned the Feldera code repository.
78
+
79
+ ### Installation
80
+
81
+ ```bash
82
+ cd python
83
+ # Optional: create and activate virtual environment if you don't have one
84
+ uv venv
85
+ source .venv/bin/activate
86
+ # Install in editable mode
87
+ uv pip install -e .
88
+ ```
89
+
90
+ ### Formatting
91
+
92
+ Formatting requires the `ruff` package: `uv pip install ruff`
93
+
94
+ ```bash
95
+ cd python
96
+ ruff check
97
+ ruff format
98
+ ```
99
+
100
+ ### Tests
101
+
102
+ Running the test requires the `pytest` package: `uv pip install pytest`
103
+
104
+ ```bash
105
+ # All tests
106
+ cd python
107
+ uv run python -m pytest tests/
108
+
109
+ # Specific tests directory
110
+ uv run python -m pytest tests/platform/
111
+
112
+ # Specific test file
113
+ uv run python -m pytest tests/platform/test_pipeline_crud.py
114
+ ```
115
+
116
+ For further information about the tests, please see `tests/README.md`.
117
+
118
+ ### Documentation
119
+
120
+ Building documentation requires the `sphinx` package: `uv pip install sphinx`
121
+
122
+ ```bash
123
+ cd python/docs
124
+ sphinx-apidoc -o . ../feldera
125
+ make html
126
+ make clean # Cleanup afterwards
127
+ ```
128
+
129
+ ### Installation from GitHub
130
+
131
+ Latest `main` branch:
132
+ ```bash
133
+ uv pip install git+https://github.com/feldera/feldera#subdirectory=python
134
+ ```
135
+
136
+ Different branch (replace `BRANCH_NAME`):
137
+ ```bash
138
+ uv pip install git+https://github.com/feldera/feldera@BRANCH_NAME#subdirectory=python
139
+ ```
@@ -1,9 +1,9 @@
1
1
  from feldera.rest.feldera_client import FelderaClient as FelderaClient
2
2
  from feldera.pipeline import Pipeline as Pipeline
3
3
  from feldera.pipeline_builder import PipelineBuilder as PipelineBuilder
4
- from feldera.rest._helpers import client_version
4
+ from feldera.rest._helpers import determine_client_version
5
5
 
6
- __version__ = client_version()
6
+ __version__ = determine_client_version()
7
7
 
8
8
  import pretty_errors
9
9
 
@@ -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]], fields: list[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 = {}
@@ -122,12 +122,13 @@ class DeploymentRuntimeStatus(Enum):
122
122
 
123
123
  UNAVAILABLE = 0
124
124
  STANDBY = 1
125
- INITIALIZING = 2
126
- BOOTSTRAPPING = 3
127
- REPLAYING = 4
128
- PAUSED = 5
129
- RUNNING = 6
130
- SUSPENDED = 7
125
+ AWAITINGAPPROVAL = 2
126
+ INITIALIZING = 3
127
+ BOOTSTRAPPING = 4
128
+ REPLAYING = 5
129
+ PAUSED = 6
130
+ RUNNING = 7
131
+ SUSPENDED = 8
131
132
 
132
133
  @staticmethod
133
134
  def from_str(value):
@@ -149,13 +150,14 @@ class PipelineStatus(Enum):
149
150
  PROVISIONING = 2
150
151
  UNAVAILABLE = 3
151
152
  STANDBY = 4
152
- INITIALIZING = 5
153
- BOOTSTRAPPING = 6
154
- REPLAYING = 7
155
- PAUSED = 8
156
- RUNNING = 9
157
- SUSPENDED = 10
158
- STOPPING = 11
153
+ AWAITINGAPPROVAL = 5
154
+ INITIALIZING = 6
155
+ BOOTSTRAPPING = 7
156
+ REPLAYING = 8
157
+ PAUSED = 9
158
+ RUNNING = 10
159
+ SUSPENDED = 11
160
+ STOPPING = 12
159
161
 
160
162
  @staticmethod
161
163
  def from_str(value):
@@ -344,3 +346,21 @@ class PipelineFieldSelector(Enum):
344
346
 
345
347
  STATUS = "status"
346
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)