1515
1616from mokelumne .util .storage import run_dir
1717from mokelumne .util .file_transfer import (
18+ DESTINATION_VOLUMES ,
19+ SOURCE_VOLUMES ,
1820 build_file_manifest ,
21+ build_volume_path ,
1922 clean_destination_path ,
2023 copy_files_from_manifest ,
2124 load_json ,
2528
2629logger = logging .getLogger (__name__ )
2730
28- SOURCE_VOLUMES = [
29- "/srv/da" ,
30- "/srv/pa" ,
31- "/srv/lpsdata4" ,
32- ]
33-
34- DESTINATION_VOLUMES = [
35- "/srv/pa" ,
36- "/srv/da" ,
37- ]
38-
39-
40- def build_volume_path (
41- volume : str ,
42- subdirectory : str ,
43- label : str ,
44- ) -> Path :
45- """Build a path from a volume and relative subdirectory."""
46-
47- if subdirectory == "" :
48- raise AirflowFailException (
49- f"{ label } subdirectory is required"
50- )
51-
52- subdirectory_path = Path (subdirectory )
53-
54- if subdirectory_path .is_absolute ():
55- raise AirflowFailException (
56- f"{ label } subdirectory must be relative: { subdirectory } "
57- )
58-
59- return Path (volume ) / subdirectory_path
60-
61-
6231@dag (
6332 description = "Transfers files from one location to another" ,
6433 schedule = None ,
@@ -88,7 +57,7 @@ def build_volume_path(
8857 default = "" ,
8958 type = "string" ,
9059 title = "Destination subdirectory" ,
91- description = "Relative path within the destination volume. Do not include leading '/'." ,
60+ description = "Relative path within the destination volume. Do not include leading '/' or the final '/incoming' to the path ." ,
9261 ),
9362 "exclude_regex" : Param (
9463 default = "" ,
@@ -105,18 +74,39 @@ def copy_files():
10574 """Copy files from a source directory to an empty destination directory."""
10675
10776 @task
108- def validate_source () -> str :
109- """Checks that the source is a valid directory ."""
77+ def build_copy_paths () -> dict [ str , str ] :
78+ """Build the resolved source and destination paths ."""
11079
11180 ctx = get_current_context ()
112- source_volume = ctx ["params" ]["source_volume" ]
113- source_subdirectory = ctx ["params" ]["source_subdirectory" ]
11481
115- source_path = build_volume_path (
116- source_volume ,
117- source_subdirectory ,
118- "Source" ,
119- )
82+ try :
83+ source_path = build_volume_path (
84+ ctx ["params" ]["source_volume" ],
85+ ctx ["params" ]["source_subdirectory" ],
86+ "Source" ,
87+ )
88+
89+ destination_path = build_volume_path (
90+ ctx ["params" ]["destination_volume" ],
91+ ctx ["params" ]["destination_subdirectory" ],
92+ "Destination" ,
93+ )
94+
95+ destination_path = clean_destination_path (destination_path )
96+ except ValueError as ex :
97+ raise AirflowFailException (str (ex )) from ex
98+
99+ return {
100+ "source" : str (source_path ),
101+ "destination" : str (destination_path )
102+ }
103+
104+
105+ @task
106+ def validate_source (copy_paths : dict [str , str ]) -> str :
107+ """Checks that the source is a valid directory."""
108+
109+ source_path = Path (copy_paths ["source" ])
120110
121111 if not source_path .exists ():
122112 raise AirflowFailException (f"Source path does not exist: { source_path } " )
@@ -127,19 +117,10 @@ def validate_source() -> str:
127117 return str (source_path )
128118
129119 @task
130- def prepare_destination () -> str :
120+ def prepare_destination (copy_paths : dict [ str , str ] ) -> str :
131121 """Prepare the destination directory."""
132122
133- ctx = get_current_context ()
134- destination_volume = ctx ["params" ]["destination_volume" ]
135- destination_subdirectory = ctx ["params" ]["destination_subdirectory" ]
136-
137- destination_path = build_volume_path (
138- destination_volume ,
139- destination_subdirectory ,
140- "Destination" ,
141- )
142- destination_path = clean_destination_path (destination_path )
123+ destination_path = Path (copy_paths ["destination" ])
143124
144125 if not destination_path .exists ():
145126 logger .info ("Creating destination directory: %s" , destination_path )
@@ -153,6 +134,7 @@ def prepare_destination() -> str:
153134
154135 return str (destination_path )
155136
137+
156138 @task
157139 def build_manifest (source : str ) -> str :
158140 """Build a manifest of all files under the source directory."""
@@ -218,19 +200,24 @@ def verify_manifest(destination: str, manifest_path: str) -> None:
218200 logger .info ("Verification report written to: %s" , verification_report_path )
219201 logger .info ("Verified %s copied file(s)" , len (verification_report ))
220202
221- """The user needs to confirm the file copy paths before proceeding"""
203+
204+ # Need to run this before defining confirm_copy since we need the resolved paths:
205+ copy_paths = build_copy_paths ()
206+
207+ # The user needs to confirm the file copy paths before proceeding
222208 confirm_copy = ApprovalOperator (
223209 task_id = "confirm_copy" ,
224210 subject = "Review the copy operation and approve it to continue." ,
225211 body = (
226212 "Approve file copy from "
227- "**{{ params.source_volume }}/{{ params.source_subdirectory } }** "
213+ f "**{ copy_paths [ 'source' ] } ** "
228214 "to "
229- "**{{ params.destination_volume }}/{{ params.destination_subdirectory } }**"
215+ f "**{ copy_paths [ 'destination' ] } **"
230216 ),
231217 )
232- validated_source = validate_source ()
233- prepared_destination = prepare_destination ()
218+
219+ validated_source = validate_source (copy_paths )
220+ prepared_destination = prepare_destination (copy_paths )
234221 manifest = build_manifest (validated_source )
235222 copied_manifest_files = copy_manifest_files (
236223 validated_source ,
@@ -240,7 +227,8 @@ def verify_manifest(destination: str, manifest_path: str) -> None:
240227 verified_manifest = verify_manifest (prepared_destination , manifest )
241228
242229 (
243- validated_source
230+ copy_paths
231+ >> validated_source
244232 >> prepared_destination
245233 >> manifest
246234 >> confirm_copy
0 commit comments