together 2.0.0a17__py3-none-any.whl → 2.0.0a19__py3-none-any.whl
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.
- together/_base_client.py +5 -2
- together/_client.py +1 -77
- together/_compat.py +3 -3
- together/_utils/_json.py +35 -0
- together/_version.py +1 -1
- together/lib/cli/api/beta/__init__.py +2 -0
- together/lib/cli/api/beta/jig/__init__.py +52 -0
- together/lib/cli/api/beta/jig/_config.py +170 -0
- together/lib/cli/api/beta/jig/jig.py +664 -0
- together/lib/cli/api/beta/jig/secrets.py +138 -0
- together/lib/cli/api/beta/jig/volumes.py +509 -0
- together/lib/cli/api/endpoints/create.py +7 -3
- together/lib/cli/api/endpoints/hardware.py +38 -7
- together/lib/cli/api/models/upload.py +5 -1
- together/resources/__init__.py +0 -28
- together/resources/beta/__init__.py +14 -0
- together/resources/beta/beta.py +32 -0
- together/resources/beta/clusters/clusters.py +12 -12
- together/resources/beta/clusters/storage.py +10 -10
- together/resources/beta/jig/__init__.py +61 -0
- together/resources/beta/jig/jig.py +1004 -0
- together/resources/beta/jig/queue.py +482 -0
- together/resources/beta/jig/secrets.py +548 -0
- together/resources/beta/jig/volumes.py +514 -0
- together/resources/chat/completions.py +10 -0
- together/resources/endpoints.py +103 -1
- together/resources/models/__init__.py +33 -0
- together/resources/{models.py → models/models.py} +41 -9
- together/resources/models/uploads.py +163 -0
- together/types/__init__.py +2 -4
- together/types/beta/__init__.py +6 -0
- together/types/beta/deployment.py +261 -0
- together/types/beta/deployment_logs.py +11 -0
- together/types/beta/jig/__init__.py +20 -0
- together/types/beta/jig/queue_cancel_params.py +13 -0
- together/types/beta/jig/queue_cancel_response.py +11 -0
- together/types/beta/jig/queue_metrics_params.py +12 -0
- together/types/beta/jig/queue_metrics_response.py +8 -0
- together/types/beta/jig/queue_retrieve_params.py +15 -0
- together/types/beta/jig/queue_retrieve_response.py +35 -0
- together/types/beta/jig/queue_submit_params.py +19 -0
- together/types/beta/jig/queue_submit_response.py +25 -0
- together/types/beta/jig/secret.py +33 -0
- together/types/beta/jig/secret_create_params.py +34 -0
- together/types/beta/jig/secret_list_response.py +16 -0
- together/types/beta/jig/secret_update_params.py +34 -0
- together/types/beta/jig/volume.py +47 -0
- together/types/beta/jig/volume_create_params.py +34 -0
- together/types/beta/jig/volume_list_response.py +16 -0
- together/types/beta/jig/volume_update_params.py +34 -0
- together/types/beta/jig_deploy_params.py +150 -0
- together/types/beta/jig_list_response.py +16 -0
- together/types/beta/jig_retrieve_logs_params.py +12 -0
- together/types/beta/jig_update_params.py +141 -0
- together/types/chat/completion_create_params.py +11 -0
- together/types/{hardware_list_params.py → endpoint_list_hardware_params.py} +2 -2
- together/types/{hardware_list_response.py → endpoint_list_hardware_response.py} +2 -2
- together/types/models/__init__.py +5 -0
- together/types/{job_retrieve_response.py → models/upload_status_response.py} +3 -3
- {together-2.0.0a17.dist-info → together-2.0.0a19.dist-info}/METADATA +15 -14
- {together-2.0.0a17.dist-info → together-2.0.0a19.dist-info}/RECORD +64 -30
- together/resources/hardware.py +0 -181
- together/resources/jobs.py +0 -214
- together/types/job_list_response.py +0 -47
- {together-2.0.0a17.dist-info → together-2.0.0a19.dist-info}/WHEEL +0 -0
- {together-2.0.0a17.dist-info → together-2.0.0a19.dist-info}/entry_points.txt +0 -0
- {together-2.0.0a17.dist-info → together-2.0.0a19.dist-info}/licenses/LICENSE +0 -0
together/_base_client.py
CHANGED
|
@@ -86,6 +86,7 @@ from ._exceptions import (
|
|
|
86
86
|
APIConnectionError,
|
|
87
87
|
APIResponseValidationError,
|
|
88
88
|
)
|
|
89
|
+
from ._utils._json import openapi_dumps
|
|
89
90
|
|
|
90
91
|
log: logging.Logger = logging.getLogger(__name__)
|
|
91
92
|
|
|
@@ -554,8 +555,10 @@ class BaseClient(Generic[_HttpxClientT, _DefaultStreamT]):
|
|
|
554
555
|
kwargs["content"] = options.content
|
|
555
556
|
elif isinstance(json_data, bytes):
|
|
556
557
|
kwargs["content"] = json_data
|
|
557
|
-
|
|
558
|
-
|
|
558
|
+
elif not files:
|
|
559
|
+
# Don't set content when JSON is sent as multipart/form-data,
|
|
560
|
+
# since httpx's content param overrides other body arguments
|
|
561
|
+
kwargs["content"] = openapi_dumps(json_data) if is_given(json_data) and json_data is not None else None
|
|
559
562
|
kwargs["files"] = files
|
|
560
563
|
else:
|
|
561
564
|
headers.pop("Content-Type", None)
|
together/_client.py
CHANGED
|
@@ -37,7 +37,6 @@ if TYPE_CHECKING:
|
|
|
37
37
|
from .resources import (
|
|
38
38
|
beta,
|
|
39
39
|
chat,
|
|
40
|
-
jobs,
|
|
41
40
|
audio,
|
|
42
41
|
evals,
|
|
43
42
|
files,
|
|
@@ -46,22 +45,18 @@ if TYPE_CHECKING:
|
|
|
46
45
|
rerank,
|
|
47
46
|
videos,
|
|
48
47
|
batches,
|
|
49
|
-
hardware,
|
|
50
48
|
endpoints,
|
|
51
49
|
embeddings,
|
|
52
50
|
completions,
|
|
53
51
|
fine_tuning,
|
|
54
52
|
code_interpreter,
|
|
55
53
|
)
|
|
56
|
-
from .resources.jobs import JobsResource, AsyncJobsResource
|
|
57
54
|
from .resources.evals import EvalsResource, AsyncEvalsResource
|
|
58
55
|
from .resources.files import FilesResource, AsyncFilesResource
|
|
59
56
|
from .resources.images import ImagesResource, AsyncImagesResource
|
|
60
|
-
from .resources.models import ModelsResource, AsyncModelsResource
|
|
61
57
|
from .resources.rerank import RerankResource, AsyncRerankResource
|
|
62
58
|
from .resources.videos import VideosResource, AsyncVideosResource
|
|
63
59
|
from .resources.batches import BatchesResource, AsyncBatchesResource
|
|
64
|
-
from .resources.hardware import HardwareResource, AsyncHardwareResource
|
|
65
60
|
from .resources.beta.beta import BetaResource, AsyncBetaResource
|
|
66
61
|
from .resources.chat.chat import ChatResource, AsyncChatResource
|
|
67
62
|
from .resources.endpoints import EndpointsResource, AsyncEndpointsResource
|
|
@@ -69,6 +64,7 @@ if TYPE_CHECKING:
|
|
|
69
64
|
from .resources.audio.audio import AudioResource, AsyncAudioResource
|
|
70
65
|
from .resources.completions import CompletionsResource, AsyncCompletionsResource
|
|
71
66
|
from .resources.fine_tuning import FineTuningResource, AsyncFineTuningResource
|
|
67
|
+
from .resources.models.models import ModelsResource, AsyncModelsResource
|
|
72
68
|
from .resources.code_interpreter.code_interpreter import CodeInterpreterResource, AsyncCodeInterpreterResource
|
|
73
69
|
|
|
74
70
|
__all__ = [
|
|
@@ -209,24 +205,12 @@ class Together(SyncAPIClient):
|
|
|
209
205
|
|
|
210
206
|
return ModelsResource(self)
|
|
211
207
|
|
|
212
|
-
@cached_property
|
|
213
|
-
def jobs(self) -> JobsResource:
|
|
214
|
-
from .resources.jobs import JobsResource
|
|
215
|
-
|
|
216
|
-
return JobsResource(self)
|
|
217
|
-
|
|
218
208
|
@cached_property
|
|
219
209
|
def endpoints(self) -> EndpointsResource:
|
|
220
210
|
from .resources.endpoints import EndpointsResource
|
|
221
211
|
|
|
222
212
|
return EndpointsResource(self)
|
|
223
213
|
|
|
224
|
-
@cached_property
|
|
225
|
-
def hardware(self) -> HardwareResource:
|
|
226
|
-
from .resources.hardware import HardwareResource
|
|
227
|
-
|
|
228
|
-
return HardwareResource(self)
|
|
229
|
-
|
|
230
214
|
@cached_property
|
|
231
215
|
def rerank(self) -> RerankResource:
|
|
232
216
|
from .resources.rerank import RerankResource
|
|
@@ -486,24 +470,12 @@ class AsyncTogether(AsyncAPIClient):
|
|
|
486
470
|
|
|
487
471
|
return AsyncModelsResource(self)
|
|
488
472
|
|
|
489
|
-
@cached_property
|
|
490
|
-
def jobs(self) -> AsyncJobsResource:
|
|
491
|
-
from .resources.jobs import AsyncJobsResource
|
|
492
|
-
|
|
493
|
-
return AsyncJobsResource(self)
|
|
494
|
-
|
|
495
473
|
@cached_property
|
|
496
474
|
def endpoints(self) -> AsyncEndpointsResource:
|
|
497
475
|
from .resources.endpoints import AsyncEndpointsResource
|
|
498
476
|
|
|
499
477
|
return AsyncEndpointsResource(self)
|
|
500
478
|
|
|
501
|
-
@cached_property
|
|
502
|
-
def hardware(self) -> AsyncHardwareResource:
|
|
503
|
-
from .resources.hardware import AsyncHardwareResource
|
|
504
|
-
|
|
505
|
-
return AsyncHardwareResource(self)
|
|
506
|
-
|
|
507
479
|
@cached_property
|
|
508
480
|
def rerank(self) -> AsyncRerankResource:
|
|
509
481
|
from .resources.rerank import AsyncRerankResource
|
|
@@ -709,24 +681,12 @@ class TogetherWithRawResponse:
|
|
|
709
681
|
|
|
710
682
|
return ModelsResourceWithRawResponse(self._client.models)
|
|
711
683
|
|
|
712
|
-
@cached_property
|
|
713
|
-
def jobs(self) -> jobs.JobsResourceWithRawResponse:
|
|
714
|
-
from .resources.jobs import JobsResourceWithRawResponse
|
|
715
|
-
|
|
716
|
-
return JobsResourceWithRawResponse(self._client.jobs)
|
|
717
|
-
|
|
718
684
|
@cached_property
|
|
719
685
|
def endpoints(self) -> endpoints.EndpointsResourceWithRawResponse:
|
|
720
686
|
from .resources.endpoints import EndpointsResourceWithRawResponse
|
|
721
687
|
|
|
722
688
|
return EndpointsResourceWithRawResponse(self._client.endpoints)
|
|
723
689
|
|
|
724
|
-
@cached_property
|
|
725
|
-
def hardware(self) -> hardware.HardwareResourceWithRawResponse:
|
|
726
|
-
from .resources.hardware import HardwareResourceWithRawResponse
|
|
727
|
-
|
|
728
|
-
return HardwareResourceWithRawResponse(self._client.hardware)
|
|
729
|
-
|
|
730
690
|
@cached_property
|
|
731
691
|
def rerank(self) -> rerank.RerankResourceWithRawResponse:
|
|
732
692
|
from .resources.rerank import RerankResourceWithRawResponse
|
|
@@ -818,24 +778,12 @@ class AsyncTogetherWithRawResponse:
|
|
|
818
778
|
|
|
819
779
|
return AsyncModelsResourceWithRawResponse(self._client.models)
|
|
820
780
|
|
|
821
|
-
@cached_property
|
|
822
|
-
def jobs(self) -> jobs.AsyncJobsResourceWithRawResponse:
|
|
823
|
-
from .resources.jobs import AsyncJobsResourceWithRawResponse
|
|
824
|
-
|
|
825
|
-
return AsyncJobsResourceWithRawResponse(self._client.jobs)
|
|
826
|
-
|
|
827
781
|
@cached_property
|
|
828
782
|
def endpoints(self) -> endpoints.AsyncEndpointsResourceWithRawResponse:
|
|
829
783
|
from .resources.endpoints import AsyncEndpointsResourceWithRawResponse
|
|
830
784
|
|
|
831
785
|
return AsyncEndpointsResourceWithRawResponse(self._client.endpoints)
|
|
832
786
|
|
|
833
|
-
@cached_property
|
|
834
|
-
def hardware(self) -> hardware.AsyncHardwareResourceWithRawResponse:
|
|
835
|
-
from .resources.hardware import AsyncHardwareResourceWithRawResponse
|
|
836
|
-
|
|
837
|
-
return AsyncHardwareResourceWithRawResponse(self._client.hardware)
|
|
838
|
-
|
|
839
787
|
@cached_property
|
|
840
788
|
def rerank(self) -> rerank.AsyncRerankResourceWithRawResponse:
|
|
841
789
|
from .resources.rerank import AsyncRerankResourceWithRawResponse
|
|
@@ -927,24 +875,12 @@ class TogetherWithStreamedResponse:
|
|
|
927
875
|
|
|
928
876
|
return ModelsResourceWithStreamingResponse(self._client.models)
|
|
929
877
|
|
|
930
|
-
@cached_property
|
|
931
|
-
def jobs(self) -> jobs.JobsResourceWithStreamingResponse:
|
|
932
|
-
from .resources.jobs import JobsResourceWithStreamingResponse
|
|
933
|
-
|
|
934
|
-
return JobsResourceWithStreamingResponse(self._client.jobs)
|
|
935
|
-
|
|
936
878
|
@cached_property
|
|
937
879
|
def endpoints(self) -> endpoints.EndpointsResourceWithStreamingResponse:
|
|
938
880
|
from .resources.endpoints import EndpointsResourceWithStreamingResponse
|
|
939
881
|
|
|
940
882
|
return EndpointsResourceWithStreamingResponse(self._client.endpoints)
|
|
941
883
|
|
|
942
|
-
@cached_property
|
|
943
|
-
def hardware(self) -> hardware.HardwareResourceWithStreamingResponse:
|
|
944
|
-
from .resources.hardware import HardwareResourceWithStreamingResponse
|
|
945
|
-
|
|
946
|
-
return HardwareResourceWithStreamingResponse(self._client.hardware)
|
|
947
|
-
|
|
948
884
|
@cached_property
|
|
949
885
|
def rerank(self) -> rerank.RerankResourceWithStreamingResponse:
|
|
950
886
|
from .resources.rerank import RerankResourceWithStreamingResponse
|
|
@@ -1036,24 +972,12 @@ class AsyncTogetherWithStreamedResponse:
|
|
|
1036
972
|
|
|
1037
973
|
return AsyncModelsResourceWithStreamingResponse(self._client.models)
|
|
1038
974
|
|
|
1039
|
-
@cached_property
|
|
1040
|
-
def jobs(self) -> jobs.AsyncJobsResourceWithStreamingResponse:
|
|
1041
|
-
from .resources.jobs import AsyncJobsResourceWithStreamingResponse
|
|
1042
|
-
|
|
1043
|
-
return AsyncJobsResourceWithStreamingResponse(self._client.jobs)
|
|
1044
|
-
|
|
1045
975
|
@cached_property
|
|
1046
976
|
def endpoints(self) -> endpoints.AsyncEndpointsResourceWithStreamingResponse:
|
|
1047
977
|
from .resources.endpoints import AsyncEndpointsResourceWithStreamingResponse
|
|
1048
978
|
|
|
1049
979
|
return AsyncEndpointsResourceWithStreamingResponse(self._client.endpoints)
|
|
1050
980
|
|
|
1051
|
-
@cached_property
|
|
1052
|
-
def hardware(self) -> hardware.AsyncHardwareResourceWithStreamingResponse:
|
|
1053
|
-
from .resources.hardware import AsyncHardwareResourceWithStreamingResponse
|
|
1054
|
-
|
|
1055
|
-
return AsyncHardwareResourceWithStreamingResponse(self._client.hardware)
|
|
1056
|
-
|
|
1057
981
|
@cached_property
|
|
1058
982
|
def rerank(self) -> rerank.AsyncRerankResourceWithStreamingResponse:
|
|
1059
983
|
from .resources.rerank import AsyncRerankResourceWithStreamingResponse
|
together/_compat.py
CHANGED
|
@@ -139,6 +139,7 @@ def model_dump(
|
|
|
139
139
|
exclude_defaults: bool = False,
|
|
140
140
|
warnings: bool = True,
|
|
141
141
|
mode: Literal["json", "python"] = "python",
|
|
142
|
+
by_alias: bool | None = None,
|
|
142
143
|
) -> dict[str, Any]:
|
|
143
144
|
if (not PYDANTIC_V1) or hasattr(model, "model_dump"):
|
|
144
145
|
return model.model_dump(
|
|
@@ -148,13 +149,12 @@ def model_dump(
|
|
|
148
149
|
exclude_defaults=exclude_defaults,
|
|
149
150
|
# warnings are not supported in Pydantic v1
|
|
150
151
|
warnings=True if PYDANTIC_V1 else warnings,
|
|
152
|
+
by_alias=by_alias,
|
|
151
153
|
)
|
|
152
154
|
return cast(
|
|
153
155
|
"dict[str, Any]",
|
|
154
156
|
model.dict( # pyright: ignore[reportDeprecated, reportUnnecessaryCast]
|
|
155
|
-
exclude=exclude,
|
|
156
|
-
exclude_unset=exclude_unset,
|
|
157
|
-
exclude_defaults=exclude_defaults,
|
|
157
|
+
exclude=exclude, exclude_unset=exclude_unset, exclude_defaults=exclude_defaults, by_alias=bool(by_alias)
|
|
158
158
|
),
|
|
159
159
|
)
|
|
160
160
|
|
together/_utils/_json.py
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import json
|
|
2
|
+
from typing import Any
|
|
3
|
+
from datetime import datetime
|
|
4
|
+
from typing_extensions import override
|
|
5
|
+
|
|
6
|
+
import pydantic
|
|
7
|
+
|
|
8
|
+
from .._compat import model_dump
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
def openapi_dumps(obj: Any) -> bytes:
|
|
12
|
+
"""
|
|
13
|
+
Serialize an object to UTF-8 encoded JSON bytes.
|
|
14
|
+
|
|
15
|
+
Extends the standard json.dumps with support for additional types
|
|
16
|
+
commonly used in the SDK, such as `datetime`, `pydantic.BaseModel`, etc.
|
|
17
|
+
"""
|
|
18
|
+
return json.dumps(
|
|
19
|
+
obj,
|
|
20
|
+
cls=_CustomEncoder,
|
|
21
|
+
# Uses the same defaults as httpx's JSON serialization
|
|
22
|
+
ensure_ascii=False,
|
|
23
|
+
separators=(",", ":"),
|
|
24
|
+
allow_nan=False,
|
|
25
|
+
).encode()
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
class _CustomEncoder(json.JSONEncoder):
|
|
29
|
+
@override
|
|
30
|
+
def default(self, o: Any) -> Any:
|
|
31
|
+
if isinstance(o, datetime):
|
|
32
|
+
return o.isoformat()
|
|
33
|
+
if isinstance(o, pydantic.BaseModel):
|
|
34
|
+
return model_dump(o, exclude_unset=True, mode="json", by_alias=True)
|
|
35
|
+
return super().default(o)
|
together/_version.py
CHANGED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
"""Jig CLI - deployment tool for Together AI."""
|
|
2
|
+
|
|
3
|
+
import click
|
|
4
|
+
|
|
5
|
+
from together.lib.cli.api.beta.jig.jig import (
|
|
6
|
+
init,
|
|
7
|
+
logs,
|
|
8
|
+
push,
|
|
9
|
+
build,
|
|
10
|
+
deploy,
|
|
11
|
+
status,
|
|
12
|
+
endpoint,
|
|
13
|
+
submit,
|
|
14
|
+
destroy,
|
|
15
|
+
dockerfile,
|
|
16
|
+
job_status,
|
|
17
|
+
queue_status,
|
|
18
|
+
list_deployments,
|
|
19
|
+
)
|
|
20
|
+
from together.lib.cli.api.beta.jig.secrets import secrets
|
|
21
|
+
from together.lib.cli.api.beta.jig.volumes import volumes
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
@click.group()
|
|
25
|
+
@click.pass_context
|
|
26
|
+
def jig(ctx: click.Context) -> None:
|
|
27
|
+
"""Jig commands - deploy and manage containers"""
|
|
28
|
+
pass
|
|
29
|
+
|
|
30
|
+
|
|
31
|
+
def add_commands(parent: click.Group):
|
|
32
|
+
# Add subgroups
|
|
33
|
+
parent.add_command(secrets)
|
|
34
|
+
parent.add_command(volumes)
|
|
35
|
+
|
|
36
|
+
# Add main commands
|
|
37
|
+
parent.add_command(init)
|
|
38
|
+
parent.add_command(dockerfile)
|
|
39
|
+
parent.add_command(build)
|
|
40
|
+
parent.add_command(push)
|
|
41
|
+
parent.add_command(deploy)
|
|
42
|
+
parent.add_command(status)
|
|
43
|
+
parent.add_command(endpoint)
|
|
44
|
+
parent.add_command(logs)
|
|
45
|
+
parent.add_command(destroy)
|
|
46
|
+
parent.add_command(submit)
|
|
47
|
+
parent.add_command(job_status)
|
|
48
|
+
parent.add_command(queue_status)
|
|
49
|
+
parent.add_command(list_deployments)
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
add_commands(jig)
|
|
@@ -0,0 +1,170 @@
|
|
|
1
|
+
"""Configuration and state management for jig CLI."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import os
|
|
6
|
+
import sys
|
|
7
|
+
import json
|
|
8
|
+
from typing import TYPE_CHECKING, Any, Optional
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from dataclasses import field, asdict, dataclass
|
|
11
|
+
|
|
12
|
+
import click
|
|
13
|
+
|
|
14
|
+
if TYPE_CHECKING:
|
|
15
|
+
import tomli as tomllib
|
|
16
|
+
else:
|
|
17
|
+
try:
|
|
18
|
+
import tomllib
|
|
19
|
+
except ImportError:
|
|
20
|
+
import tomli as tomllib
|
|
21
|
+
|
|
22
|
+
# --- Environment Configuration ---
|
|
23
|
+
|
|
24
|
+
DEBUG = os.getenv("TOGETHER_DEBUG", "").strip()[:1] in ("y", "1", "t")
|
|
25
|
+
|
|
26
|
+
UPLOAD_CONCURRENCY_LIMIT = int(os.getenv("TOGETHER_UPLOAD_CONCURRENCY", "15"))
|
|
27
|
+
MULTIPART_CHUNK_SIZE_MB = int(os.getenv("TOGETHER_MULTIPART_CHUNK_SIZE_MB", "20"))
|
|
28
|
+
MULTIPART_THRESHOLD_MB = int(os.getenv("TOGETHER_MULTIPART_THRESHOLD_MB", "100"))
|
|
29
|
+
MAX_UPLOAD_RETRIES = 3
|
|
30
|
+
|
|
31
|
+
# Warmup configuration (for torch compile cache)
|
|
32
|
+
WARMUP_ENV_NAME = os.getenv("WARMUP_ENV_NAME", "TORCHINDUCTOR_CACHE_DIR")
|
|
33
|
+
WARMUP_DEST = os.getenv("WARMUP_DEST", "torch_cache")
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
# --- Configuration Dataclasses ---
|
|
37
|
+
|
|
38
|
+
|
|
39
|
+
@dataclass
|
|
40
|
+
class ImageConfig:
|
|
41
|
+
"""Container image configuration from pyproject.toml"""
|
|
42
|
+
|
|
43
|
+
python_version: str = "3.11"
|
|
44
|
+
system_packages: list[str] = field(default_factory=list[str])
|
|
45
|
+
environment: dict[str, str] = field(default_factory=dict[str, str])
|
|
46
|
+
run: list[str] = field(default_factory=list[str])
|
|
47
|
+
cmd: str = "python app.py"
|
|
48
|
+
copy: list[str] = field(default_factory=list[str])
|
|
49
|
+
auto_include_git: bool = False
|
|
50
|
+
|
|
51
|
+
@classmethod
|
|
52
|
+
def from_dict(cls, data: dict[str, Any]) -> ImageConfig:
|
|
53
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class DeployConfig:
|
|
58
|
+
"""Deployment configuration"""
|
|
59
|
+
|
|
60
|
+
description: str = ""
|
|
61
|
+
gpu_type: str = "h100-80gb"
|
|
62
|
+
gpu_count: int = 1
|
|
63
|
+
cpu: float = 1
|
|
64
|
+
memory: float = 8
|
|
65
|
+
storage: int = 100
|
|
66
|
+
min_replicas: int = 1
|
|
67
|
+
max_replicas: int = 1
|
|
68
|
+
port: int = 8000
|
|
69
|
+
environment_variables: dict[str, str] = field(default_factory=dict[str, str])
|
|
70
|
+
command: Optional[list[str]] = None
|
|
71
|
+
autoscaling: dict[str, str] = field(default_factory=dict[str, str])
|
|
72
|
+
health_check_path: str = "/health"
|
|
73
|
+
termination_grace_period_seconds: int = 300
|
|
74
|
+
|
|
75
|
+
@classmethod
|
|
76
|
+
def from_dict(cls, data: dict[str, Any]) -> DeployConfig:
|
|
77
|
+
return cls(**{k: v for k, v in data.items() if k in cls.__annotations__})
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
@dataclass
|
|
81
|
+
class Config:
|
|
82
|
+
"""Main configuration from jig.toml or pyproject.toml"""
|
|
83
|
+
|
|
84
|
+
model_name: str = ""
|
|
85
|
+
dockerfile: str = "Dockerfile"
|
|
86
|
+
image: ImageConfig = field(default_factory=ImageConfig)
|
|
87
|
+
deploy: DeployConfig = field(default_factory=DeployConfig)
|
|
88
|
+
_path: Path = field(default_factory=lambda: Path("pyproject.toml"))
|
|
89
|
+
|
|
90
|
+
@classmethod
|
|
91
|
+
def find(cls, config_path: Optional[str] = None, init: bool = False) -> Config:
|
|
92
|
+
"""Find specified config_path, pyproject.toml, or jig.toml"""
|
|
93
|
+
if config_path:
|
|
94
|
+
found_path = Path(config_path)
|
|
95
|
+
if not found_path.exists():
|
|
96
|
+
click.echo(f"ERROR: Configuration file not found: {config_path}", err=True)
|
|
97
|
+
sys.exit(1)
|
|
98
|
+
return cls.load(tomllib.load(found_path.open("rb")), found_path)
|
|
99
|
+
|
|
100
|
+
if (jigfile := Path("jig.toml")).exists():
|
|
101
|
+
return cls.load(tomllib.load(jigfile.open("rb")), jigfile)
|
|
102
|
+
|
|
103
|
+
if (pyproject_path := Path("pyproject.toml")).exists():
|
|
104
|
+
data = tomllib.load(pyproject_path.open("rb"))
|
|
105
|
+
if "tool" in data and "jig" in data["tool"]:
|
|
106
|
+
return cls.load(data, pyproject_path)
|
|
107
|
+
|
|
108
|
+
if init:
|
|
109
|
+
return cls()
|
|
110
|
+
click.echo(
|
|
111
|
+
"ERROR: No pyproject.toml or jig.toml found, use --config to specify a config path.",
|
|
112
|
+
err=True,
|
|
113
|
+
)
|
|
114
|
+
sys.exit(1)
|
|
115
|
+
|
|
116
|
+
@classmethod
|
|
117
|
+
def load(cls, data: dict[str, Any], path: Path) -> Config:
|
|
118
|
+
"""Load configuration from parsed TOML data"""
|
|
119
|
+
is_pyproject = path.name == "pyproject.toml"
|
|
120
|
+
|
|
121
|
+
jig_config = data.get("tool", {}).get("jig", {}) if is_pyproject else data
|
|
122
|
+
|
|
123
|
+
name = jig_config.get("name")
|
|
124
|
+
if name is None:
|
|
125
|
+
if is_pyproject:
|
|
126
|
+
name = data.get("project", {}).get("name", "")
|
|
127
|
+
else:
|
|
128
|
+
name = path.resolve().parent.name
|
|
129
|
+
click.echo(f"\N{PACKAGE} Name not set in config file or pyproject.toml - defaulting to {name}")
|
|
130
|
+
|
|
131
|
+
if autoscaling := jig_config.get("autoscaling", {}):
|
|
132
|
+
autoscaling["model"] = name
|
|
133
|
+
jig_config["deploy"]["autoscaling"] = autoscaling
|
|
134
|
+
|
|
135
|
+
return cls(
|
|
136
|
+
image=ImageConfig.from_dict(jig_config.get("image", {})),
|
|
137
|
+
deploy=DeployConfig.from_dict(jig_config.get("deploy", {})),
|
|
138
|
+
dockerfile=jig_config.get("dockerfile", "Dockerfile"),
|
|
139
|
+
model_name=name,
|
|
140
|
+
_path=path,
|
|
141
|
+
)
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
# --- State Management ---
|
|
145
|
+
|
|
146
|
+
|
|
147
|
+
@dataclass
|
|
148
|
+
class State:
|
|
149
|
+
"""Persistent state stored in .jig.json"""
|
|
150
|
+
|
|
151
|
+
_config_dir: Path
|
|
152
|
+
registry_base_path: str = ""
|
|
153
|
+
secrets: dict[str, str] = field(default_factory=dict[str, str])
|
|
154
|
+
volumes: dict[str, str] = field(default_factory=dict[str, str])
|
|
155
|
+
|
|
156
|
+
@classmethod
|
|
157
|
+
def load(cls, config_dir: Path) -> State:
|
|
158
|
+
path = config_dir / ".jig.json"
|
|
159
|
+
try:
|
|
160
|
+
with open(path) as f:
|
|
161
|
+
data = {k: v for k, v in json.load(f).items() if k in cls.__annotations__ and not k.startswith("_")}
|
|
162
|
+
return cls(_config_dir=config_dir, **data)
|
|
163
|
+
except FileNotFoundError:
|
|
164
|
+
return cls(_config_dir=config_dir)
|
|
165
|
+
|
|
166
|
+
def save(self) -> None:
|
|
167
|
+
path = self._config_dir / ".jig.json"
|
|
168
|
+
data = {k: v for k, v in asdict(self).items() if not k.startswith("_")}
|
|
169
|
+
with open(path, "w") as f:
|
|
170
|
+
json.dump(data, f, indent=2)
|