feldera 0.111.0__tar.gz → 0.113.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 (32) hide show
  1. {feldera-0.111.0 → feldera-0.113.0}/PKG-INFO +5 -5
  2. {feldera-0.111.0 → feldera-0.113.0}/README.md +4 -4
  3. {feldera-0.111.0 → feldera-0.113.0}/feldera/_callback_runner.py +3 -1
  4. {feldera-0.111.0 → feldera-0.113.0}/feldera/pipeline.py +8 -6
  5. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/_httprequests.py +50 -33
  6. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/feldera_client.py +15 -8
  7. {feldera-0.111.0 → feldera-0.113.0}/feldera.egg-info/PKG-INFO +5 -5
  8. {feldera-0.111.0 → feldera-0.113.0}/pyproject.toml +1 -1
  9. {feldera-0.111.0 → feldera-0.113.0}/tests/test_shared_pipeline0.py +1 -30
  10. {feldera-0.111.0 → feldera-0.113.0}/tests/test_shared_pipeline1.py +30 -4
  11. {feldera-0.111.0 → feldera-0.113.0}/feldera/__init__.py +0 -0
  12. {feldera-0.111.0 → feldera-0.113.0}/feldera/_helpers.py +0 -0
  13. {feldera-0.111.0 → feldera-0.113.0}/feldera/enums.py +0 -0
  14. {feldera-0.111.0 → feldera-0.113.0}/feldera/output_handler.py +0 -0
  15. {feldera-0.111.0 → feldera-0.113.0}/feldera/pipeline_builder.py +0 -0
  16. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/__init__.py +0 -0
  17. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/_helpers.py +0 -0
  18. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/config.py +0 -0
  19. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/errors.py +0 -0
  20. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/feldera_config.py +0 -0
  21. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/pipeline.py +0 -0
  22. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/sql_table.py +0 -0
  23. {feldera-0.111.0 → feldera-0.113.0}/feldera/rest/sql_view.py +0 -0
  24. {feldera-0.111.0 → feldera-0.113.0}/feldera/runtime_config.py +0 -0
  25. {feldera-0.111.0 → feldera-0.113.0}/feldera/stats.py +0 -0
  26. {feldera-0.111.0 → feldera-0.113.0}/feldera.egg-info/SOURCES.txt +0 -0
  27. {feldera-0.111.0 → feldera-0.113.0}/feldera.egg-info/dependency_links.txt +0 -0
  28. {feldera-0.111.0 → feldera-0.113.0}/feldera.egg-info/requires.txt +0 -0
  29. {feldera-0.111.0 → feldera-0.113.0}/feldera.egg-info/top_level.txt +0 -0
  30. {feldera-0.111.0 → feldera-0.113.0}/setup.cfg +0 -0
  31. {feldera-0.111.0 → feldera-0.113.0}/tests/test_pipeline_builder.py +0 -0
  32. {feldera-0.111.0 → feldera-0.113.0}/tests/test_udf.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feldera
3
- Version: 0.111.0
3
+ Version: 0.113.0
4
4
  Summary: The feldera python client
5
5
  Author-email: Feldera Team <dev@feldera.com>
6
6
  License: MIT
@@ -92,14 +92,14 @@ To run tests from a specific file:
92
92
  (cd python && python3 -m pytest ./tests/path-to-file.py)
93
93
  ```
94
94
 
95
- #### Running Aggregate Tests
95
+ #### Running Tests
96
96
 
97
- The aggregate tests validate end-to-end correctness of SQL functionality.
98
- To run the aggregate tests use:
97
+ The tests validate end-to-end correctness of SQL functionality. To
98
+ run the tests use:
99
99
 
100
100
  ```bash
101
101
  cd python
102
- PYTHONPATH=`pwd` python3 ./tests/aggregate_tests/main.py
102
+ PYTHONPATH=`pwd` ./tests/run-all-tests.sh
103
103
  ```
104
104
 
105
105
  ### Reducing Compilation Cycles
@@ -69,14 +69,14 @@ To run tests from a specific file:
69
69
  (cd python && python3 -m pytest ./tests/path-to-file.py)
70
70
  ```
71
71
 
72
- #### Running Aggregate Tests
72
+ #### Running Tests
73
73
 
74
- The aggregate tests validate end-to-end correctness of SQL functionality.
75
- To run the aggregate tests use:
74
+ The tests validate end-to-end correctness of SQL functionality. To
75
+ run the tests use:
76
76
 
77
77
  ```bash
78
78
  cd python
79
- PYTHONPATH=`pwd` python3 ./tests/aggregate_tests/main.py
79
+ PYTHONPATH=`pwd` ./tests/run-all-tests.sh
80
80
  ```
81
81
 
82
82
  ### Reducing Compilation Cycles
@@ -75,7 +75,9 @@ class CallbackRunner(Thread):
75
75
  # stop blocking the main thread on `join` for the previous message
76
76
  self.queue.task_done()
77
77
 
78
- for chunk in gen_obj:
78
+ iterator = gen_obj()
79
+
80
+ for chunk in iterator:
79
81
  chunk: dict = chunk
80
82
  data: Optional[list[dict]] = chunk.get("json_data")
81
83
  seq_no: Optional[int] = chunk.get("sequence_number")
@@ -484,7 +484,9 @@ metrics"""
484
484
  for view_name, queue in self.views_tx.pop().items():
485
485
  # block until the callback runner has been stopped
486
486
  queue.join()
487
+ import time
487
488
 
489
+ time.sleep(3)
488
490
  self.client.stop_pipeline(self.name, force=force, timeout_s=timeout_s)
489
491
 
490
492
  def resume(self, timeout_s: Optional[float] = None):
@@ -838,14 +840,14 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
838
840
 
839
841
  def set_runtime_config(self, runtime_config: RuntimeConfig):
840
842
  """Updates the runtime config of the pipeline. The pipeline
841
- must be stopped and, in addition, changing some pipeline
842
- configuration requires storage to be cleared.
843
+ must be stopped. Changing some pipeline configuration, such
844
+ as the number of workers, requires storage to be cleared.
843
845
 
844
- For example, to set 'min_batch_size_records' on a pipeline:
846
+ For example, to set 'min_batch_size_records' on a pipeline::
845
847
 
846
- runtime_config = pipeline.runtime_config()
847
- runtime_config.min_batch_size_records = 500
848
- pipeline.set_runtime_config(runtime_config)
848
+ runtime_config = pipeline.runtime_config()
849
+ runtime_config.min_batch_size_records = 500
850
+ pipeline.set_runtime_config(runtime_config)
849
851
 
850
852
  """
851
853
 
@@ -12,6 +12,7 @@ import json
12
12
  import requests
13
13
  from requests.packages import urllib3
14
14
  from typing import Callable, Optional, Any, Union, Mapping, Sequence, List
15
+ import time
15
16
 
16
17
 
17
18
  def json_serialize(body: Any) -> str:
@@ -42,6 +43,7 @@ class HttpRequests:
42
43
  params: Optional[Mapping[str, Any]] = None,
43
44
  stream: bool = False,
44
45
  serialize: bool = True,
46
+ max_retries: int = 3,
45
47
  ) -> Any:
46
48
  """
47
49
  :param http_method: The HTTP method to use. Takes the equivalent `requests.*` module. (Example: `requests.get`)
@@ -68,39 +70,54 @@ class HttpRequests:
68
70
  str(params),
69
71
  )
70
72
 
71
- if http_method.__name__ == "get":
72
- request = http_method(
73
- request_path,
74
- timeout=timeout,
75
- headers=headers,
76
- params=params,
77
- stream=stream,
78
- verify=self.requests_verify,
79
- )
80
- elif isinstance(body, bytes):
81
- request = http_method(
82
- request_path,
83
- timeout=timeout,
84
- headers=headers,
85
- data=body,
86
- params=params,
87
- stream=stream,
88
- verify=self.requests_verify,
89
- )
90
- else:
91
- request = http_method(
92
- request_path,
93
- timeout=timeout,
94
- headers=headers,
95
- data=json_serialize(body) if serialize else body,
96
- params=params,
97
- stream=stream,
98
- verify=self.requests_verify,
99
- )
100
-
101
- resp = self.__validate(request, stream=stream)
102
- logging.debug("got response: %s", str(resp))
103
- return resp
73
+ for attempt in range(max_retries):
74
+ if http_method.__name__ == "get":
75
+ request = http_method(
76
+ request_path,
77
+ timeout=timeout,
78
+ headers=headers,
79
+ params=params,
80
+ stream=stream,
81
+ verify=self.requests_verify,
82
+ )
83
+ elif isinstance(body, bytes):
84
+ request = http_method(
85
+ request_path,
86
+ timeout=timeout,
87
+ headers=headers,
88
+ data=body,
89
+ params=params,
90
+ stream=stream,
91
+ verify=self.requests_verify,
92
+ )
93
+ else:
94
+ request = http_method(
95
+ request_path,
96
+ timeout=timeout,
97
+ headers=headers,
98
+ data=json_serialize(body) if serialize else body,
99
+ params=params,
100
+ stream=stream,
101
+ verify=self.requests_verify,
102
+ )
103
+
104
+ try:
105
+ resp = self.__validate(request, stream=stream)
106
+ logging.debug("got response: %s", str(resp))
107
+ return resp
108
+ except FelderaAPIError as err:
109
+ # Only retry on 503
110
+ if err.status_code == 503:
111
+ if attempt < max_retries:
112
+ logging.warning(
113
+ "HTTP 503 received for %s, retrying (%d/%d)...",
114
+ path,
115
+ attempt + 1,
116
+ max_retries,
117
+ )
118
+ time.sleep(2) # backoff, adjust as needed
119
+ continue
120
+ raise # re-raise for all other errors or if out of retries
104
121
 
105
122
  except requests.exceptions.Timeout as err:
106
123
  raise FelderaTimeoutError(str(err)) from err
@@ -272,7 +272,11 @@ class FelderaClient:
272
272
 
273
273
  if status == "Running":
274
274
  break
275
- elif status == "Failed":
275
+ elif (
276
+ status == "Stopped"
277
+ and len(resp.deployment_error or {}) > 0
278
+ and resp.deployment_desired_status == "Stopped"
279
+ ):
276
280
  raise RuntimeError(
277
281
  f"""Unable to START the pipeline.
278
282
  Reason: The pipeline is in a STOPPED state due to the following error:
@@ -601,13 +605,16 @@ Reason: The pipeline is in a STOPPED state due to the following error:
601
605
 
602
606
  end = time.monotonic() + timeout if timeout else None
603
607
 
604
- # Using the default chunk size below makes `iter_lines` extremely
605
- # inefficient when dealing with long lines.
606
- for chunk in resp.iter_lines(chunk_size=50000000):
607
- if end and time.monotonic() > end:
608
- break
609
- if chunk:
610
- yield json.loads(chunk, parse_float=Decimal)
608
+ def generator():
609
+ # Using the default chunk size below makes `iter_lines` extremely
610
+ # inefficient when dealing with long lines.
611
+ for chunk in resp.iter_lines(chunk_size=50000000):
612
+ if end and time.monotonic() > end:
613
+ break
614
+ if chunk:
615
+ yield json.loads(chunk, parse_float=Decimal)
616
+
617
+ return generator
611
618
 
612
619
  def query_as_text(
613
620
  self, pipeline_name: str, query: str
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feldera
3
- Version: 0.111.0
3
+ Version: 0.113.0
4
4
  Summary: The feldera python client
5
5
  Author-email: Feldera Team <dev@feldera.com>
6
6
  License: MIT
@@ -92,14 +92,14 @@ To run tests from a specific file:
92
92
  (cd python && python3 -m pytest ./tests/path-to-file.py)
93
93
  ```
94
94
 
95
- #### Running Aggregate Tests
95
+ #### Running Tests
96
96
 
97
- The aggregate tests validate end-to-end correctness of SQL functionality.
98
- To run the aggregate tests use:
97
+ The tests validate end-to-end correctness of SQL functionality. To
98
+ run the tests use:
99
99
 
100
100
  ```bash
101
101
  cd python
102
- PYTHONPATH=`pwd` python3 ./tests/aggregate_tests/main.py
102
+ PYTHONPATH=`pwd` ./tests/run-all-tests.sh
103
103
  ```
104
104
 
105
105
  ### Reducing Compilation Cycles
@@ -6,7 +6,7 @@ build-backend = "setuptools.build_meta"
6
6
  name = "feldera"
7
7
  readme = "README.md"
8
8
  description = "The feldera python client"
9
- version = "0.111.0"
9
+ version = "0.113.0"
10
10
  license = { text = "MIT" }
11
11
  requires-python = ">=3.10"
12
12
  authors = [
@@ -1,6 +1,5 @@
1
1
  import os
2
2
  import pathlib
3
- import threading
4
3
  import pandas as pd
5
4
  import time
6
5
  import unittest
@@ -68,35 +67,6 @@ class TestPipeline(SharedTestPipeline):
68
67
  TEST_CLIENT.pause_pipeline(self.pipeline.name)
69
68
  TEST_CLIENT.stop_pipeline(self.pipeline.name, force=True)
70
69
 
71
- def __listener(self):
72
- gen_obj = TEST_CLIENT.listen_to_pipeline(
73
- pipeline_name=self.pipeline.name,
74
- table_name="v0",
75
- format="csv",
76
- )
77
- counter = 0
78
- for chunk in gen_obj:
79
- counter += 1
80
- text_data = chunk.get("text_data")
81
- if text_data:
82
- assert text_data == "1,1\n2,1\n"
83
- self.result = True
84
- break
85
- if counter > 10:
86
- self.result = False
87
- break
88
-
89
- def test_listen_to_pipeline(self):
90
- data = "1\n2\n"
91
- TEST_CLIENT.pause_pipeline(self.pipeline.name)
92
- t1 = threading.Thread(target=self.__listener)
93
- t1.start()
94
- self.pipeline.resume()
95
- TEST_CLIENT.push_to_pipeline(self.pipeline.name, "tbl", "csv", data)
96
- t1.join()
97
- assert self.result
98
- TEST_CLIENT.stop_pipeline(self.pipeline.name, force=True)
99
-
100
70
  def test_adhoc_query_text(self):
101
71
  data = "1\n2\n"
102
72
  self.pipeline.start()
@@ -567,6 +537,7 @@ class TestPipeline(SharedTestPipeline):
567
537
  with self.assertRaises(ValueError):
568
538
  data = {"m_var": {None: 1}}
569
539
  self.pipeline.input_json("tbl_map_issue3754", [data])
540
+ self.pipeline.stop(force=True)
570
541
 
571
542
  def test_pipeline_resource_config(self):
572
543
  from feldera.runtime_config import Resources, RuntimeConfig
@@ -1,12 +1,16 @@
1
1
  import random
2
+ from uuid import uuid4
2
3
  import time
4
+ import os
3
5
  from typing import Optional
4
6
  from feldera.runtime_config import RuntimeConfig, Storage
5
7
  from tests import enterprise_only
6
8
  from tests.shared_test_pipeline import SharedTestPipeline
7
9
 
8
10
 
9
- DEFAULT_ENDPOINT = "http://minio.extra.svc.cluster.local:9000"
11
+ DEFAULT_ENDPOINT = os.environ.get(
12
+ "DEFAULT_MINIO_ENDPOINT", "http://minio.extra.svc.cluster.local:9000"
13
+ )
10
14
  DEFAULT_BUCKET = "default"
11
15
  ACCESS_KEY = "minio"
12
16
  SECRET_KEY = "miniopasswd"
@@ -36,7 +40,13 @@ def storage_cfg(
36
40
 
37
41
  class TestCheckpointSync(SharedTestPipeline):
38
42
  @enterprise_only
39
- def test_checkpoint_sync(self, from_uuid: bool = False, auth_err: bool = False):
43
+ def test_checkpoint_sync(
44
+ self,
45
+ from_uuid: bool = False,
46
+ random_uuid: bool = False,
47
+ clear_storage: bool = True,
48
+ auth_err: bool = False,
49
+ ):
40
50
  """
41
51
  CREATE TABLE t0 (c0 INT, c1 VARCHAR);
42
52
  CREATE MATERIALIZED VIEW v0 AS SELECT c0 FROM t0;
@@ -56,7 +66,12 @@ class TestCheckpointSync(SharedTestPipeline):
56
66
  uuid = self.pipeline.sync_checkpoint(wait=True)
57
67
 
58
68
  self.pipeline.stop(force=True)
59
- self.pipeline.clear_storage()
69
+
70
+ if clear_storage:
71
+ self.pipeline.clear_storage()
72
+
73
+ if random_uuid:
74
+ uuid = uuid4()
60
75
 
61
76
  # Restart pipeline from checkpoint
62
77
  storage_config = storage_cfg(
@@ -69,13 +84,24 @@ class TestCheckpointSync(SharedTestPipeline):
69
84
  self.assertCountEqual(got_before, got_after)
70
85
 
71
86
  self.pipeline.stop(force=True)
72
- self.pipeline.clear_storage()
87
+
88
+ if clear_storage:
89
+ self.pipeline.clear_storage()
73
90
 
74
91
  @enterprise_only
75
92
  def test_checkpoint_sync_from_uuid(self):
76
93
  self.test_checkpoint_sync(from_uuid=True)
77
94
 
95
+ @enterprise_only
96
+ def test_checkpoint_sync_without_clearing_storage(self):
97
+ self.test_checkpoint_sync(clear_storage=False)
98
+
78
99
  @enterprise_only
79
100
  def test_checkpoint_sync_err(self):
80
101
  with self.assertRaisesRegex(RuntimeError, "SignatureDoesNotMatch"):
81
102
  self.test_checkpoint_sync(auth_err=True)
103
+
104
+ @enterprise_only
105
+ def test_checkpoint_sync_err_nonexistent_checkpoint(self):
106
+ with self.assertRaisesRegex(RuntimeError, "were not found in source"):
107
+ self.test_checkpoint_sync(random_uuid=True, from_uuid=True)
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes