outo-models-cli 0.2.0__tar.gz → 0.2.2__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.
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/PKG-INFO +1 -1
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/pyproject.toml +1 -1
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/api/lfs.py +73 -16
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/api/lfs_upload.py +61 -37
- outo_models_cli-0.2.2/tests/test_lfs_upload.py +101 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/uv.lock +1 -1
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/.gitignore +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/LICENSE +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/README.md +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/__init__.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/__main__.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/api/__init__.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/api/auth.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/api/lfs_batch.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/api/repos.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/api/upload.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/__init__.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/_shared.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/_upload_commit.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/auth.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/download.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/ls.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/repo.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/upload.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/config.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/downloader/__init__.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/downloader/transfer.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/downloader/walk.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/errors.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/http.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/main.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/matchers.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/conftest.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_api_crud.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_api_upload.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_cli_auth.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_cli_errors.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_cli_help.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_cli_repo.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_cli_upload_lfs.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_cli_xfer.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_config.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_download_lfs.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_download_repo.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_download_transfer.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_download_walk.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_errors.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_lfs.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_matchers.py +0 -0
- {outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/tests/test_upload_commit.py +0 -0
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: outo-models-cli
|
|
3
|
-
Version: 0.2.
|
|
3
|
+
Version: 0.2.2
|
|
4
4
|
Summary: Command-line client for self-hosted outo-models servers — auth, repo, download, and upload.
|
|
5
5
|
Project-URL: Homepage, https://github.com/outo-models/outo-models
|
|
6
6
|
Project-URL: Documentation, https://github.com/outo-models/outo-models/blob/main/omc/README.md
|
|
@@ -40,7 +40,7 @@ import hashlib
|
|
|
40
40
|
from collections.abc import Iterable
|
|
41
41
|
from dataclasses import dataclass, field
|
|
42
42
|
from pathlib import Path
|
|
43
|
-
from typing import Any
|
|
43
|
+
from typing import Any, Protocol
|
|
44
44
|
|
|
45
45
|
LFS_CONTENT_TYPE = "application/vnd.git-lfs+json"
|
|
46
46
|
|
|
@@ -104,10 +104,17 @@ class Partition:
|
|
|
104
104
|
large: list[LargeFile] = field(default_factory=list)
|
|
105
105
|
|
|
106
106
|
|
|
107
|
-
def _sha256_of(
|
|
107
|
+
def _sha256_of(
|
|
108
|
+
path: Path,
|
|
109
|
+
*,
|
|
110
|
+
progress: _ProgressSink | None = None,
|
|
111
|
+
task_id: Any = None,
|
|
112
|
+
) -> tuple[str, int]:
|
|
108
113
|
"""Stream `path` through sha256; return `(hex_digest, size)`.
|
|
109
114
|
|
|
110
115
|
A 1 MiB read buffer keeps memory bounded regardless of file size.
|
|
116
|
+
When `progress`/`task_id` are given, hashing advances the task so a
|
|
117
|
+
multi-GiB file does not sit in silent disk reads for minutes.
|
|
111
118
|
Errors from the underlying `open()` (file removed between the
|
|
112
119
|
partition step and the PUT) surface unchanged so the caller's
|
|
113
120
|
`OmcError` mapping sees the real cause.
|
|
@@ -121,10 +128,17 @@ def _sha256_of(path: Path) -> tuple[str, int]:
|
|
|
121
128
|
break
|
|
122
129
|
hasher.update(chunk)
|
|
123
130
|
total += len(chunk)
|
|
131
|
+
if progress is not None and task_id is not None:
|
|
132
|
+
progress.update(task_id, advance=len(chunk))
|
|
124
133
|
return hasher.hexdigest(), total
|
|
125
134
|
|
|
126
135
|
|
|
127
|
-
def partition_files(
|
|
136
|
+
def partition_files(
|
|
137
|
+
files: Iterable[Path],
|
|
138
|
+
*,
|
|
139
|
+
cap_bytes: int,
|
|
140
|
+
show_progress: bool = True,
|
|
141
|
+
) -> Partition:
|
|
128
142
|
"""Split `files` into small (multipart) and large (LFS) buckets.
|
|
129
143
|
|
|
130
144
|
The function streams each large file exactly once to compute its
|
|
@@ -137,24 +151,67 @@ def partition_files(files: Iterable[Path], *, cap_bytes: int) -> Partition:
|
|
|
137
151
|
|
|
138
152
|
small: list[Path] = []
|
|
139
153
|
large: list[LargeFile] = []
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
f"Size of {f} changed during hashing ({observed} != {size}); retry.",
|
|
154
|
+
hashing: _ProgressSink | None = None
|
|
155
|
+
try:
|
|
156
|
+
for f in files:
|
|
157
|
+
size = f.stat().st_size
|
|
158
|
+
if size <= cap_bytes:
|
|
159
|
+
small.append(f)
|
|
160
|
+
continue
|
|
161
|
+
if hashing is None and show_progress:
|
|
162
|
+
hashing = _make_hash_progress()
|
|
163
|
+
task_id = (
|
|
164
|
+
hashing.add_task(f"hash {f.name}", total=size) if hashing is not None else None
|
|
152
165
|
)
|
|
153
|
-
|
|
166
|
+
oid, observed = _sha256_of(f, progress=hashing, task_id=task_id)
|
|
167
|
+
if hashing is not None and task_id is not None:
|
|
168
|
+
hashing.remove_task(task_id)
|
|
169
|
+
if observed != size:
|
|
170
|
+
# Defensive: `stat().st_size` could race with concurrent
|
|
171
|
+
# writers. Surface as `BadResponseError`-shaped failure so
|
|
172
|
+
# the user sees a clean English line, not a Python traceback.
|
|
173
|
+
raise BadResponseError(
|
|
174
|
+
f"Size of {f} changed during hashing ({observed} != {size}); retry.",
|
|
175
|
+
)
|
|
176
|
+
large.append(LargeFile(path=f, oid=oid, size=size))
|
|
177
|
+
finally:
|
|
178
|
+
if hashing is not None:
|
|
179
|
+
hashing.stop()
|
|
154
180
|
large.sort(key=lambda lf: lf.size, reverse=True)
|
|
155
181
|
return Partition(small=small, large=large)
|
|
156
182
|
|
|
157
183
|
|
|
184
|
+
class _ProgressSink(Protocol):
|
|
185
|
+
"""The add/update/remove/stop surface partition_files needs."""
|
|
186
|
+
|
|
187
|
+
def add_task(self, description: str, *, total: float | None = None) -> Any: ...
|
|
188
|
+
def update(self, task_id: Any, **kwargs: Any) -> None: ...
|
|
189
|
+
def remove_task(self, task_id: Any) -> None: ...
|
|
190
|
+
def stop(self) -> None: ...
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
def _make_hash_progress() -> _ProgressSink:
|
|
194
|
+
"""A Rich progress dedicated to the hashing phase (file-named tasks)."""
|
|
195
|
+
from rich.progress import (
|
|
196
|
+
BarColumn,
|
|
197
|
+
DownloadColumn,
|
|
198
|
+
Progress,
|
|
199
|
+
TextColumn,
|
|
200
|
+
TimeRemainingColumn,
|
|
201
|
+
TransferSpeedColumn,
|
|
202
|
+
)
|
|
203
|
+
|
|
204
|
+
progress = Progress(
|
|
205
|
+
TextColumn("[bold cyan]{task.description}[/bold cyan]", justify="left"),
|
|
206
|
+
BarColumn(bar_width=40),
|
|
207
|
+
DownloadColumn(),
|
|
208
|
+
TransferSpeedColumn(),
|
|
209
|
+
TimeRemainingColumn(),
|
|
210
|
+
)
|
|
211
|
+
progress.start()
|
|
212
|
+
return progress
|
|
213
|
+
|
|
214
|
+
|
|
158
215
|
# ---------------------------------------------------------------------------
|
|
159
216
|
# Deduplication — identical (oid, size) pairs collapse to one batch entry
|
|
160
217
|
# ---------------------------------------------------------------------------
|
|
@@ -26,6 +26,7 @@ from rich.progress import (
|
|
|
26
26
|
Progress,
|
|
27
27
|
TaskID,
|
|
28
28
|
TextColumn,
|
|
29
|
+
TimeRemainingColumn,
|
|
29
30
|
TransferSpeedColumn,
|
|
30
31
|
)
|
|
31
32
|
|
|
@@ -83,7 +84,8 @@ def _put_stream(
|
|
|
83
84
|
chunk = fp.read(PUT_CHUNK_BYTES)
|
|
84
85
|
if not chunk:
|
|
85
86
|
break
|
|
86
|
-
progress
|
|
87
|
+
if progress is not None and task_id is not None:
|
|
88
|
+
progress.update(task_id, advance=len(chunk))
|
|
87
89
|
yield chunk
|
|
88
90
|
|
|
89
91
|
response = client.put(url, content=_chunk_iter(), headers=headers)
|
|
@@ -95,8 +97,17 @@ def _put_stream(
|
|
|
95
97
|
"Check that the configured PAT matches the account owner.",
|
|
96
98
|
)
|
|
97
99
|
if response.status_code == 413:
|
|
100
|
+
# Prefer the server's reason: it names the configured limit and the
|
|
101
|
+
# env var that raises it (field failure: the generic line gave the
|
|
102
|
+
# operator nothing actionable).
|
|
103
|
+
detail = ""
|
|
104
|
+
try:
|
|
105
|
+
detail = str(response.json().get("error", ""))
|
|
106
|
+
except ValueError:
|
|
107
|
+
detail = response.text[:200]
|
|
108
|
+
suffix = f" ({detail})" if detail else ""
|
|
98
109
|
raise FileTooLargeError(
|
|
99
|
-
f"
|
|
110
|
+
f"{path.name} was rejected by the server as too large.{suffix}",
|
|
100
111
|
)
|
|
101
112
|
if response.status_code >= 400:
|
|
102
113
|
raise map_response_error(response)
|
|
@@ -108,56 +119,69 @@ def upload_objects(
|
|
|
108
119
|
actions: list[BatchAction],
|
|
109
120
|
paths_by_oid: dict[str, list[Path]],
|
|
110
121
|
show_progress: bool = True,
|
|
122
|
+
progress: _ProgressLike | None = None,
|
|
111
123
|
) -> None:
|
|
112
124
|
"""Stream-PUT every `BatchAction` from disk with a Rich progress bar.
|
|
113
125
|
|
|
114
126
|
`paths_by_oid` maps an oid to its one-or-more source paths; the
|
|
115
127
|
first one is used as the byte source (dedup is exact — same sha256
|
|
116
|
-
means same bytes)
|
|
117
|
-
|
|
128
|
+
means same bytes) AND as the human-facing task label — users track
|
|
129
|
+
"weights-00002-of-00004.safetensors", never a sha prefix. The caller
|
|
130
|
+
MUST ensure `actions` and `paths_by_oid` agree on the oid set.
|
|
131
|
+
`progress` lets tests inject a recorder instead of Rich.
|
|
118
132
|
"""
|
|
119
133
|
if not actions:
|
|
120
134
|
return
|
|
135
|
+
# ONE column set for every task row. (The previous layout concatenated
|
|
136
|
+
# a per-object group and a "total" group, so every row rendered both
|
|
137
|
+
# groups and the output read as doubled, unlabeled bars.)
|
|
121
138
|
columns: tuple[Any, ...] = (
|
|
122
139
|
TextColumn("[bold blue]{task.description}[/bold blue]", justify="left"),
|
|
123
|
-
BarColumn(bar_width=
|
|
124
|
-
DownloadColumn(),
|
|
125
|
-
TransferSpeedColumn(),
|
|
126
|
-
)
|
|
127
|
-
overall_columns: tuple[Any, ...] = (
|
|
128
|
-
TextColumn("[bold green]total[/bold green]"),
|
|
129
|
-
BarColumn(bar_width=40),
|
|
140
|
+
BarColumn(bar_width=32),
|
|
130
141
|
DownloadColumn(),
|
|
131
142
|
TransferSpeedColumn(),
|
|
143
|
+
TimeRemainingColumn(),
|
|
132
144
|
)
|
|
133
145
|
total_bytes = sum(a.size for a in actions)
|
|
134
|
-
if
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
146
|
+
if progress is not None:
|
|
147
|
+
_upload_all(
|
|
148
|
+
progress, client, actions=actions, paths_by_oid=paths_by_oid, total_bytes=total_bytes
|
|
149
|
+
)
|
|
150
|
+
return
|
|
151
|
+
progress_cm: Any = Progress(*columns, expand=True) if show_progress else _NullProgress()
|
|
152
|
+
with progress_cm as active:
|
|
153
|
+
_upload_all(
|
|
154
|
+
active, client, actions=actions, paths_by_oid=paths_by_oid, total_bytes=total_bytes
|
|
155
|
+
)
|
|
156
|
+
|
|
157
|
+
|
|
158
|
+
def _upload_all(
|
|
159
|
+
progress: Any,
|
|
160
|
+
client: httpx.Client,
|
|
161
|
+
*,
|
|
162
|
+
actions: list[BatchAction],
|
|
163
|
+
paths_by_oid: dict[str, list[Path]],
|
|
164
|
+
total_bytes: int,
|
|
165
|
+
) -> None:
|
|
166
|
+
overall = progress.add_task(f"Total ({len(actions)} object(s))", total=total_bytes)
|
|
167
|
+
for action in actions:
|
|
168
|
+
sources = paths_by_oid.get(action.oid) or []
|
|
169
|
+
if not sources:
|
|
170
|
+
raise BadResponseError(
|
|
171
|
+
f"LFS action for oid {action.oid} has no source file on disk.",
|
|
157
172
|
)
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
173
|
+
task_id = progress.add_task(sources[0].name, total=action.size)
|
|
174
|
+
_put_stream(
|
|
175
|
+
client,
|
|
176
|
+
url=action.href,
|
|
177
|
+
path=sources[0],
|
|
178
|
+
extra_headers=action.header,
|
|
179
|
+
progress=progress,
|
|
180
|
+
task_id=task_id,
|
|
181
|
+
)
|
|
182
|
+
progress.update(task_id, completed=action.size)
|
|
183
|
+
progress.update(overall, advance=action.size)
|
|
184
|
+
progress.remove_task(task_id)
|
|
161
185
|
|
|
162
186
|
|
|
163
187
|
class _NullProgress:
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
class _Recorder:
|
|
2
|
+
"""Stand-in progress sink capturing task descriptions for assertions."""
|
|
3
|
+
|
|
4
|
+
def __init__(self) -> None:
|
|
5
|
+
self.descriptions: list[str] = []
|
|
6
|
+
self.advances: list[int] = []
|
|
7
|
+
self._next = 0
|
|
8
|
+
|
|
9
|
+
def add_task(self, description: str, *, total: float | None = None) -> int:
|
|
10
|
+
self.descriptions.append(description)
|
|
11
|
+
self._next += 1
|
|
12
|
+
return self._next - 1
|
|
13
|
+
|
|
14
|
+
def update(self, task_id: int, advance: float = 0, **kwargs: object) -> None:
|
|
15
|
+
self.advances.append(int(advance))
|
|
16
|
+
|
|
17
|
+
def remove_task(self, task_id: int) -> None:
|
|
18
|
+
return None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class TestUploadObjectsLabels:
|
|
22
|
+
"""Users track file NAMES, not sha prefixes (the field complaint)."""
|
|
23
|
+
|
|
24
|
+
def test_tasks_are_named_after_files(self, tmp_path, monkeypatch):
|
|
25
|
+
import httpx
|
|
26
|
+
|
|
27
|
+
from outo_models_cli.api.lfs_batch import BatchAction
|
|
28
|
+
from outo_models_cli.api.lfs_upload import upload_objects
|
|
29
|
+
|
|
30
|
+
big = tmp_path / "weights-00001-of-00002.safetensors"
|
|
31
|
+
big.write_bytes(b"x" * 4096)
|
|
32
|
+
action = BatchAction(
|
|
33
|
+
oid="a" * 64, size=big.stat().st_size, href="http://srv/put", header={}
|
|
34
|
+
)
|
|
35
|
+
recorder = _Recorder()
|
|
36
|
+
|
|
37
|
+
def fake_put(client, **kwargs):
|
|
38
|
+
pass
|
|
39
|
+
|
|
40
|
+
monkeypatch.setattr("outo_models_cli.api.lfs_upload._put_stream", fake_put)
|
|
41
|
+
client = httpx.Client()
|
|
42
|
+
try:
|
|
43
|
+
upload_objects(
|
|
44
|
+
client,
|
|
45
|
+
actions=[action],
|
|
46
|
+
paths_by_oid={action.oid: [big]},
|
|
47
|
+
show_progress=False,
|
|
48
|
+
progress=recorder,
|
|
49
|
+
)
|
|
50
|
+
finally:
|
|
51
|
+
client.close()
|
|
52
|
+
assert "weights-00001-of-00002.safetensors" in recorder.descriptions
|
|
53
|
+
assert any(d.startswith("Total (1 object") for d in recorder.descriptions)
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
class TestPartitionHashingProgress:
|
|
57
|
+
def test_show_progress_true_does_not_crash(self, tmp_path):
|
|
58
|
+
from outo_models_cli.api.lfs import partition_files
|
|
59
|
+
|
|
60
|
+
big = tmp_path / "big.bin"
|
|
61
|
+
big.write_bytes(b"y" * 2048)
|
|
62
|
+
small = tmp_path / "small.txt"
|
|
63
|
+
small.write_bytes(b"y")
|
|
64
|
+
partition = partition_files([big, small], cap_bytes=512, show_progress=True)
|
|
65
|
+
assert [f.name for f in partition.small] == ["small.txt"]
|
|
66
|
+
assert partition.large and partition.large[0].path.name == "big.bin"
|
|
67
|
+
|
|
68
|
+
|
|
69
|
+
class Test413SurfacesServerReason:
|
|
70
|
+
def test_put_413_includes_server_detail(self, tmp_path):
|
|
71
|
+
import httpx
|
|
72
|
+
import pytest
|
|
73
|
+
|
|
74
|
+
from outo_models_cli.api.lfs_upload import _put_stream
|
|
75
|
+
from outo_models_cli.errors import FileTooLargeError
|
|
76
|
+
|
|
77
|
+
big = tmp_path / "shard-00001.safetensors"
|
|
78
|
+
big.write_bytes(b"z" * 32)
|
|
79
|
+
transport = httpx.MockTransport(
|
|
80
|
+
lambda req: httpx.Response(
|
|
81
|
+
413,
|
|
82
|
+
json={
|
|
83
|
+
"error": (
|
|
84
|
+
"object exceeds per-object limit 107374182400 bytes "
|
|
85
|
+
"(raise OUTO_LFS_MAX_OBJECT_BYTES ...)"
|
|
86
|
+
)
|
|
87
|
+
},
|
|
88
|
+
)
|
|
89
|
+
)
|
|
90
|
+
client = httpx.Client(transport=transport)
|
|
91
|
+
with pytest.raises(FileTooLargeError) as excinfo:
|
|
92
|
+
_put_stream(
|
|
93
|
+
client,
|
|
94
|
+
url="http://srv/put",
|
|
95
|
+
path=big,
|
|
96
|
+
extra_headers={},
|
|
97
|
+
progress=None,
|
|
98
|
+
task_id=None,
|
|
99
|
+
)
|
|
100
|
+
assert "shard-00001.safetensors" in str(excinfo.value)
|
|
101
|
+
assert "OUTO_LFS_MAX_OBJECT_BYTES" in str(excinfo.value)
|
|
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
|
{outo_models_cli-0.2.0 → outo_models_cli-0.2.2}/src/outo_models_cli/commands/_upload_commit.py
RENAMED
|
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
|
|
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|