Dp 2534 aws s3 - #91
Conversation
awilfox
left a comment
There was a problem hiding this comment.
Found a few things to fix so far, but a nice architecture.
anarchivist
left a comment
There was a problem hiding this comment.
a few comments, mostly around using Airflow's native operators to do this.
| file_extension = "{{ params.file_extension }}" | ||
|
|
||
| validate_destination = validate_destination(dest_directory) | ||
| bucket_filenames = get_bucket_file_names(bucket, file_prefix, file_extension) |
There was a problem hiding this comment.
i wonder if you can do this using the S3ListOperator and simplify the custom method to filter.
There was a problem hiding this comment.
I think using S3hook in this case might be better since we also need to filter out the extension. With S3hook I can do that directly on the list in memory. It looks like the S3ListOperator would need to get the whole list first and put it in the DB first and then I'd need to retrieve it to strip out based on the extension.
| def get_bucket_file_names(bucket: str, file_prefix: str | None = None, file_extension: str | None = None) -> list: | ||
| """Get a list of files for a given bucket. Can be filterd by prefix and extension""" | ||
| prefix = file_prefix.strip() if file_prefix else None | ||
| extension = file_extension.strip() if file_extension else None | ||
|
|
||
| file_names = list_bucket_files(bucket_name=bucket, file_prefix=prefix, file_extension=extension) | ||
|
|
||
| if file_names: | ||
| for name in file_names: | ||
| logger.info("Found file: %s", name) | ||
| else: | ||
| logger.info("The bucket is empty!") | ||
|
|
||
| return file_names |
|
|
||
| validate_destination = validate_destination(dest_directory) | ||
| bucket_filenames = get_bucket_file_names(bucket, file_prefix, file_extension) | ||
| retrieved_files = retrieve_files_from_bucket(bucket, bucket_filenames, dest_directory) |
There was a problem hiding this comment.
similarly for this you might be able to use the FileTransferOperator here.
There was a problem hiding this comment.
Actually I was thinking about this part some more. I've just been testing with tiny example files and not taking into account these files could be pretty darn big (wav files etc.). I'm going to redo how this is done so it won't crash.
There was a problem hiding this comment.
I looked at this for a while. I'm thinking dynamic task mapping using S3hook is the way to go since we can have hundreds of files and they could be really large. I changed it up to do it this way.
Each file will be downloaded as it's own task and if there's an issue with a particular task the issue could be resolved and the task could be resumed. It also looks like performance wise using S3hook is preferable since it's optimized for large S3 downloads and is more configurable.
We're not doing further processing of the downloaded file but if we wanted to we could run into a FileNotFound issue using FileTransferOperator since a subsequent task could start on a different node and possibly not see the file.
@awilfox Since Maria is out for a little bit I'm wondering if you could take a peak at the way downloading is now handled? I 'think' this should work well. I tested it locally with several large files and it handled them nicely
anarchivist
left a comment
There was a problem hiding this comment.
r+wc. i think we're close; have some questions about how we pass in connection ids and adding a description to make the purpose of this Dag clear.
| dag_id="s3_download", | ||
| schedule=None, | ||
| catchup=False, | ||
| params={ |
There was a problem hiding this comment.
do we need to parameterize the connection ID to use here? i'm also a little curious about whether we need to identify a specific connection for this since it's credentials specifically for city arts.
There was a problem hiding this comment.
We don't need to, it could be hardcoded to cityartsmedia. I did it that way for the future assuming we'd be using it for other buckets and connections. That's why I chose a general fetch_from_s3.py for the name as well. Should I remove it and hardcode it to cityartsmedia?
I actually do see there is another bucket for them that we have access to cityarts_sf and that uses the same connection as cityartsmedia
There was a problem hiding this comment.
no, i don't think you should hardcode it. if the intent is to use it with other jobs to copy from S3, then we'll need to support other connection IDs. my question is about how those connections get passed in to the Dag.
There was a problem hiding this comment.
I'm not sure the best way of handling multiple connection id's. I was under the assumption the scope of this was for City Arts but could be enhanced for other connections when and if the need arises (via a separate ticket). Should I look into how we'd have this handle multiple connections before merging this? As it is now it will default to the cityartsmedia bucket but the connection should also work for the cityarts-sf which has the same connection string.
There was a problem hiding this comment.
yes, i think we should look into that before we merge it.
There was a problem hiding this comment.
I don't think Airflow handles conditional parameters so if a user chose a particular connection from a dropdown (S3 connections) list I don't think I could dynamically populate another (bucket) parameter with a list of buckets available for that connection.
I can think of a few ways of handling this.
- I don't imagine connections would have that many buckets. It could be a single dropdown with both connection and bucket listed in the same dropdown. e.g.
[
"conn: cityarts | bucket: cityartsmedia",
"conn: cityarts | bucket: cityarts-sf",
"conn: somevendor | bucket: somebucket",
etc. etc.
]
I could parse out the connection and bucket pretty easily.
-
It could have two drop downs. One populated with the available connections and another for the user to type in the bucket. They'd need to know the bucket for a given connection though.
-
Looks like a multi-step dag where Dag 1 triggers Dag2 with supplied values and pauses at the first step would maybe work but that seems kind of messy and overkill for this. I'd probably have to redo a lot of this if we went that route
Of the three I like option 1 the best. Open to other suggestions
There was a problem hiding this comment.
confirming we discussed an option to provide a string param (without dropdown) for connection_id and bucket name.
There was a problem hiding this comment.
Just pushed but forget to change the tests to accommodate the new param. Will fix that now
Adding option to retrieve files based on prefix and extension minor formatting fixes renamed s3_utils.py using dynamic task mapping for retrieving files, some refactoring for S3 Dag and associatled s3 util removing fail fast from Dag so downstream failures aren't skipped. Added a bit of cleanup for spaces in bucket name Added test for s3 utility and Dag, changed case name for fetch_from_s3.py Added description for S3 Dag Making s3 connection configurable, Adding test seed data for multiple buckets for localstack testing Changed tests to account for s3 connection_id being a parameter only reading in neccessary environment variables for localstack instead of whole env file
dc132da to
a3befb7
Compare
No description provided.