Skip to content

Commit 507e19c

Browse files
committed
Fix Windows backslash in Azure blob names
1 parent 07d24eb commit 507e19c

2 files changed

Lines changed: 12 additions & 23 deletions

File tree

providers/microsoft/azure/src/airflow/providers/microsoft/azure/transfers/gcs_to_wasb.py

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@
1919

2020
from __future__ import annotations
2121

22-
import os
22+
import posixpath
2323
from collections.abc import Sequence
2424
from typing import TYPE_CHECKING
2525

@@ -143,7 +143,7 @@ def _transform_file_path(self, file_path: str) -> str:
143143
:return: The transformed file path for Azure Blob destination
144144
"""
145145
if self.flatten_structure:
146-
return os.path.basename(file_path)
146+
return posixpath.basename(file_path)
147147
return file_path
148148

149149
@staticmethod
@@ -186,7 +186,7 @@ def execute(self, context: Context) -> list[str]:
186186

187187
blob_prefix = self.blob_prefix
188188
if not self.keep_directory_structure and self.prefix and not self.flatten_structure:
189-
blob_prefix = os.path.join(blob_prefix, self.prefix)
189+
blob_prefix = posixpath.join(blob_prefix, self.prefix)
190190

191191
existing_blobs_set: set[str] = set()
192192
if not self.replace:
@@ -227,7 +227,7 @@ def execute(self, context: Context) -> list[str]:
227227
object_name=file, bucket_name=str(self.gcs_bucket), user_project=self.gcp_user_project
228228
) as local_tmp_file:
229229
transformed_path = self._transform_file_path(file)
230-
dest_blob = os.path.join(blob_prefix, transformed_path)
230+
dest_blob = posixpath.join(blob_prefix, transformed_path)
231231
self.log.info("Saving file from %s to %s", file, dest_blob)
232232
wasb_hook.load_file(
233233
file_path=local_tmp_file.name,

providers/microsoft/azure/tests/unit/microsoft/azure/transfers/test_gcs_to_wasb.py

Lines changed: 8 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,6 @@
1818

1919
from __future__ import annotations
2020

21-
import os
2221
import sys
2322
from types import ModuleType
2423
from unittest import mock
@@ -34,15 +33,6 @@
3433
GCS_OBJECTS = ["data/a.txt", "data/b.txt"]
3534

3635

37-
def _expected_blob_paths(blob_prefix: str, *relative_keys: str) -> list[str]:
38-
"""Match operator dest_blob construction (os.path.join) with forward slashes for asserts."""
39-
return [os.path.join(blob_prefix, key).replace("\\", "/") for key in relative_keys]
40-
41-
42-
def _norm_paths(paths: list[str]) -> list[str]:
43-
return [p.replace("\\", "/") for p in paths]
44-
45-
4636
class TestGCSToAzureBlobStorageOperator:
4737
def test_init_defaults(self):
4838
op = GCSToAzureBlobStorageOperator(
@@ -87,12 +77,11 @@ def test_execute_replace_uploads_all(self, mock_gcs_hook, mock_wasb_hook):
8777
)
8878
result = op.execute(context=None)
8979

90-
assert _norm_paths(result) == _expected_blob_paths(BLOB_PREFIX, "data/a.txt", "data/b.txt")
91-
mock_wasb_hook.return_value.load_file.assert_called()
80+
assert result == [f"{BLOB_PREFIX}/data/a.txt", f"{BLOB_PREFIX}/data/b.txt"]
9281
assert mock_wasb_hook.return_value.load_file.call_count == 2
9382
first_kw = mock_wasb_hook.return_value.load_file.call_args_list[0].kwargs
9483
assert first_kw["container_name"] == CONTAINER
95-
assert first_kw["blob_name"].startswith(BLOB_PREFIX.replace("\\", "/"))
84+
assert first_kw["blob_name"] == f"{BLOB_PREFIX}/data/a.txt"
9685
assert first_kw["overwrite"] is True
9786

9887
@mock.patch("airflow.providers.microsoft.azure.transfers.gcs_to_wasb.WasbHook")
@@ -128,7 +117,7 @@ def test_execute_skips_gcs_directory_placeholder_keys(self, mock_gcs_hook, mock_
128117
)
129118
result = op.execute(context=None)
130119

131-
assert _norm_paths(result) == ["src/airflow.png"]
120+
assert result == ["src/airflow.png"]
132121
mock_wasb_hook.return_value.load_file.assert_called_once()
133122
assert mock_wasb_hook.return_value.load_file.call_args.kwargs["blob_name"] == "src/airflow.png"
134123
assert mock_wasb_hook.return_value.load_file.call_args.kwargs["overwrite"] is True
@@ -154,7 +143,7 @@ def test_execute_skip_existing_when_replace_false(self, mock_gcs_hook, mock_wasb
154143
)
155144
result = op.execute(context=None)
156145

157-
assert _norm_paths(result) == _expected_blob_paths(BLOB_PREFIX, "data/b.txt")
146+
assert result == [f"{BLOB_PREFIX}/data/b.txt"]
158147
mock_wasb_hook.return_value.load_file.assert_called_once()
159148
assert mock_wasb_hook.return_value.load_file.call_args.kwargs["overwrite"] is False
160149

@@ -177,9 +166,9 @@ def test_flatten_structure(self, mock_gcs_hook, mock_wasb_hook):
177166
)
178167
result = op.execute(context=None)
179168

180-
assert _norm_paths(result) == _expected_blob_paths(BLOB_PREFIX, "a.txt")
169+
assert result == [f"{BLOB_PREFIX}/a.txt"]
181170
kw = mock_wasb_hook.return_value.load_file.call_args.kwargs
182-
assert str(kw["blob_name"]).replace("\\", "/") == _expected_blob_paths(BLOB_PREFIX, "a.txt")[0]
171+
assert kw["blob_name"] == f"{BLOB_PREFIX}/a.txt"
183172
assert kw["overwrite"] is True
184173

185174
@mock.patch("airflow.providers.microsoft.azure.transfers.gcs_to_wasb.WasbHook")
@@ -201,7 +190,7 @@ def test_execute_dedups_collisions_when_replace_true(self, mock_gcs_hook, mock_w
201190
)
202191
result = op.execute(context=None)
203192

204-
assert _norm_paths(result) == _expected_blob_paths(BLOB_PREFIX, "file.txt")
193+
assert result == [f"{BLOB_PREFIX}/file.txt"]
205194
mock_wasb_hook.return_value.load_file.assert_called_once()
206195

207196
@mock.patch("airflow.providers.microsoft.azure.transfers.gcs_to_wasb.WasbHook")
@@ -226,7 +215,7 @@ def test_execute_is_idempotent_on_retry(self, mock_gcs_hook, mock_wasb_hook):
226215
first = op.execute(context=None)
227216
second = op.execute(context=None)
228217

229-
assert _norm_paths(first) == _norm_paths(second)
218+
assert first == second
230219
assert op.blob_prefix == BLOB_PREFIX
231220

232221
@mock.patch("airflow.providers.microsoft.azure.transfers.gcs_to_wasb.WasbHook")

0 commit comments

Comments
 (0)