proteworks 0.1.2__tar.gz → 0.1.3__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- {proteworks-0.1.2 → proteworks-0.1.3}/PKG-INFO +1 -1
- {proteworks-0.1.2 → proteworks-0.1.3}/pyproject.toml +1 -1
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/api.py +8 -4
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/pull.py +11 -2
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/push.py +4 -1
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks.egg-info/PKG-INFO +1 -1
- {proteworks-0.1.2 → proteworks-0.1.3}/tests/test_cli.py +39 -7
- {proteworks-0.1.2 → proteworks-0.1.3}/README.md +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/setup.cfg +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/__init__.py +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/auth.py +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/cli.py +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/config.py +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/index.py +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/orgs.py +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks/redact.py +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks.egg-info/SOURCES.txt +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks.egg-info/dependency_links.txt +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks.egg-info/entry_points.txt +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks.egg-info/requires.txt +0 -0
- {proteworks-0.1.2 → proteworks-0.1.3}/src/proteworks.egg-info/top_level.txt +0 -0
|
@@ -88,19 +88,21 @@ class ApiClient:
|
|
|
88
88
|
params={"path": path, "offset": offset, "limit": limit},
|
|
89
89
|
)
|
|
90
90
|
|
|
91
|
-
def manifest_all(self, path: str) -> dict:
|
|
91
|
+
def manifest_all(self, path: str, *, on_list_progress=None, page_size: int = 500) -> dict:
|
|
92
92
|
"""Follow pages. Returns items plus root_folder_id from the first page."""
|
|
93
93
|
offset = 0
|
|
94
94
|
items: list[dict] = []
|
|
95
95
|
root_folder_id = None
|
|
96
96
|
total = None
|
|
97
97
|
while True:
|
|
98
|
-
page = self.manifest(path, offset)
|
|
98
|
+
page = self.manifest(path, offset, limit=page_size)
|
|
99
99
|
root_folder_id = root_folder_id or page.get("root_folder_id")
|
|
100
100
|
batch = list(page.get("items") or [])
|
|
101
101
|
items.extend(batch)
|
|
102
102
|
total = int(page.get("total") or 0)
|
|
103
103
|
offset += len(batch)
|
|
104
|
+
if on_list_progress is not None:
|
|
105
|
+
on_list_progress(offset, total)
|
|
104
106
|
if not batch or offset >= total:
|
|
105
107
|
break
|
|
106
108
|
return {"items": items, "total": total or 0, "root_folder_id": root_folder_id}
|
|
@@ -112,17 +114,19 @@ class ApiClient:
|
|
|
112
114
|
params={"offset": offset, "limit": limit},
|
|
113
115
|
)
|
|
114
116
|
|
|
115
|
-
def export_all(self) -> dict:
|
|
117
|
+
def export_all(self, *, on_list_progress=None, page_size: int = 500) -> dict:
|
|
116
118
|
"""Follow export pages. Returns every item for the active organization."""
|
|
117
119
|
offset = 0
|
|
118
120
|
items: list[dict] = []
|
|
119
121
|
total = None
|
|
120
122
|
while True:
|
|
121
|
-
page = self.export_page(offset)
|
|
123
|
+
page = self.export_page(offset, limit=page_size)
|
|
122
124
|
batch = list(page.get("items") or [])
|
|
123
125
|
items.extend(batch)
|
|
124
126
|
total = int(page.get("total") or 0)
|
|
125
127
|
offset += len(batch)
|
|
128
|
+
if on_list_progress is not None:
|
|
129
|
+
on_list_progress(offset, total)
|
|
126
130
|
if not batch or offset >= total:
|
|
127
131
|
break
|
|
128
132
|
return {"items": items, "total": total or 0}
|
|
@@ -20,10 +20,19 @@ from proteworks.index import (
|
|
|
20
20
|
_CONTENT_URL_BATCH = 100
|
|
21
21
|
|
|
22
22
|
|
|
23
|
+
def _list_progress(stderr):
|
|
24
|
+
"""Print how many paths the API has returned so far."""
|
|
25
|
+
|
|
26
|
+
def report(listed: int, total: int) -> None:
|
|
27
|
+
print(f"Listed {listed}/{total}", file=stderr)
|
|
28
|
+
|
|
29
|
+
return report
|
|
30
|
+
|
|
31
|
+
|
|
23
32
|
def pull_path(api: ApiClient, remote: str, dest: Path, *, dry_run: bool, stderr) -> int:
|
|
24
33
|
"""Download readable files for a file-browser path. Returns a process status code."""
|
|
25
34
|
try:
|
|
26
|
-
page = api.manifest_all(remote)
|
|
35
|
+
page = api.manifest_all(remote, on_list_progress=_list_progress(stderr))
|
|
27
36
|
except ApiError as exc:
|
|
28
37
|
print(str(exc), file=stderr)
|
|
29
38
|
return 1
|
|
@@ -35,7 +44,7 @@ def pull_path(api: ApiClient, remote: str, dest: Path, *, dry_run: bool, stderr)
|
|
|
35
44
|
def export_organization(api: ApiClient, dest: Path, *, dry_run: bool, stderr) -> int:
|
|
36
45
|
"""Download every live file in the active organization. Returns a process status code."""
|
|
37
46
|
try:
|
|
38
|
-
page = api.export_all()
|
|
47
|
+
page = api.export_all(on_list_progress=_list_progress(stderr))
|
|
39
48
|
except ApiError as exc:
|
|
40
49
|
print(str(exc), file=stderr)
|
|
41
50
|
return 1
|
|
@@ -20,7 +20,10 @@ def push_directory(api: ApiClient, local_dir: Path, remote: str, *, dry_run: boo
|
|
|
20
20
|
print(str(exc), file=stderr)
|
|
21
21
|
return 2
|
|
22
22
|
try:
|
|
23
|
-
page = api.manifest_all(
|
|
23
|
+
page = api.manifest_all(
|
|
24
|
+
remote,
|
|
25
|
+
on_list_progress=lambda listed, total: print(f"Listed {listed}/{total}", file=stderr),
|
|
26
|
+
)
|
|
24
27
|
except ApiError as exc:
|
|
25
28
|
print(str(exc), file=stderr)
|
|
26
29
|
return 1
|
|
@@ -13,6 +13,7 @@ from http.server import BaseHTTPRequestHandler, ThreadingHTTPServer
|
|
|
13
13
|
from pathlib import Path
|
|
14
14
|
from urllib.parse import parse_qs, urlparse
|
|
15
15
|
|
|
16
|
+
from proteworks.api import ApiClient
|
|
16
17
|
from proteworks.auth import loopback_redirect_uri, open_browser
|
|
17
18
|
from proteworks.cli import main
|
|
18
19
|
from proteworks.config import Settings
|
|
@@ -91,13 +92,16 @@ class FakeApi(threading.Thread):
|
|
|
91
92
|
return
|
|
92
93
|
if parsed.path == "/api/v1/sync/manifest":
|
|
93
94
|
query = parse_qs(parsed.query)
|
|
95
|
+
offset = int((query.get("offset") or ["0"])[0])
|
|
96
|
+
limit = int((query.get("limit") or ["500"])[0])
|
|
97
|
+
page = parent.items[offset : offset + limit]
|
|
94
98
|
self._send(
|
|
95
99
|
200,
|
|
96
100
|
{
|
|
97
|
-
"items":
|
|
101
|
+
"items": page,
|
|
98
102
|
"total": len(parent.items),
|
|
99
|
-
"limit":
|
|
100
|
-
"offset":
|
|
103
|
+
"limit": limit,
|
|
104
|
+
"offset": offset,
|
|
101
105
|
"root_folder_id": parent.root_folder_id,
|
|
102
106
|
"cloud_storage_url": GS_URL,
|
|
103
107
|
},
|
|
@@ -110,14 +114,18 @@ class FakeApi(threading.Thread):
|
|
|
110
114
|
status, payload = parent.export_error
|
|
111
115
|
self._send(status, payload)
|
|
112
116
|
return
|
|
117
|
+
query = parse_qs(parsed.query)
|
|
118
|
+
offset = int((query.get("offset") or ["0"])[0])
|
|
119
|
+
limit = int((query.get("limit") or ["500"])[0])
|
|
113
120
|
exported = parent.export_items or []
|
|
121
|
+
page = exported[offset : offset + limit]
|
|
114
122
|
self._send(
|
|
115
123
|
200,
|
|
116
124
|
{
|
|
117
|
-
"items":
|
|
125
|
+
"items": page,
|
|
118
126
|
"total": len(exported),
|
|
119
|
-
"limit":
|
|
120
|
-
"offset":
|
|
127
|
+
"limit": limit,
|
|
128
|
+
"offset": offset,
|
|
121
129
|
"cloud_storage_url": GS_URL,
|
|
122
130
|
},
|
|
123
131
|
)
|
|
@@ -333,7 +341,29 @@ def test_pull_with_two_regular_orgs_stops_before_manifest(tmp_path, capsys):
|
|
|
333
341
|
assert not any(path == "/api/v1/sync/manifest" for _method, path, _body in server.requests)
|
|
334
342
|
|
|
335
343
|
|
|
336
|
-
def
|
|
344
|
+
def test_manifest_all_reports_listing_progress():
|
|
345
|
+
items = []
|
|
346
|
+
for index in range(5):
|
|
347
|
+
item = _item(f"~/My Files/msa/{index}.cif")
|
|
348
|
+
item["file_id"] = f"f{index}"
|
|
349
|
+
items.append(item)
|
|
350
|
+
server = FakeApi([_org("reg", "Research")], items).start()
|
|
351
|
+
progress = []
|
|
352
|
+
try:
|
|
353
|
+
api = ApiClient(server.url, "token", organization_id="reg")
|
|
354
|
+
page = api.manifest_all(
|
|
355
|
+
"~/My Files/msa/",
|
|
356
|
+
on_list_progress=lambda listed, total: progress.append((listed, total)),
|
|
357
|
+
page_size=2,
|
|
358
|
+
)
|
|
359
|
+
finally:
|
|
360
|
+
api.close()
|
|
361
|
+
server.stop()
|
|
362
|
+
assert len(page["items"]) == 5
|
|
363
|
+
assert progress == [(2, 5), (4, 5), (5, 5)]
|
|
364
|
+
|
|
365
|
+
|
|
366
|
+
def test_pull_mints_download_urls_in_one_batch(tmp_path, capsys):
|
|
337
367
|
items = []
|
|
338
368
|
for name in ("a.cif", "b.cif", "c.cif"):
|
|
339
369
|
item = _item(f"~/My Files/msa/{name}")
|
|
@@ -355,7 +385,9 @@ def test_pull_mints_download_urls_in_one_batch(tmp_path):
|
|
|
355
385
|
for method, path, body in server.requests
|
|
356
386
|
if method == "POST" and path == "/api/v1/files/content-urls"
|
|
357
387
|
]
|
|
388
|
+
err = capsys.readouterr().err
|
|
358
389
|
assert code == 0
|
|
390
|
+
assert "Listed 3/3" in err
|
|
359
391
|
assert len(posts) == 1
|
|
360
392
|
assert set(posts[0]["file_ids"]) == {"a.cif", "b.cif", "c.cif"}
|
|
361
393
|
assert posts[0]["purpose"] == "download"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|