main
py 93 lines 2.96 KB
Raw
1 # Copyright 2026 Google LLC
2 #
3 # Licensed under the Apache License, Version 2.0 (the "License");
4 # you may not use this file except in compliance with the License.
5 # You may obtain a copy of the License at
6 #
7 # http://www.apache.org/licenses/LICENSE-2.0
8 #
9 # Unless required by applicable law or agreed to in writing, software
10 # distributed under the License is distributed on an "AS IS" BASIS,
11 # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12 # See the License for the specific language governing permissions and
13 # limitations under the License.
14
15 import base64
16 from urllib.parse import quote
17
18 import requests
19
20 from colab_cli.state import SessionState
21 from colab_cli.utils import get_status_code
22
23
24 class ContentsClient:
25 def __init__(self, session_state: SessionState):
26 self.base_url = session_state.url.rstrip("/")
27 self.token = session_state.token
28
29 def _request(
30 self, method: str, path: str, params: dict = None, json_data: dict = None
31 ):
32 # Quote the path, but don't encode slashes so directory paths stay intact
33 quoted_path = quote(path.strip("/"), safe="/")
34 url = f"{self.base_url}/api/contents/{quoted_path}"
35
36 req_params = {"authuser": "0", "colab-runtime-proxy-token": self.token}
37 if params:
38 req_params.update(params)
39
40 response = requests.request(method, url, params=req_params, json=json_data)
41
42 if get_status_code(response) == 404:
43 raise FileNotFoundError(f"File or directory not found: {path}")
44
45 response.raise_for_status()
46
47 # DELETE doesn't return JSON
48 if method == "DELETE":
49 return None
50
51 return response.json()
52
53 def list_dir(self, path: str):
54 return self._request("GET", path)
55
56 def upload(self, local_path: str, remote_path: str):
57 with open(local_path, "rb") as f:
58 content = f.read()
59
60 b64_content = base64.b64encode(content).decode("ascii")
61 filename = remote_path.split("/")[-1]
62
63 payload = {
64 "name": filename,
65 "path": remote_path,
66 "type": "file",
67 "format": "base64",
68 "content": b64_content,
69 "chunk": 1,
70 }
71
72 return self._request("PUT", remote_path, json_data=payload)
73
74 def download(self, remote_path: str, local_path: str):
75 data = self._request("GET", remote_path, params={"content": "1"})
76
77 if data.get("type") == "directory":
78 raise IsADirectoryError(f"Cannot download a directory: {remote_path}")
79
80 content = data.get("content", "")
81 fmt = data.get("format")
82
83 if fmt == "base64":
84 content_bytes = base64.b64decode(content)
85 else:
86 # Assume text if it's not base64 explicitly encoded
87 content_bytes = str(content).encode("utf-8")
88
89 with open(local_path, "wb") as f:
90 f.write(content_bytes)
91
92 def rm(self, remote_path: str):
93 self._request("DELETE", remote_path)