mangleframes 0.3.0__tar.gz → 0.3.1__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 (72) hide show
  1. {mangleframes-0.3.0 → mangleframes-0.3.1}/PKG-INFO +1 -1
  2. {mangleframes-0.3.0 → mangleframes-0.3.1}/pyproject.toml +1 -1
  3. {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/__init__.py +1 -1
  4. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/Cargo.toml +1 -1
  5. mangleframes-0.3.1/spark-connect/src/lib.rs +67 -0
  6. mangleframes-0.3.1/viewer/frontend/src/lib/api.ts +174 -0
  7. mangleframes-0.3.0/spark-connect/src/lib.rs +0 -15
  8. {mangleframes-0.3.0 → mangleframes-0.3.1}/Cargo.lock +0 -0
  9. {mangleframes-0.3.0 → mangleframes-0.3.1}/Cargo.toml +0 -0
  10. {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/alerts.py +0 -0
  11. {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/launcher.py +0 -0
  12. {mangleframes-0.3.0 → mangleframes-0.3.1}/python/mangleframes/session.py +0 -0
  13. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/build.rs +0 -0
  14. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/base.proto +0 -0
  15. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/catalog.proto +0 -0
  16. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/commands.proto +0 -0
  17. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/common.proto +0 -0
  18. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/expressions.proto +0 -0
  19. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/ml.proto +0 -0
  20. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/ml_common.proto +0 -0
  21. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/relations.proto +0 -0
  22. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/proto/spark/connect/types.proto +0 -0
  23. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/src/client.rs +0 -0
  24. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/src/error.rs +0 -0
  25. {mangleframes-0.3.0 → mangleframes-0.3.1}/spark-connect/src/proto/spark.connect.rs +0 -0
  26. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/Cargo.toml +0 -0
  27. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/index.html +0 -0
  28. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/package-lock.json +0 -0
  29. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/package.json +0 -0
  30. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/postcss.config.js +0 -0
  31. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/App.tsx +0 -0
  32. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/JoinAnalyzer.tsx +0 -0
  33. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/Reconciliation.tsx +0 -0
  34. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/analysis/SQLEditor.tsx +0 -0
  35. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/ColumnDropdown.tsx +0 -0
  36. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/ColumnStats.tsx +0 -0
  37. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/DataGrid.tsx +0 -0
  38. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/data/SchemaView.tsx +0 -0
  39. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/ContextPanel.tsx +0 -0
  40. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/Layout.tsx +0 -0
  41. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/MainContent.tsx +0 -0
  42. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/Sidebar.tsx +0 -0
  43. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/StatusBar.tsx +0 -0
  44. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/TabBar.tsx +0 -0
  45. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/layout/TopBar.tsx +0 -0
  46. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/quality/AlertBuilder.tsx +0 -0
  47. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/components/quality/QualityDashboard.tsx +0 -0
  48. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/index.css +0 -0
  49. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/main.tsx +0 -0
  50. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/stores/dataStore.ts +0 -0
  51. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/src/stores/uiStore.ts +0 -0
  52. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/tailwind.config.js +0 -0
  53. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/tsconfig.json +0 -0
  54. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/tsconfig.node.json +0 -0
  55. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/frontend/vite.config.ts +0 -0
  56. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/alert_handlers.rs +0 -0
  57. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/arrow_reader.rs +0 -0
  58. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/dashboard.rs +0 -0
  59. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/export.rs +0 -0
  60. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/handlers.rs +0 -0
  61. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/history_analysis.rs +0 -0
  62. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/history_handlers.rs +0 -0
  63. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/join_handlers.rs +0 -0
  64. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/main.rs +0 -0
  65. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/perf.rs +0 -0
  66. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/reconcile_handlers.rs +0 -0
  67. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/spark_client.rs +0 -0
  68. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/sql_builder.rs +0 -0
  69. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/stats.rs +0 -0
  70. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/test_helpers.rs +0 -0
  71. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/web_server.rs +0 -0
  72. {mangleframes-0.3.0 → mangleframes-0.3.1}/viewer/src/websocket.rs +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: mangleframes
3
- Version: 0.3.0
3
+ Version: 0.3.1
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.0"
7
+ version = "0.3.1"
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.0"
35
+ __version__ = "0.3.1"
36
36
 
37
37
  # Import alert classes for convenience (optional dependency)
38
38
  try:
@@ -6,7 +6,7 @@ description = "Rust client for Apache Spark Connect gRPC protocol"
6
6
 
7
7
  [dependencies]
8
8
  tonic = { version = "0.12", features = ["tls", "tls-roots", "gzip"] }
9
- prost = "0.13"
9
+ prost = { version = "0.13", features = ["no-recursion-limit"] }
10
10
  prost-types = "0.13"
11
11
  tokio = { version = "1", features = ["rt-multi-thread", "macros"] }
12
12
  arrow = { version = "54", features = ["ipc"] }
@@ -0,0 +1,67 @@
1
+ //! Spark Connect Rust client for Apache Spark gRPC protocol.
2
+ //!
3
+ //! This crate provides a Rust client for connecting to Spark clusters via the
4
+ //! Spark Connect gRPC protocol, with specific support for Databricks.
5
+
6
+ pub mod client;
7
+ pub mod error;
8
+
9
+ #[allow(clippy::all)]
10
+ mod proto {
11
+ include!("proto/spark.connect.rs");
12
+ }
13
+
14
+ pub use client::{ColumnSchema, SparkConnectClient};
15
+ pub use error::SparkConnectError;
16
+
17
+ #[cfg(test)]
18
+ mod tests {
19
+ use super::proto::{relation, Limit, Relation};
20
+ use prost::Message;
21
+
22
+ /// Build a deeply nested Relation with `depth` levels of Limit operations.
23
+ fn build_nested_relation(depth: usize) -> Relation {
24
+ let mut current = Relation {
25
+ common: None,
26
+ rel_type: None,
27
+ };
28
+
29
+ for i in 0..depth {
30
+ current = Relation {
31
+ common: None,
32
+ rel_type: Some(relation::RelType::Limit(Box::new(Limit {
33
+ input: Some(Box::new(current)),
34
+ limit: i as i32,
35
+ }))),
36
+ };
37
+ }
38
+
39
+ current
40
+ }
41
+
42
+ /// Verifies the no-recursion-limit feature works by encoding/decoding
43
+ /// a deeply nested protobuf message (150+ levels exceeds old 100 limit).
44
+ #[test]
45
+ fn test_deeply_nested_relation_decodes() {
46
+ let depth = 150;
47
+ let relation = build_nested_relation(depth);
48
+
49
+ let encoded = relation.encode_to_vec();
50
+ let decoded = Relation::decode(encoded.as_slice())
51
+ .expect("should decode deeply nested relation without recursion limit");
52
+
53
+ // Verify the structure by walking down to confirm depth
54
+ let mut count = 0;
55
+ let mut current = &decoded;
56
+ while let Some(relation::RelType::Limit(limit)) = &current.rel_type {
57
+ count += 1;
58
+ if let Some(input) = &limit.input {
59
+ current = input;
60
+ } else {
61
+ break;
62
+ }
63
+ }
64
+
65
+ assert_eq!(count, depth, "decoded relation should have correct nesting depth");
66
+ }
67
+ }
@@ -0,0 +1,174 @@
1
+ const BASE_URL = ''
2
+
3
+ export interface Column {
4
+ name: string
5
+ type: string
6
+ nullable: boolean
7
+ }
8
+
9
+ export interface Timing {
10
+ spark_ms: number
11
+ ipc_ms: number
12
+ json_ms: number
13
+ total_ms: number
14
+ cached: boolean
15
+ }
16
+
17
+ export interface DataResponse {
18
+ rows: Record<string, unknown>[]
19
+ total: number
20
+ offset: number
21
+ timing: Timing
22
+ }
23
+
24
+ export interface SchemaResponse {
25
+ columns: Column[]
26
+ }
27
+
28
+ export interface FramesResponse {
29
+ frames: string[]
30
+ }
31
+
32
+ export interface StatsResponse {
33
+ row_count: number
34
+ columns: ColumnStats[]
35
+ }
36
+
37
+ export interface ColumnStats {
38
+ name: string
39
+ type: string
40
+ null_count: number
41
+ distinct_count?: number
42
+ min?: string | number
43
+ max?: string | number
44
+ avg?: number
45
+ }
46
+
47
+ export interface QueryResponse {
48
+ rows: Record<string, unknown>[]
49
+ total: number
50
+ timing: { execution_ms: number }
51
+ }
52
+
53
+ export interface CsvUploadResponse {
54
+ frame_name: string
55
+ columns: { name: string; data_type: string; nullable: boolean }[]
56
+ row_count: number
57
+ }
58
+
59
+ export interface CsvFramesResponse {
60
+ frames: string[]
61
+ }
62
+
63
+ export interface ReconcileStatistics {
64
+ match_rate: number
65
+ matched_groups: number
66
+ source_groups: number
67
+ source_only_groups: number
68
+ target_groups: number
69
+ target_only_groups: number
70
+ }
71
+
72
+ export interface ColumnTotal {
73
+ column: string
74
+ aggregation: string
75
+ source_total: number
76
+ target_total: number
77
+ difference: number
78
+ percent_diff: number
79
+ }
80
+
81
+ export interface ReconcileResponse {
82
+ statistics: ReconcileStatistics
83
+ column_totals: ColumnTotal[]
84
+ source_only: { total: number; rows: Record<string, unknown>[] }
85
+ target_only: { total: number; rows: Record<string, unknown>[] }
86
+ matched_rows: { rows: Record<string, unknown>[] }
87
+ source_frame: string
88
+ target_frame: string
89
+ }
90
+
91
+ export interface ReconcileRequest {
92
+ source_frame: string
93
+ target_frame: string
94
+ source_type?: string
95
+ source_group_by: string[]
96
+ target_group_by: string[]
97
+ source_join_keys: string[]
98
+ target_join_keys: string[]
99
+ join_type: 'inner' | 'left' | 'right' | 'full'
100
+ aggregations: { column: string; aggregations: string[] }[]
101
+ sample_limit?: number
102
+ }
103
+
104
+ async function request<T>(path: string, options?: RequestInit): Promise<T> {
105
+ const response = await fetch(`${BASE_URL}${path}`, {
106
+ headers: { 'Content-Type': 'application/json' },
107
+ ...options,
108
+ })
109
+
110
+ if (!response.ok) {
111
+ const error = await response.json().catch(() => ({ error: response.statusText }))
112
+ throw new Error(error.error || `Request failed: ${response.status}`)
113
+ }
114
+
115
+ return response.json()
116
+ }
117
+
118
+ export const api = {
119
+ listFrames: () => request<FramesResponse>('/api/frames'),
120
+
121
+ getSchema: (name: string) =>
122
+ request<SchemaResponse>(`/api/frames/${encodeURIComponent(name)}/schema`),
123
+
124
+ getData: (name: string, offset = 0, limit = 100) =>
125
+ request<DataResponse>(
126
+ `/api/frames/${encodeURIComponent(name)}/data?offset=${offset}&limit=${limit}`
127
+ ),
128
+
129
+ getStats: (name: string) =>
130
+ request<StatsResponse>(`/api/frames/${encodeURIComponent(name)}/stats`),
131
+
132
+ executeQuery: (sql: string) =>
133
+ request<QueryResponse>('/api/query', {
134
+ method: 'POST',
135
+ body: JSON.stringify({ sql }),
136
+ }),
137
+
138
+ exportFrame: (name: string, format: 'csv' | 'json' | 'parquet') =>
139
+ fetch(`${BASE_URL}/api/frames/${encodeURIComponent(name)}/export`, {
140
+ method: 'POST',
141
+ headers: { 'Content-Type': 'application/json' },
142
+ body: JSON.stringify({ format }),
143
+ }),
144
+
145
+ uploadCsv: async (file: File, frameName: string): Promise<CsvUploadResponse> => {
146
+ const formData = new FormData()
147
+ formData.append('file', file)
148
+ formData.append('frame_name', frameName)
149
+ const response = await fetch(`${BASE_URL}/api/reconcile/upload`, {
150
+ method: 'POST',
151
+ body: formData,
152
+ })
153
+ if (!response.ok) {
154
+ const error = await response.json().catch(() => ({ error: response.statusText }))
155
+ throw new Error(error.error || `Upload failed: ${response.status}`)
156
+ }
157
+ return response.json()
158
+ },
159
+
160
+ listCsvFrames: () => request<CsvFramesResponse>('/api/reconcile/frames'),
161
+
162
+ reconcile: (req: ReconcileRequest) =>
163
+ request<ReconcileResponse>('/api/reconcile', {
164
+ method: 'POST',
165
+ body: JSON.stringify(req),
166
+ }),
167
+
168
+ exportReconciliation: (req: ReconcileRequest) =>
169
+ fetch(`${BASE_URL}/api/reconcile/export`, {
170
+ method: 'POST',
171
+ headers: { 'Content-Type': 'application/json' },
172
+ body: JSON.stringify(req),
173
+ }),
174
+ }
@@ -1,15 +0,0 @@
1
- //! Spark Connect Rust client for Apache Spark gRPC protocol.
2
- //!
3
- //! This crate provides a Rust client for connecting to Spark clusters via the
4
- //! Spark Connect gRPC protocol, with specific support for Databricks.
5
-
6
- pub mod client;
7
- pub mod error;
8
-
9
- #[allow(clippy::all)]
10
- mod proto {
11
- include!("proto/spark.connect.rs");
12
- }
13
-
14
- pub use client::{ColumnSchema, SparkConnectClient};
15
- pub use error::SparkConnectError;
File without changes
File without changes