mangleframes 0.3.6__tar.gz → 0.3.7__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 (87) hide show
  1. {mangleframes-0.3.6 → mangleframes-0.3.7}/Cargo.lock +1 -0
  2. {mangleframes-0.3.6 → mangleframes-0.3.7}/PKG-INFO +1 -1
  3. {mangleframes-0.3.6 → mangleframes-0.3.7}/pyproject.toml +1 -1
  4. {mangleframes-0.3.6 → mangleframes-0.3.7}/python/mangleframes/__init__.py +1 -1
  5. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/Cargo.toml +1 -0
  6. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/spark_client.rs +125 -13
  7. {mangleframes-0.3.6 → mangleframes-0.3.7}/Cargo.toml +0 -0
  8. {mangleframes-0.3.6 → mangleframes-0.3.7}/python/mangleframes/alerts.py +0 -0
  9. {mangleframes-0.3.6 → mangleframes-0.3.7}/python/mangleframes/launcher.py +0 -0
  10. {mangleframes-0.3.6 → mangleframes-0.3.7}/python/mangleframes/session.py +0 -0
  11. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/Cargo.toml +0 -0
  12. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/build.rs +0 -0
  13. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/base.proto +0 -0
  14. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/catalog.proto +0 -0
  15. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/commands.proto +0 -0
  16. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/common.proto +0 -0
  17. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/expressions.proto +0 -0
  18. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/ml.proto +0 -0
  19. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/ml_common.proto +0 -0
  20. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/relations.proto +0 -0
  21. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/proto/spark/connect/types.proto +0 -0
  22. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/src/client.rs +0 -0
  23. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/src/error.rs +0 -0
  24. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/src/lib.rs +0 -0
  25. {mangleframes-0.3.6 → mangleframes-0.3.7}/spark-connect/src/proto/spark.connect.rs +0 -0
  26. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/index.html +0 -0
  27. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/package-lock.json +0 -0
  28. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/package.json +0 -0
  29. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/postcss.config.js +0 -0
  30. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/App.tsx +0 -0
  31. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/analysis/JoinAnalyzer.tsx +0 -0
  32. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/analysis/Reconciliation.tsx +0 -0
  33. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/analysis/SQLEditor.tsx +0 -0
  34. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/analysis/__tests__/JoinAnalyzer.test.tsx +0 -0
  35. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/data/ColumnDropdown.tsx +0 -0
  36. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/data/ColumnStats.tsx +0 -0
  37. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/data/DataGrid.tsx +0 -0
  38. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/data/SchemaView.tsx +0 -0
  39. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/ERDBuilder.tsx +0 -0
  40. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/ERDCanvas.tsx +0 -0
  41. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/ERDConfigModal.tsx +0 -0
  42. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/ERDTableList.tsx +0 -0
  43. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/ERDToolbar.tsx +0 -0
  44. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/ERDValidationPanel.tsx +0 -0
  45. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/TableNode.tsx +0 -0
  46. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/__tests__/ERDDragDrop.test.tsx +0 -0
  47. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/erd/index.ts +0 -0
  48. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/layout/ContextPanel.tsx +0 -0
  49. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/layout/Layout.tsx +0 -0
  50. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/layout/MainContent.tsx +0 -0
  51. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/layout/Sidebar.tsx +0 -0
  52. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/layout/StatusBar.tsx +0 -0
  53. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/layout/TabBar.tsx +0 -0
  54. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/layout/TopBar.tsx +0 -0
  55. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/quality/AlertBuilder.tsx +0 -0
  56. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/components/quality/QualityDashboard.tsx +0 -0
  57. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/hooks/useFrameLoadingState.ts +0 -0
  58. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/hooks/useFramePrefetch.ts +0 -0
  59. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/index.css +0 -0
  60. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/lib/api.ts +0 -0
  61. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/lib/erdValidation.ts +0 -0
  62. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/main.tsx +0 -0
  63. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/stores/dataStore.ts +0 -0
  64. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/stores/erdStore.ts +0 -0
  65. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/stores/uiStore.ts +0 -0
  66. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/src/test/setup.ts +0 -0
  67. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/tailwind.config.js +0 -0
  68. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/tsconfig.json +0 -0
  69. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/tsconfig.node.json +0 -0
  70. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/frontend/vite.config.ts +0 -0
  71. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/alert_handlers.rs +0 -0
  72. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/arrow_reader.rs +0 -0
  73. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/benchmark.rs +0 -0
  74. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/dashboard.rs +0 -0
  75. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/export.rs +0 -0
  76. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/handlers.rs +0 -0
  77. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/history_analysis.rs +0 -0
  78. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/history_handlers.rs +0 -0
  79. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/join_handlers.rs +0 -0
  80. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/main.rs +0 -0
  81. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/perf.rs +0 -0
  82. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/reconcile_handlers.rs +0 -0
  83. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/sql_builder.rs +0 -0
  84. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/stats.rs +0 -0
  85. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/test_helpers.rs +0 -0
  86. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/web_server.rs +0 -0
  87. {mangleframes-0.3.6 → mangleframes-0.3.7}/viewer/src/websocket.rs +0 -0
@@ -1486,6 +1486,7 @@ dependencies = [
1486
1486
  "spark-connect",
1487
1487
  "thiserror",
1488
1488
  "tokio",
1489
+ "tonic",
1489
1490
  "tower-http",
1490
1491
  "tracing",
1491
1492
  "tracing-subscriber",
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mangleframes
3
- Version: 0.3.6
3
+ Version: 0.3.7
4
4
  Classifier: Programming Language :: Python :: 3
5
5
  Classifier: Programming Language :: Rust
6
6
  Classifier: License :: OSI Approved :: MIT License
@@ -4,7 +4,7 @@ build-backend = "maturin"
4
4
 
5
5
  [project]
6
6
  name = "mangleframes"
7
- version = "0.3.6"
7
+ version = "0.3.7"
8
8
  description = "PySpark DataFrame viewer with modern web UI"
9
9
  requires-python = ">=3.12"
10
10
  license = { text = "MIT" }
@@ -32,7 +32,7 @@ from .session import SparkSession, get_proxy_port, get_spark_session
32
32
  if TYPE_CHECKING:
33
33
  from pyspark.sql import DataFrame
34
34
 
35
- __version__ = "0.3.6"
35
+ __version__ = "0.3.7"
36
36
 
37
37
  # Import alert classes for convenience (optional dependency)
38
38
  try:
@@ -29,6 +29,7 @@ chrono = "0.4"
29
29
  base64 = "0.22"
30
30
  reqwest = { version = "0.12", default-features = false, features = ["json", "rustls-tls"] }
31
31
  spark-connect = { path = "../spark-connect" }
32
+ tonic = { version = "0.12", default-features = false }
32
33
 
33
34
  [profile.release]
34
35
  lto = true
@@ -15,10 +15,23 @@ pub struct SqlResponse {
15
15
  pub execution_ms: u64,
16
16
  }
17
17
 
18
+ /// Stored credentials/URL used to reconnect after session expiry.
19
+ enum ConnectionConfig {
20
+ Direct {
21
+ host: String,
22
+ token: String,
23
+ cluster_id: Option<String>,
24
+ },
25
+ Proxy {
26
+ url: String,
27
+ },
28
+ }
29
+
18
30
  /// Wrapper around SparkConnectClient for Databricks integration.
19
31
  pub struct DatabricksClient {
20
32
  client: Arc<RwLock<Option<SparkConnectClient>>>,
21
33
  connected: Arc<RwLock<bool>>,
34
+ config: Arc<RwLock<Option<ConnectionConfig>>>,
22
35
  }
23
36
 
24
37
  impl DatabricksClient {
@@ -27,6 +40,7 @@ impl DatabricksClient {
27
40
  Self {
28
41
  client: Arc::new(RwLock::new(None)),
29
42
  connected: Arc::new(RwLock::new(false)),
43
+ config: Arc::new(RwLock::new(None)),
30
44
  }
31
45
  }
32
46
 
@@ -46,6 +60,11 @@ impl DatabricksClient {
46
60
 
47
61
  *self.client.write().await = Some(client);
48
62
  *self.connected.write().await = true;
63
+ *self.config.write().await = Some(ConnectionConfig::Direct {
64
+ host: host.to_string(),
65
+ token: token.to_string(),
66
+ cluster_id: cluster_id.map(str::to_string),
67
+ });
49
68
 
50
69
  info!("Databricks {} connection established", mode);
51
70
  Ok(())
@@ -64,6 +83,9 @@ impl DatabricksClient {
64
83
 
65
84
  *self.client.write().await = Some(client);
66
85
  *self.connected.write().await = true;
86
+ *self.config.write().await = Some(ConnectionConfig::Proxy {
87
+ url: proxy_url.to_string(),
88
+ });
67
89
 
68
90
  info!("Connected to proxy at {}", proxy_url);
69
91
  Ok(())
@@ -79,6 +101,50 @@ impl DatabricksClient {
79
101
  &self,
80
102
  query: &str,
81
103
  limit: usize,
104
+ ) -> Result<SqlResponse, SparkConnectError> {
105
+ match self.execute_sql_inner(query, limit).await {
106
+ Err(ref e) if is_session_timeout(e) => {
107
+ self.reconnect().await?;
108
+ self.execute_sql_inner(query, limit).await
109
+ }
110
+ other => other,
111
+ }
112
+ }
113
+
114
+ /// Execute SQL with reattachable execution for large result sets (>10K rows).
115
+ /// Uses ReattachExecute RPC to ensure complete results.
116
+ pub async fn execute_sql_reattachable(
117
+ &self,
118
+ query: &str,
119
+ limit: usize,
120
+ ) -> Result<SqlResponse, SparkConnectError> {
121
+ match self.execute_sql_reattachable_inner(query, limit).await {
122
+ Err(ref e) if is_session_timeout(e) => {
123
+ self.reconnect().await?;
124
+ self.execute_sql_reattachable_inner(query, limit).await
125
+ }
126
+ other => other,
127
+ }
128
+ }
129
+
130
+ /// Register Arrow batches as a temporary view in Spark.
131
+ pub async fn create_temp_view(
132
+ &self,
133
+ view_name: &str,
134
+ batches: &[RecordBatch],
135
+ ) -> Result<(), SparkConnectError> {
136
+ let guard = self.client.read().await;
137
+ let client = guard
138
+ .as_ref()
139
+ .ok_or_else(|| SparkConnectError::Config("Not connected".to_string()))?;
140
+
141
+ client.create_temp_view(view_name, batches).await
142
+ }
143
+
144
+ async fn execute_sql_inner(
145
+ &self,
146
+ query: &str,
147
+ limit: usize,
82
148
  ) -> Result<SqlResponse, SparkConnectError> {
83
149
  let guard = self.client.read().await;
84
150
  let client = guard
@@ -103,9 +169,7 @@ impl DatabricksClient {
103
169
  })
104
170
  }
105
171
 
106
- /// Execute SQL with reattachable execution for large result sets (>10K rows).
107
- /// Uses ReattachExecute RPC to ensure complete results.
108
- pub async fn execute_sql_reattachable(
172
+ async fn execute_sql_reattachable_inner(
109
173
  &self,
110
174
  query: &str,
111
175
  limit: usize,
@@ -133,18 +197,26 @@ impl DatabricksClient {
133
197
  })
134
198
  }
135
199
 
136
- /// Register Arrow batches as a temporary view in Spark.
137
- pub async fn create_temp_view(
138
- &self,
139
- view_name: &str,
140
- batches: &[RecordBatch],
141
- ) -> Result<(), SparkConnectError> {
142
- let guard = self.client.read().await;
143
- let client = guard
200
+ async fn reconnect(&self) -> Result<(), SparkConnectError> {
201
+ info!("Spark Connect session expired due to inactivity, reconnecting...");
202
+ let config_guard = self.config.read().await;
203
+ let config = config_guard
144
204
  .as_ref()
145
- .ok_or_else(|| SparkConnectError::Config("Not connected".to_string()))?;
205
+ .ok_or_else(|| SparkConnectError::Config("No connection config stored".to_string()))?;
146
206
 
147
- client.create_temp_view(view_name, batches).await
207
+ let new_client = match config {
208
+ ConnectionConfig::Direct { host, token, cluster_id } => {
209
+ SparkConnectClient::connect(host, token, cluster_id.as_deref()).await?
210
+ }
211
+ ConnectionConfig::Proxy { url } => {
212
+ SparkConnectClient::connect_via_proxy(url).await?
213
+ }
214
+ };
215
+
216
+ drop(config_guard);
217
+ *self.client.write().await = Some(new_client);
218
+ info!("Spark Connect session reinitialized successfully");
219
+ Ok(())
148
220
  }
149
221
  }
150
222
 
@@ -154,6 +226,15 @@ impl Default for DatabricksClient {
154
226
  }
155
227
  }
156
228
 
229
+ fn is_session_timeout(err: &SparkConnectError) -> bool {
230
+ if let SparkConnectError::Grpc(status) = err {
231
+ status.code() == tonic::Code::FailedPrecondition
232
+ && status.message().contains("INACTIVITY_TIMEOUT")
233
+ } else {
234
+ false
235
+ }
236
+ }
237
+
157
238
  /// Try to create and connect a DatabricksClient from environment variables.
158
239
  /// Uses serverless if DATABRICKS_CLUSTER_ID is not set.
159
240
  pub async fn try_connect_from_env() -> Option<Arc<DatabricksClient>> {
@@ -171,3 +252,34 @@ pub async fn try_connect_from_env() -> Option<Arc<DatabricksClient>> {
171
252
  }
172
253
  }
173
254
  }
255
+
256
+ #[cfg(test)]
257
+ mod tests {
258
+ use super::*;
259
+
260
+ #[test]
261
+ fn is_session_timeout_detects_inactivity_timeout() {
262
+ let status = tonic::Status::failed_precondition(
263
+ "Session expired: reason=INACTIVITY_TIMEOUT after 10 minutes",
264
+ );
265
+ assert!(is_session_timeout(&SparkConnectError::Grpc(status)));
266
+ }
267
+
268
+ #[test]
269
+ fn is_session_timeout_ignores_other_precondition() {
270
+ let status = tonic::Status::failed_precondition("Some other precondition failure");
271
+ assert!(!is_session_timeout(&SparkConnectError::Grpc(status)));
272
+ }
273
+
274
+ #[test]
275
+ fn is_session_timeout_ignores_non_grpc_errors() {
276
+ let err = SparkConnectError::Config("Not connected".to_string());
277
+ assert!(!is_session_timeout(&err));
278
+ }
279
+
280
+ #[test]
281
+ fn is_session_timeout_ignores_wrong_grpc_code() {
282
+ let status = tonic::Status::unavailable("INACTIVITY_TIMEOUT");
283
+ assert!(!is_session_timeout(&SparkConnectError::Grpc(status)));
284
+ }
285
+ }
File without changes