outo-models-cli 0.2.2__tar.gz → 0.2.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.
Files changed (50) hide show
  1. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/PKG-INFO +1 -1
  2. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/pyproject.toml +1 -1
  3. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/api/lfs_batch.py +2 -0
  4. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/api/lfs_upload.py +2 -1
  5. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/api/upload.py +2 -0
  6. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/errors.py +12 -4
  7. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/http.py +13 -0
  8. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_errors.py +23 -0
  9. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/uv.lock +1 -1
  10. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/.gitignore +0 -0
  11. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/LICENSE +0 -0
  12. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/README.md +0 -0
  13. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/__init__.py +0 -0
  14. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/__main__.py +0 -0
  15. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/api/__init__.py +0 -0
  16. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/api/auth.py +0 -0
  17. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/api/lfs.py +0 -0
  18. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/api/repos.py +0 -0
  19. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/__init__.py +0 -0
  20. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/_shared.py +0 -0
  21. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/_upload_commit.py +0 -0
  22. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/auth.py +0 -0
  23. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/download.py +0 -0
  24. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/ls.py +0 -0
  25. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/repo.py +0 -0
  26. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/commands/upload.py +0 -0
  27. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/config.py +0 -0
  28. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/downloader/__init__.py +0 -0
  29. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/downloader/transfer.py +0 -0
  30. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/downloader/walk.py +0 -0
  31. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/main.py +0 -0
  32. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/src/outo_models_cli/matchers.py +0 -0
  33. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/conftest.py +0 -0
  34. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_api_crud.py +0 -0
  35. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_api_upload.py +0 -0
  36. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_cli_auth.py +0 -0
  37. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_cli_errors.py +0 -0
  38. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_cli_help.py +0 -0
  39. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_cli_repo.py +0 -0
  40. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_cli_upload_lfs.py +0 -0
  41. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_cli_xfer.py +0 -0
  42. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_config.py +0 -0
  43. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_download_lfs.py +0 -0
  44. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_download_repo.py +0 -0
  45. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_download_transfer.py +0 -0
  46. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_download_walk.py +0 -0
  47. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_lfs.py +0 -0
  48. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_lfs_upload.py +0 -0
  49. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/tests/test_matchers.py +0 -0
  50. {outo_models_cli-0.2.2 → outo_models_cli-0.2.3}/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.2
3
+ Version: 0.2.3
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
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "outo-models-cli"
3
- version = "0.2.2"
3
+ version = "0.2.3"
4
4
  description = "Command-line client for self-hosted outo-models servers — auth, repo, download, and upload."
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.12,<3.13"
@@ -23,6 +23,7 @@ import httpx
23
23
  from outo_models_cli.api import send, unwrap
24
24
  from outo_models_cli.api.lfs import LFS_CONTENT_TYPE
25
25
  from outo_models_cli.errors import BadResponseError, map_response_error
26
+ from outo_models_cli.http import TIMEOUT_BATCH
26
27
 
27
28
 
28
29
  @dataclass(frozen=True, slots=True)
@@ -72,6 +73,7 @@ def batch_upload(
72
73
  f"/{owner}/{name}.git/info/lfs/objects/batch",
73
74
  headers={"Accept": LFS_CONTENT_TYPE, "Content-Type": LFS_CONTENT_TYPE},
74
75
  json=body,
76
+ timeout=TIMEOUT_BATCH,
75
77
  )
76
78
  if response.status_code == 404:
77
79
  raise BadResponseError(
@@ -37,6 +37,7 @@ from outo_models_cli.errors import (
37
37
  FileTooLargeError,
38
38
  map_response_error,
39
39
  )
40
+ from outo_models_cli.http import TIMEOUT_PUT
40
41
 
41
42
 
42
43
  class _ProgressLike(Protocol):
@@ -88,7 +89,7 @@ def _put_stream(
88
89
  progress.update(task_id, advance=len(chunk))
89
90
  yield chunk
90
91
 
91
- response = client.put(url, content=_chunk_iter(), headers=headers)
92
+ response = client.put(url, content=_chunk_iter(), headers=headers, timeout=TIMEOUT_PUT)
92
93
  if response.status_code == 404:
93
94
  raise BadResponseError("LFS object endpoint returned 404.")
94
95
  if response.status_code == 401:
@@ -15,6 +15,7 @@ from outo_models_cli.api import (
15
15
  unwrap,
16
16
  )
17
17
  from outo_models_cli.errors import BadResponseError, map_response_error
18
+ from outo_models_cli.http import TIMEOUT_COMMIT
18
19
 
19
20
 
20
21
  def upload(
@@ -84,6 +85,7 @@ def upload(
84
85
  f"/api/repos/{owner}/{name}/upload",
85
86
  files=multipart_files,
86
87
  data=fields,
88
+ timeout=TIMEOUT_COMMIT,
87
89
  )
88
90
  if response.status_code >= 400:
89
91
  raise map_response_error(response)
@@ -130,12 +130,20 @@ def map_response_error(response: httpx.Response, *, fallback: str | None = None)
130
130
 
131
131
 
132
132
  def map_transport_error(exc: httpx.HTTPError) -> OmcError:
133
- """Wrap an `httpx.HTTPError` as `ServerUnreachableError`.
133
+ """Wrap an `httpx.HTTPError` as a stable, actionable CLI error.
134
134
 
135
- Used by the API client so transport-layer failures (DNS, refusal,
136
- timeouts, TLS) surface with a single stable message instead of leaking
137
- the underlying class name into the operator's terminal.
135
+ Timeouts get their own message (field failure: a slow-but-working
136
+ server was reported as "cannot reach", sending the operator hunting
137
+ for outages). A timeout often means the server is STILL processing —
138
+ retrying a commit can duplicate work, so tell the user to check
139
+ first.
138
140
  """
141
+ if isinstance(exc, httpx.TimeoutException):
142
+ return ServerUnreachableError(
143
+ "The server did not respond in time. It may still be processing — "
144
+ "verify with `omc ls` before retrying, then use a longer timeout "
145
+ "if it keeps happening.",
146
+ )
139
147
  return ServerUnreachableError(
140
148
  "Cannot reach the server. Check the URL, network, and that the server is running.",
141
149
  )
@@ -19,6 +19,16 @@ import httpx
19
19
  # the connection for a few seconds. Anything longer is genuinely hung.
20
20
  _TIMEOUT = httpx.Timeout(30.0, connect=5.0)
21
21
 
22
+ # Long operations need long read timeouts. A multipart commit after a
23
+ # multi-hundred-GiB LFS session does real server-side work (worktree
24
+ # clone, fetch, quota, audit) that can exceed 30 s — the field failure
25
+ # surfaced as "Cannot reach the server" while the server was alive and
26
+ # working. Writes stay unlimited: the transport buffers, so a slow disk
27
+ # on either side must not abort a 3 GiB PUT mid-stream.
28
+ TIMEOUT_BATCH = httpx.Timeout(120.0, connect=5.0)
29
+ TIMEOUT_COMMIT = httpx.Timeout(600.0, connect=5.0)
30
+ TIMEOUT_PUT = httpx.Timeout(None, connect=10.0, read=900.0)
31
+
22
32
 
23
33
  def build_client(
24
34
  base_url: str,
@@ -138,6 +148,9 @@ def build_basic_async_client(
138
148
 
139
149
 
140
150
  __all__ = [
151
+ "TIMEOUT_BATCH",
152
+ "TIMEOUT_COMMIT",
153
+ "TIMEOUT_PUT",
141
154
  "basic_auth_header",
142
155
  "build_async_client",
143
156
  "build_basic_async_client",
@@ -105,3 +105,26 @@ def test_every_omc_error_has_stable_code() -> None:
105
105
  assert ValidationFailedError.code == "validation_failed"
106
106
  assert ServerUnreachableError.code == "server_unreachable"
107
107
  assert BadResponseError.code == "bad_response"
108
+
109
+
110
+ class TestTimeoutMessageSplit:
111
+ """A working-but-slow server is NOT 'unreachable' (field failure)."""
112
+
113
+ def test_timeout_gets_its_own_message(self):
114
+ import httpx
115
+
116
+ from outo_models_cli.errors import map_transport_error
117
+
118
+ exc = httpx.ReadTimeout("took too long")
119
+ err = map_transport_error(exc)
120
+ assert "did not respond in time" in str(err)
121
+ assert "Cannot reach" not in str(err)
122
+
123
+ def test_connect_error_keeps_unreachable_message(self):
124
+ import httpx
125
+
126
+ from outo_models_cli.errors import map_transport_error
127
+
128
+ exc = httpx.ConnectError("refused")
129
+ err = map_transport_error(exc)
130
+ assert "Cannot reach the server" in str(err)
@@ -227,7 +227,7 @@ wheels = [
227
227
 
228
228
  [[package]]
229
229
  name = "outo-models-cli"
230
- version = "0.2.2"
230
+ version = "0.2.3"
231
231
  source = { editable = "." }
232
232
  dependencies = [
233
233
  { name = "httpx" },
File without changes