Forum Discussion
Fabric Notebooks. Get latest file from bronze Data Lake into Dataframe
I have a bronze lake house and files
S1File_19012018.csv
S2File_13052019.csv
S3File_01112019.csv
And A new file drops in
S4File_12022020.csv
What I want to do it pull through this latest file only (Obviously I cant hard code it because i dont know what the last file will be)
And then run through the transformations and the append the data into the Parquet file in the Silver lakehouse that already contains the files we have already captured
I feel like I need a parameter to dynamically get that last file. But Im not quite sure how would be the best way to do it. Has anyone got tips or could point me towards proper documentation for this?
I did a test which seems to work:
In my Bronze lakehouse, I created a folder which receives a new csv file every 10 minutes.
I also created a table in my Bronze lakehouse, called processed_files_log, where I keep a log of the csv files which have already been processed and loaded into a table in my Silver Lakehouse.
I have pasted the Notebook code below. The notebook runs every 30 minutes to process new files and load the content to the Silver table. The notebook also enters the processed files' filenames into the log table.
ChatGPT helped me with creating this code, and I don't claim to fully understand all the details of the code.
Step 1 is to list the files in the folder
# abfss path to the folder where the csv files are located files_path = '<insert abfss path to the folder which keeps the csv files>' # the mssparkutils.fs.ls method lists all the files (and/or subfolders) inside the folder. I only have csv files in the folder. files = mssparkutils.fs.ls(files_path) # Convert FileInfo objects to list of tuples (this creates a list of the file names in the folder.) file_data = [(file.name,) for file in files] # This creates a dataframe consisting of the file names in the folder df_files_in_folder = spark.createDataFrame(file_data, ["name"]) # Show the DataFrame (this step can be removed) display(df_files_in_folder)Step 2 is to list all the files which have already been processed
# This creates a dataframe with the file names of all the files which have already been processed df_already_processed = spark.sql("SELECT * FROM Lakehouse_bronze.processed_files_log") # Show the DataFrame (this step can be removed) display(df_already_processed)Step 3 is to find out which files in the folder have not been processed yet
# Selecting only the filename column from df_already_processed df_already_processed_filenames = df_already_processed.select("filename") # Selecting only the name column from df_files_in_folder df_files_in_folder_names = df_files_in_folder.select("name") # Performing subtract operation to find non-matching rows # (so only the file names of the files which have not been processed already are kept) df_to_process = df_files_in_folder_names.subtract(df_already_processed_filenames) # Showing the resulting DataFrame of files which have not yet been processed (this step can be removed) display(df_to_process)Step 4 is the code to process the files which have not already been processed, load the content into the silver table, and create new entries into the log table
from pyspark.sql.functions import current_timestamp, lit, to_timestamp from pyspark.sql.types import StructType, StructField, StringType, TimestampType from datetime import datetime # Loop through the dataframe which consist of the filenames for row in df_to_process.rdd.collect(): # Collecting to driver (local) as a list of Rows # Extract filename from the current row filename = row["name"] # Read the current csv file into a dataframe df = spark.read.format("csv").option("header", "true").option("inferSchema", "true").load(files_path + filename).select("OrderDateTime", "OrderID", "ProductID", "Quantity") # Add filename column to the dataframe df = df.withColumn("source_filename", lit(filename)) tidsstempel = current_timestamp() # Add current timestamp column ("source_processedTime") to the dataframe df = df.withColumn("source_processedTime", tidsstempel) # Append the dataframe to the table in Silver lakehouse df.write.format("delta").mode("append").save('<Insert the abfss path to your silver layer Lakehouse table here>') # Create a single-row DataFrame with the filename (this will be appended to the log table) single_row = [[filename]] single_row_schema = StructType([ StructField("filename", StringType(), False) ]) df_log = spark.createDataFrame(single_row, single_row_schema) # Add the processedTime to the single-row DataFrame which will be inserted into the log table df_log = df_log.withColumn("processedTime", tidsstempel) # Insert the filename and the processedTime into the log table df_log.write.mode("append").saveAsTable("processed_files_log")The raw csv files in Bronze lakehouse look like this:
The destination table in the Silver lakehouse looks like this:
This table contains the data from the csv files, in addition to the source_filename and the time when the source csv file was processed by the Notebook and loaded to the table (source_processedTime).
(If someone notice: The raw csv files just contain dummy data, this is the reason why the OrderID's are repeating between 1-5 for all the csv files.).
If someone have suggestions and/or corrections to this approach and code, please share 😀
Done it. I created an Empty dataframe and then appended into it in the loop you suggested
dfapp = dfapp.union(dfnf)
18 Replies
- frithjof_v
Community Champion
Maybe something like this:
https://youtu.be/Uu5_Qeo0sfg?si=ytP2X5NjoXz8iqNt
Maybe you can use some functions for getting information about files in Fabric Lakehouse directory from here (e.g. list files):
https://learn.microsoft.com/en-us/fabric/data-engineering/microsoft-spark-utilities
In general I think you could use some directory function to list all the files in the directory, put this information in a dataframe, then sort the dataframe according to some attribute like file created timestamp / last modified timestamp / file name (or substring from file name), depending on which attribute you consider to be most relevant for determining what is the latest file in your context.
And then select the first or last row from the dataframe, depending on the sort order you chose.
And then select the value of the path attribute (path column) from the selected row, to get the path of the latest file.
However: could there sometimes be situations where you will need to load not only the latest, but the two latest (or more) files? If more than one file exists which has not already been loaded into silver?
Then you would need some mechanism to keep track of which files have already been loaded to silver.
I must admit I don't have so much experience with this case. Hopefully someone more experienced can tell how this is usually done 😃
- DebbieE
Community Champion
The youtube is on databricks and I think i would be more comfortable following one on Fabric
And having a look at the spark utilities. I dont think Im going to get very far with this unfortunately, but I agree that thats definitely what Im wanting to do.
And yes, Eventually I want to be able to trigger either delta or full load
- AnonymousNot applicable
Hi DebbieE ,
Thanks for the reply from frithjof_v .
You can use Python's built-in os module to list all the files in a directory and then sort them by creation time. The file with the latest creation time is your latest file.
Once you have the latest file, you can use pandas to read the file.
After reading the file, you can perform transformations on the data. This depends on the specific transformation you want to apply.
Finally, you can use the pyarrow library to append the transformed data to the existing Parquet file.
Below I have provided a sample Python code for these operations:
import os import pandas as pd import pyarrow.parquet as pq # Get the list of all files in your directory files = os.listdir('/path/to/your/directory') # Get the latest file latest_file = max(files, key=os.path.getctime) # Read the latest file df = pd.read_csv('/path/to/your/directory/' + latest_file) # Perform your transformations here # df = transform(df) # Append to existing Parquet file table = pq.Table.from_pandas(df) pq.write_to_dataset(table, root_path='/path/to/your/parquet/file', partition_cols=['date'])Please replace '/path/to/your/directory' and '/path/to/your/parquet/file' with the actual path to your directory.
If you have any further questions, please feel free to contact me.
Best Regards,
Yang
Community Support TeamIf there is any post helps, then please consider Accept it as the solution to help the other members find it more quickly.
If I misunderstand your needs or you still have problems on it, please feel free to let us know. Thanks a lot!- frithjof_v
Community Champion
DebbieE do you want to write to a parquet file or a delta table? In general, my impression is that the delta table is the preferred format in Fabric. So I'm curious if there is a specific reason to write to a parquet file only.
- DebbieE
Community Champion
Parquet because its just the transformation data in the silver layer. I have Delta parquet in the gold layer as dims and facts
- DebbieE
Community Champion
You can use Python's built-in os module to list all the files in a directory and then sort them by creation time. The file with the latest creation time is your latest file.
I wouldnt know how to go about this. i would need more detail
I wouldnt mind trying to attempt this without using Pandas. I havent needed to do this yet
- frithjof_v
Community Champion
I was able to get the latest modified file from a Lakehouse directory by using this code (ChatGPT and Google helped me).
I used Python's os module, as it seems mssparkutils' ls-method doesn't return the created-time or modified-time of the files in a directory.
I guess this code will only work if the Notebook's mounted Lakehouse is the Lakehouse where the raw files (bronze files) reside. At least, I only know how to use os module with the Notebook's mounted Lakehouse.
import os from datetime import datetime import pandas as pd # Directory's relative path for Spark (you can copy this from the Fabric user interface) relative_path_for_spark = "Files/Daily/" # Directory's File API path (you could also copy this from the Fabric user interface) file_api_path = '/lakehouse/default/' + relative_path_for_spark # Get the list of all files in your directory files = os.listdir(file_api_path) # Initialize lists to store file names and last modified times file_names = [] last_modified_times = [] # Iterate over the list of files for file in files: # Get the full path of the file file_path = os.path.join(file_api_path, file) # Ensure it is a file if os.path.isfile(file_path): # Get the last modified time modified_timestamp = os.path.getmtime(file_path) # Convert to a human-readable format last_modified_date = datetime.fromtimestamp(modified_timestamp) # Append to lists file_names.append(file) last_modified_times.append(last_modified_date) # Create a Pandas DataFrame df = pd.DataFrame({ 'File Name': file_names, 'Last Modified Time': last_modified_times }) # Print the Pandas DataFrame (you can remove this step) print(df) # Sort the Pandas DataFrame by 'Last Modified Time' in descending order df_sorted = df.sort_values(by='Last Modified Time', ascending=False) # Choose the file name of the first row in the sorted Pandas dataframe (as this is the file which was modified latest) latest_file_name = df_sorted['File Name'].values[0] # Create a Spark dataframe with the content of the CSV file df_file_content = spark.read.format("csv").option("header", "true").option("inferSchema", "true").load(relative_path_for_spark + latest_file_name) display(df_file_content) # Then write the dataframe contents to somewhere...There is also some interesting information about mounting Lakehouses dynamically in a Notebook in this blog post: How To Mount A Lakehouse and Identify Mounted Lakehouses in Fabric
Here is another thread about looping files in a directory:
Solved: Re: Fabric Notebook how do I loop files in a folde... - Microsoft Fabric Community
A user suggested a logic like below. This does not require the use of the os module. However, I think it reads all the content of all files in the folder, which I guess may cause some overhead, depending on your situation:
# path abfss_path = "<abfss path to the folder which contains the files>" # read each CSV file in the folder df_files = spark.read.option("header", "true").csv(abfss_path).select("*", "_metadata.file_name","_metadata.file_modification_time") display(df_files)