-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathtemp
More file actions
122 lines (101 loc) · 3.96 KB
/
temp
File metadata and controls
122 lines (101 loc) · 3.96 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
# artifactory_client.py
from __future__ import annotations
import mimetypes
from pathlib import Path
import requests
import db_service # your in-memory “database”
class ArtifactoryClient:
"""
Tiny wrapper around the Artifactory REST API **plus** a light persistence
layer for the API key.
The key lives in `db_service`, which exposes:
save_api_key(key: str) -> None
get_api_key() -> str | None
"""
def __init__(
self,
base_url: str,
*,
api_key: str | None = None,
db_service=db_service, # allow injection/mocking in tests
) -> None:
self._db = db_service
# --- 1. obtain an API key -------------------------------------------
if api_key is None:
api_key = self._db.get_api_key()
if api_key is None:
raise ValueError(
"No API key provided and none stored in db_service. "
"Pass api_key=... when constructing ArtifactoryClient."
)
else:
# caller gave us a key → cache it for next time
self._db.save_api_key(api_key)
# --- 2. normalise base URL ------------------------------------------
if not base_url.endswith("/"):
base_url += "/"
if "/artifactory/" not in base_url:
base_url += "artifactory/"
self._base = base_url
# --- 3. prepare session ---------------------------------------------
self._s = requests.Session()
self._s.headers.update({"X-JFrog-Art-Api": api_key})
# --------------------------------------------------------------------- #
# everything below this line is IDENTICAL to the original implementation
# --------------------------------------------------------------------- #
def list_repositories(self, repo_type: str | None = None) -> list[dict]:
url = self._make_url("api", "repositories")
if repo_type:
url += f"?type={repo_type}"
return self._get_json(url)
def file_info(self, repo: str, path: str = "") -> dict:
url = self._make_url("api", "storage", repo, path)
return self._get_json(url)
def upload_file(
self,
repo: str,
remote_path: str,
local_path: str | Path,
*,
properties: dict[str, str] | None = None,
) -> dict:
lp = Path(local_path)
url = self._make_url(repo, remote_path)
if properties:
url += ";" + ";".join(f"{k}={v}" for k, v in properties.items())
headers = {
"Content-Type": mimetypes.guess_type(lp.name)[0] or "application/octet-stream"
}
with lp.open("rb") as fh:
r = self._s.put(url, data=fh, headers=headers, timeout=300)
r.raise_for_status()
return r.json() if r.headers.get("Content-Type") == "application/json" else {}
def download_file(
self,
repo: str,
remote_path: str,
local_path: str | Path,
*,
chunk: int = 1 << 20,
) -> Path:
lp = Path(local_path)
lp.parent.mkdir(parents=True, exist_ok=True)
url = self._make_url(repo, remote_path)
with self._s.get(url, stream=True, timeout=300) as r:
r.raise_for_status()
with lp.open("wb") as fh:
for block in r.iter_content(chunk):
fh.write(block)
return lp
# --- housekeeping ------------------------------------------------------
def close(self) -> None:
self._s.close()
def __enter__(self): return self
def __exit__(self, *_): self.close()
# --- internals ----------------------------------------------------------
def _make_url(self, *parts: str) -> str:
return self._base + "/".join(str(p).lstrip("/") for p in parts)
def _get_json(self, url: str) -> dict | list:
r = self._s.get(url, timeout=30)
r.raise_for_status()
return r.json()