-
Notifications
You must be signed in to change notification settings - Fork 1
Expand file tree
/
Copy pathio.py
More file actions
130 lines (107 loc) · 4.97 KB
/
Copy pathio.py
File metadata and controls
130 lines (107 loc) · 4.97 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
# python/io.py
from __future__ import annotations
import os, json, subprocess, tempfile
from pathlib import Path
from typing import Any, Optional
try:
import yaml # ensure pyyaml in requirements.txt
except Exception:
yaml = None
# ── Repo & data ────────────────────────────────────────────────────────────────
def repo_root() -> Path:
return Path(__file__).resolve().parents[1]
def data_path(*parts: str) -> Path:
return repo_root() / "data" / Path(*parts)
def read_yaml(path: str | Path) -> Any:
if yaml is None:
raise RuntimeError("pyyaml not installed")
with open(path, "r") as f:
return yaml.safe_load(f)
def read_json(path: str | Path) -> Any:
with open(path, "r") as f:
return json.load(f)
# ── EOS utilities ──────────────────────────────────────────────────────────────
def eos_endpoint() -> str:
"""Hostname for xrdfs/xrdcp (override via $EOS_ENDPOINT)."""
return os.environ.get("EOS_ENDPOINT", "eosuser.cern.ch")
def resolve_eos_user() -> str:
"""Return segment like 'w/wijackso' for /eos/user/<seg>/..."""
if (p := os.environ.get("EOSUSER_PATH")):
return p.strip("/")
if (u := os.environ.get("EOSUSER")):
return f"{u[0]}/{u}"
mapping = {"bjackson": "w/wijackso"}
user = os.environ.get("USER", "user")
return mapping.get(user, f"{user[0]}/{user}")
def eos_base(run: str, year: str, era: str, subdir: Optional[str] = None) -> Path:
"""Build an EOS path prefix (can override root via $EOS_BASE)."""
base_override = os.environ.get("EOS_BASE") # e.g. /eos/user/w/wijackso
root = Path(base_override) if base_override else Path(f"/eos/user/{resolve_eos_user()}")
base = root / run / year / era
return base / subdir if subdir else base
def is_eos_path(path: str | Path) -> bool:
"""Heuristic: treat paths under /eos as EOS destinations."""
return str(path).startswith("/eos/")
def eos_mkdir_p(dir_path: str | Path) -> None:
"""
Recursively create a directory on EOS using xrdfs.
Equivalent to: xrdfs <host> mkdir -p <dir_path>
"""
host = eos_endpoint()
dir_path = str(dir_path)
# xrdfs doesn't mkdir the final leaf if parents absent unless -p used
subprocess.run(["xrdfs", host, "mkdir", "-p", dir_path], check=True)
def eos_upload(local_file: str | Path, eos_dest: str | Path) -> None:
"""
Copy a local file to EOS with xrdcp.
"""
host = eos_endpoint()
local_file = str(local_file)
eos_dest = str(eos_dest)
subprocess.run(["xrdcp", "-f", local_file, f"root://{host}/{eos_dest}"], check=True)
# ── Output location (EOS vs local) ─────────────────────────────────────────────
def _eos_available() -> bool:
if os.environ.get("FORCE_LOCAL"):
return False
if os.environ.get("FORCE_EOS"):
return True
base_override = os.environ.get("EOS_BASE")
probe = Path(base_override) if base_override else Path("/eos")
return probe.exists()
def output_dir(run: str, year: str, era: str, subdir: Optional[str] = None,
*, prefer_local: bool = False) -> Path:
"""
Choose an output root and ensure it exists:
- EOS when mounted (or FORCE_EOS=1),
- otherwise {repo}/outputs/...
NOTE: For EOS, we don't mkdir with Path().mkdir(); we use xrdfs on demand in save_figure().
"""
if prefer_local or not _eos_available():
base = repo_root() / "outputs" / run / year / era
out = base / subdir if subdir else base
out.mkdir(parents=True, exist_ok=True)
return out
else:
return eos_base(run, year, era, subdir)
# ── Save helpers ───────────────────────────────────────────────────────────────
def ensure_local_dir(path: str | Path) -> None:
Path(path).mkdir(parents=True, exist_ok=True)
def save_figure(fig, dest_path: str | Path, *, force_local: bool = False, fmt: str = "pdf") -> None:
"""
Save a Matplotlib figure to either local filesystem or EOS, based on dest_path.
- If path starts with /eos and not force_local: use xrdfs mkdir -p + xrdcp
- Otherwise: save locally (mkdir -p)
"""
dest_path = Path(dest_path)
if is_eos_path(dest_path) and not force_local:
# ensure EOS dir
eos_dir = dest_path.parent
eos_mkdir_p(eos_dir)
# temp save then upload
with tempfile.NamedTemporaryFile(suffix=f".{fmt}") as tmp:
fig.savefig(tmp.name, format=fmt)
tmp.flush()
eos_upload(tmp.name, dest_path)
else:
ensure_local_dir(dest_path.parent)
fig.savefig(dest_path, format=fmt)