Skip to content

Commit 3d6c360

Browse files
cleaning input path to account for ending '/' as well as 'incoming' (#82)
* cleaning input path to account for ending '/' as well as 'incoming' * Using pathlib for ensuring destination is /incoming directory
1 parent a6e83c7 commit 3d6c360

3 files changed

Lines changed: 36 additions & 12 deletions

File tree

mokelumne/dags/copy_files.py

Lines changed: 4 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@
1414
from mokelumne.util.storage import run_dir
1515
from mokelumne.util.file_transfer import (
1616
build_file_manifest,
17+
clean_destination_path,
1718
copy_files_from_manifest,
1819
load_json,
1920
save_json,
@@ -64,20 +65,11 @@ def validate_source() -> str:
6465

6566
@task
6667
def prepare_destination() -> str:
67-
"""
68-
Prepare the destination directory.
69-
TODO: one of the requirements that we got from Lynne is that the destination
70-
should be an incoming subdirectory of any directory on PA or DA,
71-
e.g. /srv/pa/aerial/ucb/incoming. i don't have a good answer for how to
72-
approach this, but i'm curious about how you think we should confirm that 1)
73-
we're actually writing to the appropriate incoming subdirectory, and if the
74-
incoming directory exists and has files in it, we should fail the job.
75-
"""
68+
"""Prepare the destination directory."""
7669

7770
ctx = get_current_context()
7871
destination = ctx["params"]["destination"]
79-
80-
destination_path = Path(destination)
72+
destination_path = clean_destination_path(Path(destination))
8173

8274
if not destination_path.exists():
8375
logger.info("Creating destination directory: %s", destination_path)
@@ -89,7 +81,7 @@ def prepare_destination() -> str:
8981
if next(os.scandir(destination_path), None) is not None:
9082
raise AirflowFailException(f"Destination directory contains files: {destination_path}")
9183

92-
return destination
84+
return str(destination_path)
9385

9486
@task
9587
def build_manifest(source: str) -> str:

mokelumne/util/file_transfer.py

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,12 @@ def load_json(path: Path) -> Manifest:
6464
with open(path, "r", encoding="utf-8") as json_file:
6565
return json.load(json_file)
6666

67+
def clean_destination_path(destination_path: Path) -> Path:
68+
"""Normalize a destination path to end with /incoming."""
69+
if destination_path.name.casefold() == "incoming":
70+
destination_path = destination_path.parent
71+
72+
return Path(destination_path / "incoming")
6773

6874
def verify_file_manifest(destination_path: Path, manifest_path: Path) -> list[ManifestEntry]:
6975
"""Verify all copied files exist at the destination."""

test/unit/test_file_transfer.py

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,32 @@ def test_build_manifest_excludes_hidden_and_excluded_files(self, tmp_path: Path)
9595
assert paths == {"visible.tif"}
9696
assert len(result["files"]) == 1
9797

98+
@pytest.mark.parametrize(
99+
("input_path", "expected_path"),
100+
[
101+
(
102+
Path("/srv/pa/aerial/ucb"),
103+
Path("/srv/pa/aerial/ucb/incoming"),
104+
),
105+
(
106+
Path("/srv/pa/aerial/ucb/"),
107+
Path("/srv/pa/aerial/ucb/incoming"),
108+
),
109+
(
110+
Path("/srv/pa/aerial/ucb/incoming"),
111+
Path("/srv/pa/aerial/ucb/incoming"),
112+
),
113+
(
114+
Path("/srv/pa/aerial/ucb/incoming/"),
115+
Path("/srv/pa/aerial/ucb/incoming"),
116+
),
117+
],
118+
)
119+
def test_clean_destination_path(self, input_path: Path, expected_path: Path):
120+
"""Ensure clean_destination_path normalizes the incoming directory suffix."""
121+
122+
assert file_transfer.clean_destination_path(input_path) == expected_path
123+
98124
def test_verify_manifest_success(self, tmp_path: Path):
99125
"""Ensure verify_manifest succeeds for valid files."""
100126

0 commit comments

Comments
 (0)