Skip to content

Commit e5fd50e

Browse files
committed
Document and test Metaflow tempdir defaults
1 parent 90bee09 commit e5fd50e

4 files changed

Lines changed: 88 additions & 36 deletions

File tree

‎metaflow/plugins/aws/batch/batch_decorator.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -299,8 +299,9 @@ def task_pre_step(
299299
self.metadata = metadata
300300
self.task_datastore = task_datastore
301301

302-
# current.tempdir reflects the value of METAFLOW_TEMPDIR (the current working
303-
# directory by default), or the value of tmpfs_path if tmpfs_tempdir=False.
302+
# current.tempdir reflects the value of METAFLOW_TEMPDIR (the system
303+
# temporary directory from tempfile.gettempdir() by default), or the
304+
# value of tmpfs_path if tmpfs_tempdir=False.
304305
if not self.attributes["tmpfs_tempdir"]:
305306
current._update_env({"tempdir": self.attributes["tmpfs_path"]})
306307

‎metaflow/plugins/datatools/s3/s3.py‎

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -479,8 +479,9 @@ class S3(object):
479479
data = [obj.blob for obj in s3.get_many(urls)]
480480
s3.close()
481481
```
482-
You can customize the location of the temporary directory with `tmproot`. It
483-
defaults to the current working directory.
482+
You can customize the location of the temporary directory with `tmproot`, or
483+
with the `METAFLOW_TEMPDIR` configuration variable. If neither is specified,
484+
the system temporary directory returned by `tempfile.gettempdir()` is used.
484485
485486
To make it easier to deal with object locations, the client can be initialized
486487
with an S3 path prefix. There are three ways to handle locations:
@@ -499,7 +500,7 @@ class S3(object):
499500
500501
Parameters
501502
----------
502-
tmproot : str, default '.'
503+
tmproot : str, default METAFLOW_TEMPDIR or tempfile.gettempdir()
503504
Where to store the temporary directory.
504505
bucket : str, optional, default None
505506
Override the bucket from `DATATOOLS_S3ROOT` when `run` is specified.

‎metaflow/plugins/kubernetes/kubernetes_decorator.py‎

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -529,8 +529,9 @@ def task_pre_step(
529529
self.metadata = metadata
530530
self.task_datastore = task_datastore
531531

532-
# current.tempdir reflects the value of METAFLOW_TEMPDIR (the current working
533-
# directory by default), or the value of tmpfs_path if tmpfs_tempdir=False.
532+
# current.tempdir reflects the value of METAFLOW_TEMPDIR (the system
533+
# temporary directory from tempfile.gettempdir() by default), or the
534+
# value of tmpfs_path if tmpfs_tempdir=False.
534535
if not self.attributes["tmpfs_tempdir"]:
535536
current._update_env({"tempdir": self.attributes["tmpfs_path"]})
536537

‎test/unit/test_s3_readonly_cwd.py‎

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,13 @@
11
"""Test that S3 client works when current directory is not writeable."""
22

3+
import importlib
34
import os
45
import tempfile
6+
57
import pytest
68

79

8-
def test_s3_client_readonly_cwd(mocker):
9-
"""Test that S3 client doesn't require writable CWD (issue #854)."""
10-
# Mock boto3 and dependencies before importing S3
10+
def _mock_s3_dependencies(mocker):
1111
mock_boto3 = mocker.MagicMock()
1212
mock_transfer_config = mocker.MagicMock()
1313
mocker.patch.dict("sys.modules", {"boto3": mock_boto3})
@@ -16,33 +16,82 @@ def test_s3_client_readonly_cwd(mocker):
1616
{"boto3.s3.transfer": mocker.MagicMock(TransferConfig=mock_transfer_config)},
1717
)
1818

19-
from metaflow.plugins.datatools.s3 import S3
2019

21-
# Create a temporary read-only directory
22-
with tempfile.TemporaryDirectory() as tmpdir:
23-
readonly_dir = os.path.join(tmpdir, "readonly")
24-
os.makedirs(readonly_dir, mode=0o555)
20+
def _load_s3_class():
21+
"""Reload configuration and S3 so environment changes affect the default."""
22+
metaflow_config = importlib.import_module("metaflow.metaflow_config")
23+
s3_module = importlib.import_module("metaflow.plugins.datatools.s3.s3")
24+
importlib.reload(metaflow_config)
25+
importlib.reload(s3_module)
26+
# Keep the package-level export in sync for tests that import S3 later.
27+
s3_package = importlib.import_module("metaflow.plugins.datatools.s3")
28+
s3_package.S3 = s3_module.S3
29+
return s3_module.S3
2530

26-
# Change to read-only directory
27-
original_cwd = os.getcwd()
28-
try:
29-
os.chdir(readonly_dir)
3031

31-
# This should not raise an exception about permissions
32-
# The S3 client should use system temp dir, not CWD
33-
s3_client = S3()
32+
def test_s3_client_readonly_cwd(mocker, monkeypatch):
33+
"""Test that S3 client doesn't require writable CWD (issue #854)."""
34+
_mock_s3_dependencies(mocker)
35+
original_tempdir = os.environ.get("METAFLOW_TEMPDIR")
36+
37+
try:
38+
with monkeypatch.context() as env:
39+
env.delenv("METAFLOW_TEMPDIR", raising=False)
40+
S3 = _load_s3_class()
41+
42+
# Create a temporary read-only directory
43+
with tempfile.TemporaryDirectory() as tmpdir:
44+
readonly_dir = os.path.join(tmpdir, "readonly")
45+
os.makedirs(readonly_dir, mode=0o555)
46+
47+
# Change to read-only directory
48+
original_cwd = os.getcwd()
49+
try:
50+
os.chdir(readonly_dir)
3451

35-
# Verify that tmpdir was created in system temp, not CWD
36-
assert s3_client._tmpdir is not None
37-
# The temp dir should NOT be in the current (read-only) directory
38-
assert not s3_client._tmpdir.startswith(readonly_dir)
39-
# The temp dir should be in the system temp directory
40-
system_temp = tempfile.gettempdir()
41-
assert s3_client._tmpdir.startswith(system_temp)
42-
43-
# Clean up
44-
s3_client.close()
45-
finally:
46-
os.chdir(original_cwd)
47-
# Make directory writable again so it can be deleted
48-
os.chmod(readonly_dir, 0o755)
52+
# This should not raise an exception about permissions
53+
# The S3 client should use system temp dir, not CWD
54+
s3_client = S3()
55+
56+
# Verify that tmpdir was created in system temp, not CWD
57+
assert s3_client._tmpdir is not None
58+
# The temp dir should NOT be in the current (read-only) directory
59+
assert not s3_client._tmpdir.startswith(readonly_dir)
60+
# The temp dir should be in the system temp directory
61+
system_temp = tempfile.gettempdir()
62+
assert s3_client._tmpdir.startswith(system_temp)
63+
64+
# Clean up
65+
s3_client.close()
66+
finally:
67+
os.chdir(original_cwd)
68+
# Make directory writable again so it can be deleted
69+
os.chmod(readonly_dir, 0o755)
70+
assert os.environ.get("METAFLOW_TEMPDIR") == original_tempdir
71+
finally:
72+
# Restore the S3 module's import-time default after the isolated test.
73+
_load_s3_class()
74+
75+
76+
def test_s3_client_respects_metaflow_tempdir(mocker, monkeypatch, tmp_path):
77+
"""Test that METAFLOW_TEMPDIR overrides the system temporary directory."""
78+
_mock_s3_dependencies(mocker)
79+
configured_dir = tmp_path / "configured"
80+
configured_dir.mkdir()
81+
original_tempdir = os.environ.get("METAFLOW_TEMPDIR")
82+
83+
try:
84+
with monkeypatch.context() as env:
85+
env.setenv("METAFLOW_TEMPDIR", str(configured_dir))
86+
S3 = _load_s3_class()
87+
88+
s3_client = S3()
89+
try:
90+
assert s3_client._tmproot == str(configured_dir)
91+
assert os.path.dirname(s3_client._tmpdir) == str(configured_dir)
92+
finally:
93+
s3_client.close()
94+
assert os.environ.get("METAFLOW_TEMPDIR") == original_tempdir
95+
finally:
96+
# Restore the S3 module's import-time default after the isolated test.
97+
_load_s3_class()

0 commit comments

Comments
 (0)