SilverStreamingApplication 1.4__tar.gz → 1.6__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 (22) hide show
  1. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/PKG-INFO +1 -1
  2. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/Operations.py +86 -1
  3. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/SilverMerge.py +10 -5
  4. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/WorkflowBuilder.py +15 -3
  5. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/setup.py +1 -1
  6. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/silverstreamingapplication.egg-info/PKG-INFO +1 -1
  7. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/Maintenance.py +0 -0
  8. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/PartitionAnalyzer.py +0 -0
  9. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/PerfAnalyzer.py +0 -0
  10. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/__init__.py +0 -0
  11. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/logging.py +0 -0
  12. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/sparksession.py +0 -0
  13. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/utils.py +0 -0
  14. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/main/utilsDatabricksAPI.py +0 -0
  15. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/tests/OperationsTest.py +0 -0
  16. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/tests/__init__.py +0 -0
  17. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/tests/main_test.py +0 -0
  18. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/com/phida/tests/utils_test.py +0 -0
  19. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/setup.cfg +0 -0
  20. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/silverstreamingapplication.egg-info/SOURCES.txt +0 -0
  21. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/silverstreamingapplication.egg-info/dependency_links.txt +0 -0
  22. {silverstreamingapplication-1.4 → silverstreamingapplication-1.6}/silverstreamingapplication.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: silverstreamingapplication
3
- Version: 1.4
3
+ Version: 1.6
4
4
  Summary: initial version
5
5
  Home-page:
6
6
  Author:
@@ -1,6 +1,7 @@
1
1
  from pyspark.sql.utils import AnalysisException
2
2
  from com.phida.main.sparksession import spark
3
3
  from pyspark.sql.functions import col
4
+ from pyspark.sql.types import _parse_datatype_string, StructType, StructField
4
5
 
5
6
 
6
7
  def tableExists(dbName, tblName):
@@ -92,6 +93,89 @@ def hiveDDL(df):
92
93
 
93
94
  return tblDDL
94
95
 
96
+ def describe_table_to_schema(desc_rows):
97
+ """
98
+ Convert table description rows into a Spark StructType schema.
99
+ This function parses the output from a DESCRIBE TABLE command and organizes
100
+ the columns into a StructType schema, separating regular data fields from
101
+ partition fields.
102
+ Args:
103
+ desc_rows: An iterable of row objects containing table metadata.
104
+ Each row should have 'col_name' and 'data_type' attributes.
105
+ Returns:
106
+ StructType: A Spark StructType object representing the table schema,
107
+ with data fields listed first, followed by partition fields.
108
+ Example:
109
+ >>> desc_rows = [
110
+ ... Row(col_name='id', data_type='bigint'),
111
+ ... Row(col_name='name', data_type='string'),
112
+ ... Row(col_name='# Partition Information', data_type=''),
113
+ ... Row(col_name='date', data_type='date')
114
+ ... ]
115
+ >>> schema = describe_table_to_schema(desc_rows)
116
+ """
117
+ data_fields = []
118
+ partition_fields = []
119
+
120
+ in_partition_section = False
121
+
122
+ for r in desc_rows:
123
+ col = r.col_name
124
+ dtype = r.data_type
125
+
126
+ # Enter partition section
127
+ if col.startswith("# Partition Information"):
128
+ in_partition_section = True
129
+ continue
130
+
131
+ # Skip header rows
132
+ if col.startswith("#"):
133
+ continue
134
+
135
+ # Skip empty rows (defensive)
136
+ if not col or not dtype:
137
+ continue
138
+
139
+ field = StructField(
140
+ col,
141
+ _parse_datatype(dtype),
142
+ True
143
+ )
144
+
145
+ if in_partition_section:
146
+ partition_fields.append(field)
147
+ else:
148
+ data_fields.append(field)
149
+
150
+ return StructType(data_fields + partition_fields)
151
+
152
+ def _parse_datatype(dt):
153
+ """
154
+ Parse a data type string and return the corresponding Spark data type.
155
+
156
+ This function serves as a wrapper that leverages Spark's internal parsing
157
+ mechanism to correctly handle complex data types including nested structures,
158
+ arrays, maps, and other composite types.
159
+
160
+ Args:
161
+ dt (str): A string representation of a Spark data type
162
+ (e.g., 'IntegerType', 'StructType', 'ArrayType(StringType)')
163
+
164
+ Returns:
165
+ pyspark.sql.types.DataType: The parsed Spark data type object corresponding
166
+ to the input string.
167
+
168
+ Raises:
169
+ ParseException: If the data type string is invalid or cannot be parsed.
170
+
171
+ Examples:
172
+ >>> _parse_datatype('IntegerType')
173
+ IntegerType
174
+ >>> _parse_datatype('ArrayType(StringType)')
175
+ ArrayType(StringType, containsNull=true)
176
+ """
177
+ # Let Spark parse complex types correctly
178
+ return _parse_datatype_string(dt)
95
179
 
96
180
  def hasColumn(df, columnName):
97
181
  """
@@ -299,9 +383,10 @@ def schemaDiff(df1, df2):
299
383
  1. Make sure that the first dataframe has got more columns than the second
300
384
  """
301
385
  df1_columns = df1.columns
386
+ df2_columns_lower = [col.lower() for col in df2.columns]
302
387
 
303
388
  for columnName in df1.columns:
304
- if columnName in df2.columns:
389
+ if columnName.lower() in df2_columns_lower:
305
390
  df1_columns.remove(columnName)
306
391
 
307
392
  df = df1.select(df1_columns)
@@ -1,5 +1,6 @@
1
+ from datetime import date, timedelta
1
2
  from com.phida.main.sparksession import spark, logger
2
- from com.phida.main.Operations import tableExists, addDerivedColumns, createDeltaTable, alterDeltaTable, \
3
+ from com.phida.main.Operations import describe_table_to_schema, tableExists, addDerivedColumns, createDeltaTable, alterDeltaTable, \
3
4
  dropColumns, getDerivedColumnsList, schemaDiff, getKeyCols, buildJoinCondition, buildColumnsDict, hiveDDL, schemaDataTypeDiff
4
5
  from com.phida.main.utils import pathExists, convertStrToList
5
6
  from pyspark.sql.functions import row_number, col, broadcast,expr, max as spark_max
@@ -43,7 +44,7 @@ class SilverMerge:
43
44
  """
44
45
  def __init__(self, srcDatabaseName, srcTableName, tgtDatabaseName, tgtTableName,
45
46
  tgtCheckpoint, tgtTablePath, tgtPartitionColumns, derivedColExpr,
46
- triggerOnce, dropColumnStr, pruneColumn, cluster_table_path, enable_delete_filter, use_auto_loader):
47
+ triggerOnce, dropColumnStr, pruneColumn, cluster_table_path, enable_delete_filter, use_auto_loader, modified_after=None, backfill_interval=None):
47
48
  """
48
49
  desc:
49
50
  Initialize the required class variables
@@ -80,7 +81,9 @@ class SilverMerge:
80
81
  self.pruneColumn = pruneColumn
81
82
  self.cluster_table_path = cluster_table_path
82
83
  self.enable_delete_filter = enable_delete_filter.strip().upper() if enable_delete_filter else 'N'
83
- self.use_auto_loader = use_auto_loader.strip().upper() if use_auto_loader and use_auto_loader.strip().upper() == 'Y' else 'N'
84
+ self.use_auto_loader = use_auto_loader.strip().upper() if use_auto_loader and use_auto_loader.strip().upper() in ('Y') else 'N'
85
+ self.modified_after = modified_after.strip() if modified_after else (date.today() - timedelta(days=15)).strftime('%Y-%m-%d')
86
+ self.backfill_interval = backfill_interval if backfill_interval else "1 month"
84
87
 
85
88
  logger.info(f"phida_log: Check if source table {self.srcDatabaseName}.{self.srcTableName} exists")
86
89
 
@@ -93,8 +96,10 @@ class SilverMerge:
93
96
  if self.use_auto_loader == 'N':
94
97
  self.srcDF = spark.readStream.table(self.srcDatabaseName + "." + self.srcTableName)
95
98
  elif self.use_auto_loader == 'Y':
99
+ desc = spark.sql(f"DESCRIBE TABLE {self.srcDatabaseName}.{self.srcTableName}").collect()
100
+ schema = describe_table_to_schema(desc)
96
101
  src_table_path = spark.sql(f"DESCRIBE DETAIL {self.srcDatabaseName}.{self.srcTableName}").collect()[0]['location']
97
- self.srcDF = spark.readStream.format("cloudFiles").option("cloudFiles.format", "parquet").option("cloudFiles.schemaLocation", src_table_path + "/_cloudfiles/_schemas").load(src_table_path)
102
+ self.srcDF = spark.readStream.format("cloudFiles").option("cloudFiles.format", "parquet").option("cloudFiles.schemaLocation", src_table_path + "/_cloudfiles/_schemas").option("modifiedAfter", f"{self.modified_after} 00:00:00.000000 UTC+0").option("cloudFiles.backfillInterval", self.backfill_interval).schema(schema).load(src_table_path).drop("_rescued_data")
98
103
 
99
104
  self.keyCols = getKeyCols(self.srcDatabaseName, self.srcTableName)
100
105
 
@@ -240,7 +245,7 @@ class SilverMerge:
240
245
 
241
246
  filtered_df = (joined_df.filter(
242
247
  (col("delete_hvr_integ_key").isNull()) |
243
- (col("hvr_integ_key") > col("delete_hvr_integ_key"))
248
+ (col("hvr_integ_key") >= col("delete_hvr_integ_key"))
244
249
  )
245
250
  .drop("delete_hvr_integ_key"))
246
251
 
@@ -72,7 +72,7 @@ class WorkflowBuilder:
72
72
  @staticmethod
73
73
  def createTask(taskKey, jobClusterKey, notebookPath, srcDatabaseName, srcTableName, tgtDatabaseName, tgtTableName,
74
74
  tgtCheckpoint, tgtTablePath, tgtPartitionColumns, derivedColExpr, triggerOnce, dropColumnStr,
75
- pruneColumn, cluster_table_path, enable_delete_filter, enable_autoloader, library):
75
+ pruneColumn, cluster_table_path, enable_delete_filter, enable_autoloader, modified_after, backfill_interval, library):
76
76
  """
77
77
  desc:
78
78
  A method for creating the dict/json for a single task
@@ -96,6 +96,8 @@ class WorkflowBuilder:
96
96
  cluster_table_path: String - The path of the table in the cluster
97
97
  enable_delete_filter: String - Flag to enable/disable filtering of deleted records (default: 'N')
98
98
  enable_autoloader: String - Flag to enable/disable autoloader (default: 'N')
99
+ modified_after: String - The modified after parameter for autoloader to read only the files modified after the specified timestamp (optional, format: "2026-01-01", "2025-06-01" etc.)
100
+ backfill_interval: String - The backfill interval for autoloader to read the historical data by looking back a certain interval from the current timestamp (optional, format: "1 month", "10 months", "15 days", "12 hours" etc.)
99
101
  pythonWhlPath: String - Path of the python wheel file
100
102
  library: String - Library to be installed for the job (with version number)
101
103
 
@@ -137,7 +139,9 @@ class WorkflowBuilder:
137
139
  "drop_column_list": dropColumnStr,
138
140
  "cluster_table_path": cluster_table_path,
139
141
  "enable_delete_filter": str(enable_delete_filter),
140
- "enable_autoloader": enable_autoloader
142
+ "enable_autoloader": enable_autoloader,
143
+ "modified_after": modified_after,
144
+ "backfill_interval": backfill_interval
141
145
  }
142
146
  },
143
147
  "timeout_seconds": timeout_seconds,
@@ -348,11 +352,19 @@ class WorkflowBuilder:
348
352
  enable_autoloader = task['enable_autoloader']
349
353
  except:
350
354
  enable_autoloader = ""
355
+ try:
356
+ modified_after = task['modified_after']
357
+ except:
358
+ modified_after = ""
359
+ try:
360
+ backfill_interval = task['backfill_interval']
361
+ except:
362
+ backfill_interval = ""
351
363
 
352
364
  taskTemp = self.createTask(taskKey, jobClusterKey, notebookPath, srcDatabaseName, srcTableName,
353
365
  tgtDatabaseName, tgtTableName, tgtCheckpoint, tgtTablePath,
354
366
  tgtPartitionColumns, derivedColExpr, triggerOnce, dropColumnStr,
355
- pruneColumn, cluster_table_path, enable_delete_filter, enable_autoloader, self.library)
367
+ pruneColumn, cluster_table_path, enable_delete_filter, enable_autoloader, modified_after, backfill_interval, self.library)
356
368
 
357
369
  tasks.append(taskTemp)
358
370
  if jobId is None:
@@ -2,7 +2,7 @@ from setuptools import setup
2
2
 
3
3
  setup(
4
4
  name='silverstreamingapplication',
5
- version='1.4',
5
+ version='1.6',
6
6
  packages=['com.phida.main', 'com.phida.tests'],
7
7
  url='',
8
8
  license='',
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.1
2
2
  Name: silverstreamingapplication
3
- Version: 1.4
3
+ Version: 1.6
4
4
  Summary: initial version
5
5
  Home-page:
6
6
  Author: