feldera 0.147.0__tar.gz → 0.149.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 (30) hide show
  1. {feldera-0.147.0 → feldera-0.149.0}/PKG-INFO +1 -1
  2. {feldera-0.147.0 → feldera-0.149.0}/feldera/_callback_runner.py +4 -1
  3. {feldera-0.147.0 → feldera-0.149.0}/feldera/enums.py +99 -93
  4. {feldera-0.147.0 → feldera-0.149.0}/feldera/pipeline.py +92 -34
  5. {feldera-0.147.0 → feldera-0.149.0}/feldera/pipeline_builder.py +5 -2
  6. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/_httprequests.py +5 -0
  7. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/feldera_client.py +139 -103
  8. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/pipeline.py +13 -0
  9. {feldera-0.147.0 → feldera-0.149.0}/feldera.egg-info/PKG-INFO +1 -1
  10. {feldera-0.147.0 → feldera-0.149.0}/pyproject.toml +1 -1
  11. {feldera-0.147.0 → feldera-0.149.0}/README.md +0 -0
  12. {feldera-0.147.0 → feldera-0.149.0}/feldera/__init__.py +0 -0
  13. {feldera-0.147.0 → feldera-0.149.0}/feldera/_helpers.py +0 -0
  14. {feldera-0.147.0 → feldera-0.149.0}/feldera/output_handler.py +0 -0
  15. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/__init__.py +0 -0
  16. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/_helpers.py +0 -0
  17. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/config.py +0 -0
  18. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/errors.py +0 -0
  19. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/feldera_config.py +0 -0
  20. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/sql_table.py +0 -0
  21. {feldera-0.147.0 → feldera-0.149.0}/feldera/rest/sql_view.py +0 -0
  22. {feldera-0.147.0 → feldera-0.149.0}/feldera/runtime_config.py +0 -0
  23. {feldera-0.147.0 → feldera-0.149.0}/feldera/stats.py +0 -0
  24. {feldera-0.147.0 → feldera-0.149.0}/feldera/tests/test_datafusionize.py +0 -0
  25. {feldera-0.147.0 → feldera-0.149.0}/feldera/testutils.py +0 -0
  26. {feldera-0.147.0 → feldera-0.149.0}/feldera.egg-info/SOURCES.txt +0 -0
  27. {feldera-0.147.0 → feldera-0.149.0}/feldera.egg-info/dependency_links.txt +0 -0
  28. {feldera-0.147.0 → feldera-0.149.0}/feldera.egg-info/requires.txt +0 -0
  29. {feldera-0.147.0 → feldera-0.149.0}/feldera.egg-info/top_level.txt +0 -0
  30. {feldera-0.147.0 → feldera-0.149.0}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feldera
3
- Version: 0.147.0
3
+ Version: 0.149.0
4
4
  Summary: The feldera python client
5
5
  Author-email: Feldera Team <dev@feldera.com>
6
6
  License: MIT
@@ -6,6 +6,7 @@ from queue import Queue, Empty
6
6
  import pandas as pd
7
7
  from feldera import FelderaClient
8
8
  from feldera._helpers import dataframe_from_response
9
+ from feldera.enums import PipelineFieldSelector
9
10
 
10
11
 
11
12
  class _CallbackRunnerInstruction(Enum):
@@ -38,7 +39,9 @@ class CallbackRunner(Thread):
38
39
  :meta private:
39
40
  """
40
41
 
41
- pipeline = self.client.get_pipeline(self.pipeline_name)
42
+ pipeline = self.client.get_pipeline(
43
+ self.pipeline_name, PipelineFieldSelector.ALL
44
+ )
42
45
 
43
46
  schemas = pipeline.tables + pipeline.views
44
47
  for schema in schemas:
@@ -34,131 +34,129 @@ 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.
132
-
133
- The pipeline remains in this state until:
134
-
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
-
140
- Note: While in this state, `/start` or `/pause` express desired state but
141
- are only applied once the pipeline becomes reachable.
120
+ Deployment runtime status of the pipeline.
142
121
  """
143
122
 
144
- SUSPENDING = 7
145
- """
146
- The pipeline is being suspended to storage.
123
+ UNAVAILABLE = 0
124
+ STANDBY = 1
125
+ INITIALIZING = 2
126
+ BOOTSTRAPPING = 3
127
+ REPLAYING = 4
128
+ PAUSED = 5
129
+ RUNNING = 6
130
+ SUSPENDED = 7
147
131
 
148
- The pipeline remains in this state until:
132
+ @staticmethod
133
+ def from_str(value):
134
+ for member in DeploymentRuntimeStatus:
135
+ if member.name.lower() == value.lower():
136
+ return member
137
+ raise ValueError(
138
+ f"Unknown value '{value}' for enum {DeploymentRuntimeStatus.__name__}"
139
+ )
149
140
 
150
- 1. Suspension succeeds, transitioning to `STOPPING`.
151
- 2. A runtime error occurs, transitioning to `STOPPING`.
152
- """
153
141
 
154
- STOPPING = 8
142
+ class PipelineStatus(Enum):
155
143
  """
156
- The pipeline's compute resources are being scaled down to zero.
157
-
158
- The pipeline remains in this state until deallocation completes,
159
- transitioning to `STOPPED`.
144
+ Represents the state that this pipeline is currently in.
160
145
  """
161
146
 
147
+ NOT_FOUND = 0
148
+ STOPPED = 1
149
+ PROVISIONING = 2
150
+ UNAVAILABLE = 3
151
+ STANDBY = 4
152
+ INITIALIZING = 5
153
+ BOOTSTRAPPING = 6
154
+ REPLAYING = 7
155
+ PAUSED = 8
156
+ RUNNING = 9
157
+ SUSPENDED = 10
158
+ STOPPING = 11
159
+
162
160
  @staticmethod
163
161
  def from_str(value):
164
162
  for member in PipelineStatus:
@@ -338,3 +336,11 @@ class FaultToleranceModel(Enum):
338
336
  raise ValueError(
339
337
  f"Unknown value '{value}' for enum {FaultToleranceModel.__name__}"
340
338
  )
339
+
340
+
341
+ class PipelineFieldSelector(Enum):
342
+ ALL = "all"
343
+ """Select all fields of a pipeline."""
344
+
345
+ STATUS = "status"
346
+ """Select only the fields required to know the status of a pipeline."""
@@ -12,12 +12,18 @@ from queue import Queue
12
12
 
13
13
  from feldera.rest.errors import FelderaAPIError
14
14
  from feldera.enums import (
15
+ PipelineFieldSelector,
15
16
  PipelineStatus,
16
17
  ProgramStatus,
17
18
  CheckpointStatus,
18
19
  TransactionStatus,
20
+ StorageStatus,
21
+ DeploymentDesiredStatus,
22
+ DeploymentResourcesDesiredStatus,
23
+ DeploymentResourcesStatus,
24
+ DeploymentRuntimeDesiredStatus,
25
+ DeploymentRuntimeStatus,
19
26
  )
20
- from feldera.enums import StorageStatus
21
27
  from feldera.rest.pipeline import Pipeline as InnerPipeline
22
28
  from feldera.rest.feldera_client import FelderaClient
23
29
  from feldera._callback_runner import _CallbackRunnerInstruction, CallbackRunner
@@ -55,14 +61,16 @@ class Pipeline:
55
61
  # block until the callback runner is ready
56
62
  queue.join()
57
63
 
58
- def refresh(self):
64
+ def refresh(self, field_selector: PipelineFieldSelector):
59
65
  """
60
66
  Calls the backend to get the updated, latest version of the pipeline.
61
67
 
68
+ :param field_selector: Choose what pipeline information to refresh; see PipelineFieldSelector enum definition.
69
+
62
70
  :raises FelderaConnectionError: If there is an issue connecting to the backend.
63
71
  """
64
72
 
65
- self._inner = self.client.get_pipeline(self.name)
73
+ self._inner = self.client.get_pipeline(self.name, field_selector)
66
74
 
67
75
  def status(self) -> PipelineStatus:
68
76
  """
@@ -70,7 +78,7 @@ class Pipeline:
70
78
  """
71
79
 
72
80
  try:
73
- self.refresh()
81
+ self.refresh(PipelineFieldSelector.STATUS)
74
82
  return PipelineStatus.from_str(self._inner.deployment_status)
75
83
 
76
84
  except FelderaAPIError as err:
@@ -119,7 +127,7 @@ class Pipeline:
119
127
 
120
128
  ensure_dataframe_has_columns(df)
121
129
 
122
- pipeline = self.client.get_pipeline(self.name)
130
+ pipeline = self.client.get_pipeline(self.name, PipelineFieldSelector.ALL)
123
131
  if table_name.lower() != "now" and table_name.lower() not in [
124
132
  tbl.name.lower() for tbl in pipeline.tables
125
133
  ]:
@@ -393,9 +401,7 @@ method or use `Pipeline.resume()` to resume a paused pipeline."""
393
401
 
394
402
  return
395
403
 
396
- self.client.pause_pipeline(
397
- self.name, "Unable to START the pipeline.\n", wait=wait, timeout_s=timeout_s
398
- )
404
+ self.client.start_pipeline_as_paused(self.name, wait=wait, timeout_s=timeout_s)
399
405
  self.__setup_output_listeners()
400
406
  self.resume(timeout_s=timeout_s)
401
407
 
@@ -505,6 +511,20 @@ metrics"""
505
511
 
506
512
  self.client.activate_pipeline(self.name, wait=wait, timeout_s=timeout_s)
507
513
 
514
+ def start_paused(self, wait: bool = True, timeout_s: Optional[float] = None):
515
+ """
516
+ Starts the pipeline in the paused state.
517
+ """
518
+
519
+ self.client.start_pipeline_as_paused(self.name, wait=wait, timeout_s=timeout_s)
520
+
521
+ def start_standby(self, wait: bool = True, timeout_s: Optional[float] = None):
522
+ """
523
+ Starts the pipeline in the standby state.
524
+ """
525
+
526
+ self.client.start_pipeline_as_standby(self.name, wait=wait, timeout_s=timeout_s)
527
+
508
528
  def pause(self, wait: bool = True, timeout_s: Optional[float] = None):
509
529
  """
510
530
  Pause the pipeline.
@@ -562,7 +582,7 @@ metrics"""
562
582
  pipeline to resume.
563
583
  """
564
584
 
565
- self.client.start_pipeline(self.name, wait=wait, timeout_s=timeout_s)
585
+ self.client.resume_pipeline(self.name, wait=wait, timeout_s=timeout_s)
566
586
 
567
587
  def start_transaction(self) -> int:
568
588
  """
@@ -655,7 +675,7 @@ metrics"""
655
675
  """
656
676
 
657
677
  try:
658
- inner = client.get_pipeline(name)
678
+ inner = client.get_pipeline(name, PipelineFieldSelector.ALL)
659
679
  return Pipeline._from_inner(inner, client)
660
680
  except FelderaAPIError as err:
661
681
  if err.status_code == 404:
@@ -931,7 +951,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
931
951
  Return the program SQL code of the pipeline.
932
952
  """
933
953
 
934
- self.refresh()
954
+ self.refresh(PipelineFieldSelector.ALL)
935
955
  return self._inner.program_code
936
956
 
937
957
  def modify(
@@ -971,7 +991,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
971
991
  Return the storage status of the pipeline.
972
992
  """
973
993
 
974
- self.refresh()
994
+ self.refresh(PipelineFieldSelector.STATUS)
975
995
  return StorageStatus.from_str(self._inner.storage_status)
976
996
 
977
997
  def program_status(self) -> ProgramStatus:
@@ -983,7 +1003,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
983
1003
  Rust code to a binary.
984
1004
  """
985
1005
 
986
- self.refresh()
1006
+ self.refresh(PipelineFieldSelector.STATUS)
987
1007
  return ProgramStatus.from_value(self._inner.program_status)
988
1008
 
989
1009
  def program_status_since(self) -> datetime:
@@ -991,7 +1011,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
991
1011
  Return the timestamp when the current program status was set.
992
1012
  """
993
1013
 
994
- self.refresh()
1014
+ self.refresh(PipelineFieldSelector.STATUS)
995
1015
  return datetime.fromisoformat(self._inner.program_status_since)
996
1016
 
997
1017
  def udf_rust(self) -> str:
@@ -999,7 +1019,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
999
1019
  Return the Rust code for UDFs.
1000
1020
  """
1001
1021
 
1002
- self.refresh()
1022
+ self.refresh(PipelineFieldSelector.ALL)
1003
1023
  return self._inner.udf_rust
1004
1024
 
1005
1025
  def udf_toml(self) -> str:
@@ -1007,7 +1027,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1007
1027
  Return the Rust dependencies required by UDFs (in the TOML format).
1008
1028
  """
1009
1029
 
1010
- self.refresh()
1030
+ self.refresh(PipelineFieldSelector.ALL)
1011
1031
  return self._inner.udf_toml
1012
1032
 
1013
1033
  def program_config(self) -> Mapping[str, Any]:
@@ -1015,7 +1035,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1015
1035
  Return the program config of the pipeline.
1016
1036
  """
1017
1037
 
1018
- self.refresh()
1038
+ self.refresh(PipelineFieldSelector.ALL)
1019
1039
  return self._inner.program_config
1020
1040
 
1021
1041
  def runtime_config(self) -> RuntimeConfig:
@@ -1023,7 +1043,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1023
1043
  Return the runtime config of the pipeline.
1024
1044
  """
1025
1045
 
1026
- self.refresh()
1046
+ self.refresh(PipelineFieldSelector.ALL)
1027
1047
  return RuntimeConfig.from_dict(self._inner.runtime_config)
1028
1048
 
1029
1049
  def set_runtime_config(self, runtime_config: RuntimeConfig):
@@ -1048,7 +1068,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1048
1068
  Return the ID of the pipeline.
1049
1069
  """
1050
1070
 
1051
- self.refresh()
1071
+ self.refresh(PipelineFieldSelector.STATUS)
1052
1072
  return self._inner.id
1053
1073
 
1054
1074
  def description(self) -> str:
@@ -1056,7 +1076,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1056
1076
  Return the description of the pipeline.
1057
1077
  """
1058
1078
 
1059
- self.refresh()
1079
+ self.refresh(PipelineFieldSelector.STATUS)
1060
1080
  return self._inner.description
1061
1081
 
1062
1082
  def tables(self) -> List[SQLTable]:
@@ -1064,7 +1084,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1064
1084
  Return the tables of the pipeline.
1065
1085
  """
1066
1086
 
1067
- self.refresh()
1087
+ self.refresh(PipelineFieldSelector.ALL)
1068
1088
  return self._inner.tables
1069
1089
 
1070
1090
  def views(self) -> List[SQLView]:
@@ -1072,7 +1092,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1072
1092
  Return the views of the pipeline.
1073
1093
  """
1074
1094
 
1075
- self.refresh()
1095
+ self.refresh(PipelineFieldSelector.ALL)
1076
1096
  return self._inner.views
1077
1097
 
1078
1098
  def created_at(self) -> datetime:
@@ -1080,7 +1100,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1080
1100
  Return the creation time of the pipeline.
1081
1101
  """
1082
1102
 
1083
- self.refresh()
1103
+ self.refresh(PipelineFieldSelector.STATUS)
1084
1104
  return datetime.fromisoformat(self._inner.created_at)
1085
1105
 
1086
1106
  def version(self) -> int:
@@ -1088,7 +1108,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1088
1108
  Return the version of the pipeline.
1089
1109
  """
1090
1110
 
1091
- self.refresh()
1111
+ self.refresh(PipelineFieldSelector.STATUS)
1092
1112
  return self._inner.version
1093
1113
 
1094
1114
  def program_version(self) -> int:
@@ -1096,7 +1116,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1096
1116
  Return the program version of the pipeline.
1097
1117
  """
1098
1118
 
1099
- self.refresh()
1119
+ self.refresh(PipelineFieldSelector.STATUS)
1100
1120
  return self._inner.program_version
1101
1121
 
1102
1122
  def deployment_status_since(self) -> datetime:
@@ -1105,7 +1125,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1105
1125
  was set.
1106
1126
  """
1107
1127
 
1108
- self.refresh()
1128
+ self.refresh(PipelineFieldSelector.STATUS)
1109
1129
  return datetime.fromisoformat(self._inner.deployment_status_since)
1110
1130
 
1111
1131
  def deployment_config(self) -> Mapping[str, Any]:
@@ -1113,17 +1133,55 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1113
1133
  Return the deployment config of the pipeline.
1114
1134
  """
1115
1135
 
1116
- self.refresh()
1136
+ self.refresh(PipelineFieldSelector.ALL)
1117
1137
  return self._inner.deployment_config
1118
1138
 
1119
- def deployment_desired_status(self) -> PipelineStatus:
1139
+ def deployment_desired_status(self) -> DeploymentDesiredStatus:
1120
1140
  """
1121
1141
  Return the desired deployment status of the pipeline.
1122
1142
  This is the next state that the pipeline should transition to.
1123
1143
  """
1124
1144
 
1125
- self.refresh()
1126
- return PipelineStatus.from_str(self._inner.deployment_desired_status)
1145
+ self.refresh(PipelineFieldSelector.STATUS)
1146
+ return DeploymentDesiredStatus.from_str(self._inner.deployment_desired_status)
1147
+
1148
+ def deployment_resources_desired_status(self) -> DeploymentResourcesDesiredStatus:
1149
+ """
1150
+ Return the desired status of the the deployment resources.
1151
+ """
1152
+
1153
+ self.refresh(PipelineFieldSelector.STATUS)
1154
+ return DeploymentResourcesDesiredStatus.from_str(
1155
+ self._inner.deployment_resources_desired_status
1156
+ )
1157
+
1158
+ def deployment_resources_status(self) -> DeploymentResourcesStatus:
1159
+ """
1160
+ Return the status of the deployment resources.
1161
+ """
1162
+
1163
+ self.refresh(PipelineFieldSelector.STATUS)
1164
+ return DeploymentResourcesStatus.from_str(
1165
+ self._inner.deployment_resources_status
1166
+ )
1167
+
1168
+ def deployment_runtime_desired_status(self) -> DeploymentRuntimeDesiredStatus:
1169
+ """
1170
+ Return the deployment runtime desired status.
1171
+ """
1172
+
1173
+ self.refresh(PipelineFieldSelector.STATUS)
1174
+ return DeploymentRuntimeDesiredStatus.from_str(
1175
+ self._inner.deployment_runtime_desired_status
1176
+ )
1177
+
1178
+ def deployment_runtime_status(self) -> DeploymentRuntimeStatus:
1179
+ """
1180
+ Return the deployment runtime status.
1181
+ """
1182
+
1183
+ self.refresh(PipelineFieldSelector.STATUS)
1184
+ return DeploymentRuntimeStatus.from_str(self._inner.deployment_runtime_status)
1127
1185
 
1128
1186
  def deployment_error(self) -> Mapping[str, Any]:
1129
1187
  """
@@ -1131,7 +1189,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1131
1189
  Returns an empty string if there is no error.
1132
1190
  """
1133
1191
 
1134
- self.refresh()
1192
+ self.refresh(PipelineFieldSelector.STATUS)
1135
1193
  return self._inner.deployment_error
1136
1194
 
1137
1195
  def deployment_location(self) -> str:
@@ -1141,7 +1199,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1141
1199
  at runtime (a TCP port number or a URI).
1142
1200
  """
1143
1201
 
1144
- self.refresh()
1202
+ self.refresh(PipelineFieldSelector.STATUS)
1145
1203
  return self._inner.deployment_location
1146
1204
 
1147
1205
  def program_info(self) -> Mapping[str, Any]:
@@ -1152,7 +1210,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1152
1210
  and the SQL program schema.
1153
1211
  """
1154
1212
 
1155
- self.refresh()
1213
+ self.refresh(PipelineFieldSelector.ALL)
1156
1214
  return self._inner.program_info
1157
1215
 
1158
1216
  def program_error(self) -> Mapping[str, Any]:
@@ -1162,7 +1220,7 @@ pipeline '{self.name}' to sync checkpoint '{uuid}'"""
1162
1220
  `sql_compilation` and `rust_compilation` will be 0.
1163
1221
  """
1164
1222
 
1165
- self.refresh()
1223
+ self.refresh(PipelineFieldSelector.ALL)
1166
1224
  return self._inner.program_error
1167
1225
 
1168
1226
  def errors(self) -> List[Mapping[str, Any]]:
@@ -4,7 +4,7 @@ from typing import Optional
4
4
  from feldera.rest.feldera_client import FelderaClient
5
5
  from feldera.rest.pipeline import Pipeline as InnerPipeline
6
6
  from feldera.pipeline import Pipeline
7
- from feldera.enums import CompilationProfile
7
+ from feldera.enums import CompilationProfile, PipelineFieldSelector
8
8
  from feldera.runtime_config import RuntimeConfig
9
9
  from feldera.rest.errors import FelderaAPIError
10
10
 
@@ -60,7 +60,10 @@ class PipelineBuilder:
60
60
  raise ValueError("Name and SQL are required to create a pipeline")
61
61
 
62
62
  try:
63
- if self.client.get_pipeline(self.name) is not None:
63
+ if (
64
+ self.client.get_pipeline(self.name, PipelineFieldSelector.STATUS)
65
+ is not None
66
+ ):
64
67
  raise RuntimeError(f"Pipeline with name {self.name} already exists")
65
68
  except FelderaAPIError as err:
66
69
  if err.error_code != "UnknownPipelineName":
@@ -206,6 +206,11 @@ class HttpRequests:
206
206
  try:
207
207
  request.raise_for_status()
208
208
 
209
+ if request is None:
210
+ # This shouldn't ever be the case, but we've seen it happen
211
+ return FelderaCommunicationError(
212
+ "Failed to Communicate with Feldera Received None as Response",
213
+ )
209
214
  if stream:
210
215
  return request
211
216
  if request.headers.get("content-type") == "text/plain":
@@ -7,6 +7,7 @@ from decimal import Decimal
7
7
  from typing import Generator, Mapping
8
8
  from urllib.parse import quote
9
9
 
10
+ from feldera.enums import PipelineFieldSelector
10
11
  from feldera.rest.config import Config
11
12
  from feldera.rest.feldera_config import FelderaConfig
12
13
  from feldera.rest.errors import FelderaTimeoutError, FelderaAPIError
@@ -93,14 +94,19 @@ class FelderaClient:
93
94
 
94
95
  return FelderaClient(f"http://127.0.0.1:{port}")
95
96
 
96
- def get_pipeline(self, pipeline_name) -> Pipeline:
97
+ def get_pipeline(
98
+ self, pipeline_name: str, field_selector: PipelineFieldSelector
99
+ ) -> Pipeline:
97
100
  """
98
101
  Get a pipeline by name
99
102
 
100
103
  :param pipeline_name: The name of the pipeline
104
+ :param field_selector: Choose what pipeline information to refresh; see PipelineFieldSelector enum definition.
101
105
  """
102
106
 
103
- resp = self.http.get(f"/pipelines/{pipeline_name}")
107
+ resp = self.http.get(
108
+ f"/pipelines/{pipeline_name}?selector={field_selector.value}"
109
+ )
104
110
 
105
111
  return Pipeline.from_dict(resp)
106
112
 
@@ -130,12 +136,14 @@ class FelderaClient:
130
136
  wait = ["Pending", "CompilingSql", "SqlCompiled", "CompilingRust"]
131
137
 
132
138
  while True:
133
- p = self.get_pipeline(name)
139
+ p = self.get_pipeline(name, PipelineFieldSelector.STATUS)
134
140
  status = p.program_status
135
141
 
136
142
  if status == "Success":
137
- return p
143
+ return self.get_pipeline(name, PipelineFieldSelector.ALL)
138
144
  elif status not in wait:
145
+ p = self.get_pipeline(name, PipelineFieldSelector.ALL)
146
+
139
147
  # error handling for SQL compilation errors
140
148
  if status == "SqlError":
141
149
  sql_errors = p.program_error["sql_compilation"]["messages"]
@@ -163,6 +171,46 @@ class FelderaClient:
163
171
  logging.debug("still compiling %s, waiting for 100 more milliseconds", name)
164
172
  time.sleep(0.1)
165
173
 
174
+ def __wait_for_pipeline_state(
175
+ self,
176
+ pipeline_name: str,
177
+ state: str,
178
+ timeout_s: float = 300.0,
179
+ start: bool = True,
180
+ ):
181
+ start_time = time.monotonic()
182
+
183
+ while True:
184
+ if timeout_s is not None:
185
+ elapsed = time.monotonic() - start_time
186
+ if elapsed > timeout_s:
187
+ raise TimeoutError(
188
+ f"Timed out waiting for pipeline {pipeline_name} to"
189
+ f"transition to '{state}' state"
190
+ )
191
+
192
+ resp = self.get_pipeline(pipeline_name, PipelineFieldSelector.STATUS)
193
+ status = resp.deployment_status
194
+
195
+ if status.lower() == state.lower():
196
+ break
197
+ elif (
198
+ status == "Stopped"
199
+ and len(resp.deployment_error or {}) > 0
200
+ and resp.deployment_desired_status == "Stopped"
201
+ ):
202
+ err_msg = "Unable to START the pipeline:\n" if start else ""
203
+ raise RuntimeError(
204
+ f"""{err_msg}Unable to transition the pipeline to '{state}'.
205
+ Reason: The pipeline is in a STOPPED state due to the following error:
206
+ {resp.deployment_error.get("message", "")}"""
207
+ )
208
+
209
+ logging.debug(
210
+ "still starting %s, waiting for 100 more milliseconds", pipeline_name
211
+ )
212
+ time.sleep(0.1)
213
+
166
214
  def create_pipeline(self, pipeline: Pipeline) -> Pipeline:
167
215
  """
168
216
  Create a pipeline if it doesn't exist and wait for it to compile
@@ -284,9 +332,10 @@ class FelderaClient:
284
332
  """
285
333
 
286
334
  :param pipeline_name: The name of the pipeline to activate
287
- :param wait: Set True to wait for the pipeline to activate. True by default
288
- :param timeout_s: The amount of time in seconds to wait for the pipeline
289
- to activate. 300 seconds by default.
335
+ :param wait: Set True to wait for the pipeline to activate. True by
336
+ default
337
+ :param timeout_s: The amount of time in seconds to wait for the
338
+ pipeline to activate. 300 seconds by default.
290
339
  """
291
340
 
292
341
  if timeout_s is None:
@@ -299,46 +348,23 @@ class FelderaClient:
299
348
  if not wait:
300
349
  return
301
350
 
302
- start_time = time.monotonic()
351
+ self.__wait_for_pipeline_state(pipeline_name, "running", timeout_s)
303
352
 
304
- while True:
305
- if timeout_s is not None:
306
- elapsed = time.monotonic() - start_time
307
- if elapsed > timeout_s:
308
- raise TimeoutError(
309
- f"Timed out waiting for pipeline {pipeline_name} to activate"
310
- )
311
-
312
- resp = self.get_pipeline(pipeline_name)
313
- status = resp.deployment_status
314
-
315
- if status == "Running":
316
- break
317
- elif (
318
- status == "Stopped"
319
- and len(resp.deployment_error or {}) > 0
320
- and resp.deployment_desired_status == "Stopped"
321
- ):
322
- raise RuntimeError(
323
- f"""Unable to ACTIVATE the pipeline.
324
- Reason: The pipeline is in a STOPPED state due to the following error:
325
- {resp.deployment_error.get("message", "")}"""
326
- )
327
-
328
- logging.debug(
329
- "still starting %s, waiting for 100 more milliseconds", pipeline_name
330
- )
331
- time.sleep(0.1)
332
-
333
- def start_pipeline(
334
- self, pipeline_name: str, wait: bool = True, timeout_s: Optional[float] = 300
353
+ def _inner_start_pipeline(
354
+ self,
355
+ pipeline_name: str,
356
+ initial: str = "running",
357
+ wait: bool = True,
358
+ timeout_s: Optional[float] = 300,
335
359
  ):
336
360
  """
337
361
 
338
362
  :param pipeline_name: The name of the pipeline to start
363
+ :param initial: The initial state to start the pipeline in. "running"
364
+ by default.
339
365
  :param wait: Set True to wait for the pipeline to start. True by default
340
- :param timeout_s: The amount of time in seconds to wait for the pipeline
341
- to start. 300 seconds by default.
366
+ :param timeout_s: The amount of time in seconds to wait for the
367
+ pipeline to start. 300 seconds by default.
342
368
  """
343
369
 
344
370
  if timeout_s is None:
@@ -346,55 +372,64 @@ Reason: The pipeline is in a STOPPED state due to the following error:
346
372
 
347
373
  self.http.post(
348
374
  path=f"/pipelines/{pipeline_name}/start",
375
+ params={"initial": initial},
349
376
  )
350
377
 
351
378
  if not wait:
352
379
  return
353
380
 
354
- start_time = time.monotonic()
381
+ self.__wait_for_pipeline_state(pipeline_name, initial, timeout_s)
355
382
 
356
- while True:
357
- if timeout_s is not None:
358
- elapsed = time.monotonic() - start_time
359
- if elapsed > timeout_s:
360
- raise TimeoutError(
361
- f"Timed out waiting for pipeline {pipeline_name} to start"
362
- )
383
+ def start_pipeline(
384
+ self, pipeline_name: str, wait: bool = True, timeout_s: Optional[float] = 300
385
+ ):
386
+ """
363
387
 
364
- resp = self.get_pipeline(pipeline_name)
365
- status = resp.deployment_status
388
+ :param pipeline_name: The name of the pipeline to start
389
+ :param wait: Set True to wait for the pipeline to start.
390
+ True by default
391
+ :param timeout_s: The amount of time in seconds to wait for the
392
+ pipeline to start. 300 seconds by default.
393
+ """
366
394
 
367
- if status == "Running":
368
- break
369
- elif (
370
- status == "Stopped"
371
- and len(resp.deployment_error or {}) > 0
372
- and resp.deployment_desired_status == "Stopped"
373
- ):
374
- raise RuntimeError(
375
- f"""Unable to START the pipeline.
376
- Reason: The pipeline is in a STOPPED state due to the following error:
377
- {resp.deployment_error.get("message", "")}"""
378
- )
395
+ self._inner_start_pipeline(pipeline_name, "running", wait, timeout_s)
379
396
 
380
- logging.debug(
381
- "still starting %s, waiting for 100 more milliseconds", pipeline_name
382
- )
383
- time.sleep(0.1)
397
+ def start_pipeline_as_paused(
398
+ self, pipeline_name: str, wait: bool = True, timeout_s: Optional[float] = 300
399
+ ):
400
+ """
401
+ :param pipeline_name: The name of the pipeline to start as paused.
402
+ :param wait: Set True to wait for the pipeline to start as pause.
403
+ True by default
404
+ :param timeout_s: The amount of time in seconds to wait for the
405
+ pipeline to start. 300 seconds by default.
406
+ """
384
407
 
385
- def pause_pipeline(
408
+ self._inner_start_pipeline(pipeline_name, "paused", wait, timeout_s)
409
+
410
+ def start_pipeline_as_standby(
411
+ self, pipeline_name: str, wait: bool = True, timeout_s: Optional[float] = 300
412
+ ):
413
+ """
414
+ :param pipeline_name: The name of the pipeline to start as standby.
415
+ :param wait: Set True to wait for the pipeline to start as standby.
416
+ True by default
417
+ :param timeout_s: The amount of time in seconds to wait for the
418
+ pipeline to start. 300 seconds by default.
419
+ """
420
+
421
+ self._inner_start_pipeline(pipeline_name, "paused", wait, timeout_s)
422
+
423
+ def resume_pipeline(
386
424
  self,
387
425
  pipeline_name: str,
388
- error_message: str = None,
389
426
  wait: bool = True,
390
427
  timeout_s: Optional[float] = 300,
391
428
  ):
392
429
  """
393
- Stop a pipeline
430
+ Resume a pipeline
394
431
 
395
432
  :param pipeline_name: The name of the pipeline to stop
396
- :param error_message: The error message to show if the pipeline is in
397
- STOPPED state due to a failure.
398
433
  :param wait: Set True to wait for the pipeline to pause. True by default
399
434
  :param timeout_s: The amount of time in seconds to wait for the pipeline
400
435
  to pause. 300 seconds by default.
@@ -404,45 +439,42 @@ Reason: The pipeline is in a STOPPED state due to the following error:
404
439
  timeout_s = 300
405
440
 
406
441
  self.http.post(
407
- path=f"/pipelines/{pipeline_name}/pause",
442
+ path=f"/pipelines/{pipeline_name}/resume",
408
443
  )
409
444
 
410
445
  if not wait:
411
446
  return
412
447
 
413
- if error_message is None:
414
- error_message = "Unable to PAUSE the pipeline.\n"
448
+ self.__wait_for_pipeline_state(pipeline_name, "running", timeout_s)
415
449
 
416
- start_time = time.monotonic()
450
+ def pause_pipeline(
451
+ self,
452
+ pipeline_name: str,
453
+ wait: bool = True,
454
+ timeout_s: Optional[float] = 300,
455
+ ):
456
+ """
457
+ Pause a pipeline
417
458
 
418
- while True:
419
- if timeout_s is not None:
420
- elapsed = time.monotonic() - start_time
421
- if elapsed > timeout_s:
422
- raise TimeoutError(
423
- f"Timed out waiting for pipeline {pipeline_name} to pause"
424
- )
459
+ :param pipeline_name: The name of the pipeline to stop
460
+ :param error_message: The error message to show if the pipeline is in
461
+ STOPPED state due to a failure.
462
+ :param wait: Set True to wait for the pipeline to pause. True by default
463
+ :param timeout_s: The amount of time in seconds to wait for the pipeline
464
+ to pause. 300 seconds by default.
465
+ """
425
466
 
426
- resp = self.get_pipeline(pipeline_name)
427
- status = resp.deployment_status
467
+ if timeout_s is None:
468
+ timeout_s = 300
428
469
 
429
- if status == "Paused":
430
- break
431
- elif (
432
- status == "Stopped"
433
- and len(resp.deployment_error or {}) > 0
434
- and resp.deployment_desired_status == "Stopped"
435
- ):
436
- raise RuntimeError(
437
- error_message
438
- + f"""Reason: The pipeline is in a STOPPED state due to the following error:
439
- {resp.deployment_error.get("message", "")}"""
440
- )
470
+ self.http.post(
471
+ path=f"/pipelines/{pipeline_name}/pause",
472
+ )
441
473
 
442
- logging.debug(
443
- "still pausing %s, waiting for 100 more milliseconds", pipeline_name
444
- )
445
- time.sleep(0.1)
474
+ if not wait:
475
+ return
476
+
477
+ self.__wait_for_pipeline_state(pipeline_name, "paused", timeout_s)
446
478
 
447
479
  def stop_pipeline(
448
480
  self,
@@ -478,7 +510,9 @@ Reason: The pipeline is in a STOPPED state due to the following error:
478
510
  start = time.monotonic()
479
511
 
480
512
  while time.monotonic() - start < timeout_s:
481
- status = self.get_pipeline(pipeline_name).deployment_status
513
+ status = self.get_pipeline(
514
+ pipeline_name, PipelineFieldSelector.STATUS
515
+ ).deployment_status
482
516
 
483
517
  if status == "Stopped":
484
518
  return
@@ -512,7 +546,9 @@ Reason: The pipeline is in a STOPPED state due to the following error:
512
546
  start = time.monotonic()
513
547
 
514
548
  while time.monotonic() - start < timeout_s:
515
- status = self.get_pipeline(pipeline_name).storage_status
549
+ status = self.get_pipeline(
550
+ pipeline_name, PipelineFieldSelector.STATUS
551
+ ).storage_status
516
552
 
517
553
  if status == "Cleared":
518
554
  return
@@ -47,6 +47,9 @@ class Pipeline:
47
47
  self.program_version: Optional[int] = None
48
48
  self.deployment_config: Optional[dict] = None
49
49
  self.deployment_desired_status: Optional[str] = None
50
+ self.deployment_desired_status_since: Optional[str] = None
51
+ self.deployment_id: Optional[str] = None
52
+ self.deployment_initial: Optional[str] = None
50
53
  self.deployment_error: Optional[dict] = None
51
54
  self.deployment_location: Optional[str] = None
52
55
  self.program_info: Optional[dict] = (
@@ -57,6 +60,16 @@ class Pipeline:
57
60
  self.program_error: Optional[dict] = None
58
61
  self.storage_status: Optional[str] = None
59
62
 
63
+ self.deployment_resources_desired_status: Optional[str] = None
64
+ self.deployment_resources_desired_status_since: Optional[str] = None
65
+ self.deployment_resources_status: Optional[str] = None
66
+ self.deployment_resources_status_since: Optional[str] = None
67
+
68
+ self.deployment_runtime_desired_status: Optional[str] = None
69
+ self.deployment_runtime_desired_status_since: Optional[str] = None
70
+ self.deployment_runtime_status: Optional[str] = None
71
+ self.deployment_runtime_status_since: Optional[str] = None
72
+
60
73
  @classmethod
61
74
  def from_dict(cls, d: Mapping[str, Any]):
62
75
  pipeline = cls("", "", "", "", {}, {})
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: feldera
3
- Version: 0.147.0
3
+ Version: 0.149.0
4
4
  Summary: The feldera python client
5
5
  Author-email: Feldera Team <dev@feldera.com>
6
6
  License: MIT
@@ -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.147.0"
9
+ version = "0.149.0"
10
10
  license = { text = "MIT" }
11
11
  requires-python = ">=3.10"
12
12
  authors = [
File without changes
File without changes
File without changes
File without changes
File without changes