Skip to content

Commit f270535

Browse files
stainless-app[bot]blainekastenartek0chumaktechnilloguembrostami
authored
release: 2.1.0 (#246)
* Update README.md (#245) * Add from_checkpoint parameter to price estimation for FT Job creation (#247) * codegen metadata * codegen metadata * codegen metadata * jig papercuts (#238) * feat(jig): show a unique name tip when deployment create fails * feat(jig): factor out config_path option and add short flag * feat: jig support for multi deployment * codegen metadata * chore: Update descriptions for jig queue methods and properties * chore(internal): bump dependencies * Allow tool calls through together-py and remove alternating roles check (#244) * Update README.md (#245) * Add from_checkpoint parameter to price estimation for FT Job creation (#247) * codegen metadata * codegen metadata * codegen metadata * Allow tool calls through together-py and remove outdated checks * Remove outdated tests * lint * Enforce that either content or tool calls exists * Skip for empty content * Condition field checks on role --------- Co-authored-by: Blaine Kasten <blainekasten@gmail.com> Co-authored-by: Artem Chumachenko <artek.chumak@gmail.com> Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> * make submit --watch signal status through exit code * fix(cli): fine-tuning retrieve now renders data instead of schema (#250) * fix(jig): lint errors * fix(jig): migrate old state files properly and be even more defensive about parsing deploy errors * fix(jig): pyright does not handle isinstance type narrowing in ternary expressions. also fix migration logic * format code * release: 2.1.0 --------- Co-authored-by: Blaine Kasten <blainekasten@gmail.com> Co-authored-by: Artem Chumachenko <artek.chumak@gmail.com> Co-authored-by: stainless-app[bot] <142633134+stainless-app[bot]@users.noreply.github.com> Co-authored-by: technillogue <technillogue@gmail.com> Co-authored-by: Mohamad Rostami <mbrostami@proton.me> Co-authored-by: Conner Manuel <57027354+connermanuel@users.noreply.github.com>
1 parent 8f2c60c commit f270535

28 files changed

+926
-761
lines changed

.release-please-manifest.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
{
2-
".": "2.0.0"
2+
".": "2.1.0"
33
}

.stats.yml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
configured_endpoints: 74
2-
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai%2Ftogetherai-dc45695614158674dec4da8ae843a7564905f24d2ce577e8e6e5246b4a7b0f61.yml
3-
openapi_spec_hash: 46a91a84c8c270792676ee863b33ab99
2+
openapi_spec_url: https://storage.googleapis.com/stainless-sdk-openapi-specs/togetherai%2Ftogetherai-d0664effad76cfb3a97290815ced94d0766121ee8f0ac137ecc18bd8b5f9c7e8.yml
3+
openapi_spec_hash: b56c7850a7a3d3827ab65c6f0aebe394
44
config_hash: 67b76d1064bef2e591cadf50de08ad19

CHANGELOG.md

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,29 @@
11
# Changelog
22

3+
## 2.1.0 (2026-02-10)
4+
5+
Full Changelog: [v2.0.0...v2.1.0](https://github.com/togethercomputer/together-py/compare/v2.0.0...v2.1.0)
6+
7+
### Features
8+
9+
* **cli:** improve error messages for endpoint creation failures ([#230](https://github.com/togethercomputer/together-py/issues/230)) ([0285a69](https://github.com/togethercomputer/together-py/commit/0285a69893688938068d235aa109a5d7678cb713))
10+
* jig support for multi deployment ([d1165fd](https://github.com/togethercomputer/together-py/commit/d1165fd786533e37ed401f4bb60601b39473a5d8))
11+
12+
13+
### Bug Fixes
14+
15+
* **cli:** fine-tuning retrieve now renders data instead of schema ([#250](https://github.com/togethercomputer/together-py/issues/250)) ([52cde25](https://github.com/togethercomputer/together-py/commit/52cde258d39644a5a9706fc8f491c951115aa16d))
16+
* **jig:** lint errors ([07f4d34](https://github.com/togethercomputer/together-py/commit/07f4d340ebd24247d0d280ce53cac768f76e03b8))
17+
* **jig:** migrate old state files properly and be even more defensive about parsing deploy errors ([92ef79b](https://github.com/togethercomputer/together-py/commit/92ef79b4229953d8e463bebe8027420c8e8decfe))
18+
* **jig:** pyright does not handle isinstance type narrowing in ternary expressions. also fix migration logic ([bf5267f](https://github.com/togethercomputer/together-py/commit/bf5267ff65fa409c21d3ac92e794dfd83f70c300))
19+
* remove hardcoded API key from image example ([#254](https://github.com/togethercomputer/together-py/issues/254)) ([8f2c60c](https://github.com/togethercomputer/together-py/commit/8f2c60c8fa4c2352e5768aa47876dd51b379df6b))
20+
21+
22+
### Chores
23+
24+
* **internal:** bump dependencies ([c9678ff](https://github.com/togethercomputer/together-py/commit/c9678ff922fa549261c5db2af5be60f719e6a1cf))
25+
* Update descriptions for jig queue methods and properties ([23be158](https://github.com/togethercomputer/together-py/commit/23be1581cf1986bf4e9474e70f6e1029bc082ec4))
26+
327
## 2.0.0 (2026-02-04)
428

529
Full Changelog: [v2.0.0-alpha.20...v2.0.0](https://github.com/togethercomputer/together-py/compare/v2.0.0-alpha.20...v2.0.0)

README.md

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,11 @@ The REST API documentation can be found on [docs.together.ai](https://docs.toget
1616
## Installation
1717

1818
```sh
19-
# install from PyPI
20-
pip install '--pre together'
19+
pip install together
20+
```
21+
22+
```sh
23+
uv add together
2124
```
2225

2326
## Usage

pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
[project]
22
name = "together"
3-
version = "2.0.0"
3+
version = "2.1.0"
44
description = "The official Python library for the together API"
55
dynamic = ["readme"]
66
license = "Apache-2.0"

requirements-dev.lock

Lines changed: 19 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
-e .
44
aiohappyeyeballs==2.6.1
55
# via aiohttp
6-
aiohttp==3.13.2
6+
aiohttp==3.13.3
77
aiosignal==1.4.0
88
# via aiohttp
99
annotated-types==0.7.0
1010
# via pydantic
11-
anyio==4.12.0
11+
anyio==4.12.1
1212
# via
1313
# httpx
1414
# together
@@ -18,7 +18,7 @@ attrs==25.4.0
1818
# via aiohttp
1919
backports-asyncio-runner==1.2.0 ; python_full_version < '3.11'
2020
# via pytest-asyncio
21-
certifi==2025.11.12
21+
certifi==2026.1.4
2222
# via
2323
# httpcore
2424
# httpx
@@ -42,7 +42,7 @@ execnet==2.1.2
4242
# via pytest-xdist
4343
filelock==3.19.1 ; python_full_version < '3.10'
4444
# via together
45-
filelock==3.20.0 ; python_full_version >= '3.10'
45+
filelock==3.20.3 ; python_full_version >= '3.10'
4646
# via together
4747
frozenlist==1.8.0
4848
# via
@@ -61,7 +61,7 @@ idna==3.11
6161
# anyio
6262
# httpx
6363
# yarl
64-
importlib-metadata==8.7.0
64+
importlib-metadata==8.7.1
6565
iniconfig==2.1.0 ; python_full_version < '3.10'
6666
# via pytest
6767
iniconfig==2.3.0 ; python_full_version >= '3.10'
@@ -79,15 +79,15 @@ multidict==6.7.0
7979
mypy==1.17.0
8080
mypy-extensions==1.1.0
8181
# via mypy
82-
nodeenv==1.9.1
82+
nodeenv==1.10.0
8383
# via pyright
8484
packaging==25.0
8585
# via pytest
86-
pathspec==0.12.1
86+
pathspec==1.0.3
8787
# via mypy
8888
pillow==11.3.0 ; python_full_version < '3.10'
8989
# via together
90-
pillow==12.0.0 ; python_full_version >= '3.10'
90+
pillow==12.1.0 ; python_full_version >= '3.10'
9191
# via together
9292
pluggy==1.6.0
9393
# via pytest
@@ -109,7 +109,7 @@ pytest==8.4.2 ; python_full_version < '3.10'
109109
# pytest-asyncio
110110
# pytest-mock
111111
# pytest-xdist
112-
pytest==9.0.1 ; python_full_version >= '3.10'
112+
pytest==9.0.2 ; python_full_version >= '3.10'
113113
# via
114114
# pytest-asyncio
115115
# pytest-mock
@@ -123,26 +123,29 @@ python-dateutil==2.9.0.post0 ; python_full_version < '3.10'
123123
respx==0.22.0
124124
rich==14.2.0
125125
# via together
126-
ruff==0.14.7
126+
ruff==0.14.13
127127
six==1.17.0 ; python_full_version < '3.10'
128128
# via python-dateutil
129129
sniffio==1.3.1
130130
# via together
131131
tabulate==0.9.0
132132
# via together
133133
time-machine==2.19.0 ; python_full_version < '3.10'
134-
time-machine==3.1.0 ; python_full_version >= '3.10'
135-
tomli==2.3.0 ; python_full_version < '3.11'
134+
time-machine==3.2.0 ; python_full_version >= '3.10'
135+
tomli==2.4.0 ; python_full_version < '3.11'
136136
# via
137137
# mypy
138138
# pytest
139-
tqdm==4.67.1
139+
# together
140+
tqdm==4.67.3
141+
# via together
142+
types-pyyaml==6.0.12.20250915
140143
# via together
141-
types-requests==2.32.4.20250913
144+
types-requests==2.32.4.20260107
142145
# via types-tqdm
143146
types-tabulate==0.9.0.20241207
144147
# via together
145-
types-tqdm==4.67.0.20250809
148+
types-tqdm==4.67.3.20260205
146149
# via together
147150
typing-extensions==4.15.0
148151
# via
@@ -159,7 +162,7 @@ typing-extensions==4.15.0
159162
# typing-inspection
160163
typing-inspection==0.4.2
161164
# via pydantic
162-
urllib3==2.5.0
165+
urllib3==2.6.3
163166
# via types-requests
164167
yarl==1.22.0
165168
# via aiohttp

src/together/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
# File generated from our OpenAPI spec by Stainless. See CONTRIBUTING.md for details.
22

33
__title__ = "together"
4-
__version__ = "2.0.0" # x-release-please-version
4+
__version__ = "2.1.0" # x-release-please-version

src/together/lib/cli/api/beta/jig/_config.py

Lines changed: 71 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@ class Config:
102102
image: ImageConfig = field(default_factory=ImageConfig)
103103
deploy: DeployConfig = field(default_factory=DeployConfig)
104104
_path: Path = field(default_factory=lambda: Path("pyproject.toml"))
105+
_unique_name_tip: str = "Update project.name in pyproject.toml"
105106

106107
@classmethod
107108
def find(cls, config_path: Optional[str] = None, init: bool = False) -> Config:
@@ -132,17 +133,26 @@ def find(cls, config_path: Optional[str] = None, init: bool = False) -> Config:
132133
@classmethod
133134
def load(cls, data: dict[str, Any], path: Path) -> Config:
134135
"""Load configuration from parsed TOML data"""
135-
is_pyproject = path.name == "pyproject.toml"
136-
137-
jig_config = data.get("tool", {}).get("jig", {}) if is_pyproject else data
138-
139-
name = jig_config.get("name")
140-
if name is None:
141-
if is_pyproject:
142-
name = data.get("project", {}).get("name", "")
136+
# figure out config location and "Deployment name must be unique. Tip: update ..." message
137+
is_pyproject = path.name.endswith("pyproject.toml")
138+
if is_pyproject:
139+
jig_config = data.get("tool", {}).get("jig", {})
140+
if name := jig_config.get("name"):
141+
tip = "update `name` in your pyproject.toml"
142+
elif name := data.get("project", {}).get("name", ""):
143+
tip = "update `project.name` in your pyproject.toml"
144+
else:
145+
name = path.resolve().parent.name
146+
tip = "rename your folder or add `project.name` to your pyproject.toml"
147+
click.echo(f"\N{PACKAGE} Name not set in {path} - defaulting to {name}")
148+
else:
149+
jig_config = data
150+
if name := jig_config.get("name"):
151+
tip = "update `name` in {path}"
143152
else:
144153
name = path.resolve().parent.name
145-
click.echo(f"\N{PACKAGE} Name not set in config file or pyproject.toml - defaulting to {name}")
154+
tip = f"rename your folder or add `name` to {path}"
155+
click.echo(f"\N{PACKAGE} Name not set in {path} - defaulting to {name}")
146156

147157
if autoscaling := jig_config.get("autoscaling", {}):
148158
autoscaling["model"] = name
@@ -157,6 +167,7 @@ def load(cls, data: dict[str, Any], path: Path) -> Config:
157167
dockerfile=jig_config.get("dockerfile", "Dockerfile"),
158168
model_name=name,
159169
_path=path,
170+
_unique_name_tip=tip,
160171
)
161172

162173

@@ -168,22 +179,67 @@ class State:
168179
"""Persistent state stored in .jig.json"""
169180

170181
_config_dir: Path
182+
_project_name: str
171183
registry_base_path: str = ""
172184
secrets: dict[str, str] = field(default_factory=dict[str, str])
173185
volumes: dict[str, str] = field(default_factory=dict[str, str])
174186

175187
@classmethod
176-
def load(cls, config_dir: Path) -> State:
188+
def from_dict(cls, config_dir: Path, project_name: str, **data: Any) -> State:
189+
filtered = {k: v for k, v in data.items() if k in cls.__annotations__ and not k.startswith("_")}
190+
return cls(_config_dir=config_dir, _project_name=project_name, **filtered)
191+
192+
@classmethod
193+
def load(cls, config_dir: Path, project_name: str) -> State:
194+
"""Load state for a specific project from .jig.json.
195+
196+
The state file structure is:
197+
{
198+
"project-name-1": {
199+
"registry_base_path": "...",
200+
"secrets": {...},
201+
"volumes": {...}
202+
},
203+
"project-name-2": {...}
204+
}
205+
206+
"""
177207
path = config_dir / ".jig.json"
178208
try:
179209
with open(path) as f:
180-
data = {k: v for k, v in json.load(f).items() if k in cls.__annotations__ and not k.startswith("_")}
181-
return cls(_config_dir=config_dir, **data)
210+
all_data = json.load(f)
211+
212+
# Check if this is the new nested structure (project_name as key)
213+
if project_name in all_data and isinstance(all_data[project_name], dict):
214+
# New structure: extract project-specific state
215+
project_data = all_data[project_name]
216+
return cls.from_dict(config_dir, project_name, **project_data)
217+
# Secrets or volumes exist, but not yet migrated (don't care about registry base path)
218+
if "secrets" in all_data or "volumes" in all_data:
219+
return cls.from_dict(config_dir, project_name, **all_data)
220+
# File exists but this project isn't in it yet
221+
return cls(_config_dir=config_dir, _project_name=project_name)
182222
except FileNotFoundError:
183-
return cls(_config_dir=config_dir)
223+
return cls(_config_dir=config_dir, _project_name=project_name)
184224

185225
def save(self) -> None:
226+
"""Save state for this project to .jig.json.
227+
228+
Preserves other projects' state in the same file.
229+
"""
186230
path = self._config_dir / ".jig.json"
187-
data = {k: v for k, v in asdict(self).items() if not k.startswith("_")}
231+
232+
# Load existing file to preserve other projects
233+
try:
234+
with open(path) as f:
235+
all_data = json.load(f)
236+
except FileNotFoundError:
237+
all_data = {}
238+
239+
# Update this project's state
240+
project_data = {k: v for k, v in asdict(self).items() if not k.startswith("_")}
241+
all_data[self._project_name] = project_data
242+
243+
# Save back to file
188244
with open(path, "w") as f:
189-
json.dump(data, f, indent=2)
245+
json.dump(all_data, f, indent=2)

0 commit comments

Comments
 (0)