meshapi 0.1.5__tar.gz → 0.1.6__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 (44) hide show
  1. {meshapi-0.1.5 → meshapi-0.1.6}/CLAUDE.md +35 -0
  2. {meshapi-0.1.5 → meshapi-0.1.6}/PKG-INFO +1 -1
  3. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_audio.py +17 -1
  4. meshapi-0.1.6/livetests/test_compare.py +38 -0
  5. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/__init__.py +4 -0
  6. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/_types.py +18 -0
  7. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/audio.py +14 -8
  8. {meshapi-0.1.5 → meshapi-0.1.6}/pyproject.toml +1 -1
  9. {meshapi-0.1.5 → meshapi-0.1.6}/.gitignore +0 -0
  10. {meshapi-0.1.5 → meshapi-0.1.6}/CHANGELOG.md +0 -0
  11. {meshapi-0.1.5 → meshapi-0.1.6}/README.md +0 -0
  12. {meshapi-0.1.5 → meshapi-0.1.6}/TESTING.md +0 -0
  13. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/compare.py +0 -0
  14. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/config.py +0 -0
  15. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/conftest.py +0 -0
  16. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/pytest.ini +0 -0
  17. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/requirements.txt +0 -0
  18. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/responses.py +0 -0
  19. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_chat.py +0 -0
  20. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_errors.py +0 -0
  21. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_feature_matrix.py +0 -0
  22. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_inference_resources.py +0 -0
  23. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_models.py +0 -0
  24. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_rag.py +0 -0
  25. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_realtime.py +0 -0
  26. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_stream.py +0 -0
  27. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_structured_output.py +0 -0
  28. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_templates.py +0 -0
  29. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/test_video.py +0 -0
  30. {meshapi-0.1.5 → meshapi-0.1.6}/livetests/tool_call.py +0 -0
  31. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/_errors.py +0 -0
  32. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/_http.py +0 -0
  33. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/__init__.py +0 -0
  34. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/batches.py +0 -0
  35. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/chat.py +0 -0
  36. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/compare.py +0 -0
  37. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/embeddings.py +0 -0
  38. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/images.py +0 -0
  39. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/models.py +0 -0
  40. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/rag.py +0 -0
  41. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/realtime.py +0 -0
  42. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/responses.py +0 -0
  43. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/templates.py +0 -0
  44. {meshapi-0.1.5 → meshapi-0.1.6}/meshapi/resources/videos.py +0 -0
@@ -162,6 +162,7 @@ pytest test_rag.py::test_rag_upload_embed_search -v
162
162
  | `test_realtime.py` | WebSocket connect/close, session lifecycle |
163
163
  | `test_audio.py` | TTS synthesize, voice listing |
164
164
  | `test_video.py` | Video list, generate → retrieve |
165
+ | `test_compare.py` | Non-streaming compare, streaming compare |
165
166
 
166
167
  ---
167
168
 
@@ -176,6 +177,40 @@ Every SDK change — however small — must include all of the following before
176
177
 
177
178
  ---
178
179
 
180
+ ---
181
+
182
+ ## Release
183
+
184
+ Releases are triggered by pushing a `v*` git tag. The `publish.yml` workflow builds the package with hatch and publishes to PyPI via OIDC (no API token needed).
185
+
186
+ ### Release checklist
187
+
188
+ 1. **Bump the version** in `pyproject.toml`:
189
+ ```toml
190
+ version = "0.1.6"
191
+ ```
192
+
193
+ 2. **Commit the version bump**:
194
+ ```bash
195
+ git add pyproject.toml
196
+ git commit -m "chore: bump version to 0.1.6"
197
+ ```
198
+
199
+ 3. **Tag and push**:
200
+ ```bash
201
+ git tag v0.1.6
202
+ git push origin main
203
+ git push origin v0.1.6
204
+ ```
205
+
206
+ 4. **Monitor the workflow** at `Actions → Publish to PyPI`.
207
+
208
+ 5. **Verify** the new version is live:
209
+ ```bash
210
+ pip install meshapi==0.1.6
211
+ python -c "import meshapi; print(meshapi.__version__)"
212
+ ```
213
+
179
214
  ### RAG live test notes
180
215
 
181
216
  `test_rag_upload_embed_search` does the following:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshapi
3
- Version: 0.1.5
3
+ Version: 0.1.6
4
4
  Summary: Official Python SDK for the MeshAPI AI model gateway
5
5
  Project-URL: Homepage, https://meshapi.ai
6
6
  Project-URL: Documentation, https://developers.meshapi.ai
@@ -4,7 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import os
6
6
 
7
- from meshapi import MeshAPI, SpeechParams, ListVoicesParams
7
+ from meshapi import MeshAPI, SpeechParams, ListVoicesParams, TranscriptionParams
8
8
 
9
9
  TTS_MODEL = os.environ.get("MESHAPI_TTS_MODEL", "sarvam/bulbul:v2")
10
10
  STT_MODEL = os.environ.get("MESHAPI_STT_MODEL", "sarvam/saaras:v3")
@@ -18,6 +18,22 @@ def test_audio_synthesize(client: MeshAPI) -> None:
18
18
  print(f"[PASS] audio.synthesize -> {len(audio_bytes)} bytes")
19
19
 
20
20
 
21
+ def test_audio_stt_from_tts(client: MeshAPI) -> None:
22
+ audio_bytes = client.audio.synthesize(
23
+ SpeechParams(input="Hello from MeshAPI audio test.", model=TTS_MODEL)
24
+ )
25
+ assert isinstance(audio_bytes, bytes) and len(audio_bytes) > 0, "TTS step failed"
26
+
27
+ result = client.audio.transcribe(
28
+ audio_bytes,
29
+ TranscriptionParams(model=STT_MODEL),
30
+ filename="tts_output.wav",
31
+ )
32
+ assert result is not None
33
+ assert isinstance(result.text, str) and len(result.text) > 0
34
+ print(f"[PASS] audio.transcribe (via TTS audio) -> {result.text!r}")
35
+
36
+
21
37
  def test_audio_list_voices(client: MeshAPI) -> None:
22
38
  voices = client.audio.list_voices(ListVoicesParams(page_size=5))
23
39
  assert voices is not None
@@ -0,0 +1,38 @@
1
+ """Live tests: Model Compare API."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import pytest
6
+ from meshapi import MeshAPI, CompareParams, ChatMessage
7
+
8
+
9
+ def test_compare_nonstreaming(client: MeshAPI, model: str, second_model: str) -> None:
10
+ result = client.compare.create(
11
+ CompareParams(
12
+ models=[model, second_model],
13
+ messages=[ChatMessage(role="user", content="What is 2+2? Reply in one word.")],
14
+ skip_comparison=True,
15
+ max_tokens=20,
16
+ )
17
+ )
18
+ assert result.comparison_id, "expected comparison_id"
19
+ assert len(result.results) == 2, f"expected 2 results, got {len(result.results)}"
20
+ found_models = {r.model for r in result.results}
21
+ assert model in found_models, f"expected {model} in results"
22
+ assert second_model in found_models, f"expected {second_model} in results"
23
+ for r in result.results:
24
+ assert r.content or r.error, f"result for {r.model} has neither content nor error"
25
+
26
+
27
+ def test_compare_streaming(client: MeshAPI, model: str, second_model: str) -> None:
28
+ events = list(
29
+ client.compare.stream(
30
+ CompareParams(
31
+ models=[model, second_model],
32
+ messages=[ChatMessage(role="user", content="Tell me a joke.")],
33
+ skip_comparison=True,
34
+ max_tokens=50,
35
+ )
36
+ )
37
+ )
38
+ assert len(events) > 0, "expected at least one streaming event"
@@ -16,6 +16,8 @@ from ._types import (
16
16
  TranscriptionParams,
17
17
  TranscriptionResponse,
18
18
  TranscriptionTranslateParams,
19
+ Voice,
20
+ VoicesResponse,
19
21
  VoiceSettings,
20
22
  BulkEmbedResponse,
21
23
  BulkEmbedResult,
@@ -218,6 +220,8 @@ __all__ = [
218
220
  "TranscriptionTranslateParams",
219
221
  "TranscriptionResponse",
220
222
  "ListVoicesParams",
223
+ "Voice",
224
+ "VoicesResponse",
221
225
  ]
222
226
 
223
227
 
@@ -935,6 +935,24 @@ class ListVoicesParams(BaseModel):
935
935
  voice_ids: Optional[List[str]] = None
936
936
 
937
937
 
938
+ class Voice(BaseModel):
939
+ model_config = ConfigDict(extra="ignore")
940
+ voice_id: str
941
+ name: str
942
+ category: str
943
+ description: str
944
+ preview_url: str
945
+ labels: Dict[str, str] = {}
946
+
947
+
948
+ class VoicesResponse(BaseModel):
949
+ model_config = ConfigDict(extra="ignore")
950
+ voices: List[Voice]
951
+ has_more: bool
952
+ total_count: int
953
+ next_page_token: Optional[str] = None
954
+
955
+
938
956
  # ---------------------------------------------------------------------------
939
957
 
940
958
  class ApiErrorBody(BaseModel):
@@ -11,6 +11,8 @@ from .._types import (
11
11
  TranscriptionParams,
12
12
  TranscriptionResponse,
13
13
  TranscriptionTranslateParams,
14
+ Voice,
15
+ VoicesResponse,
14
16
  )
15
17
 
16
18
 
@@ -62,16 +64,18 @@ class AudioResource:
62
64
  )
63
65
  return TranscriptionResponse.model_validate(data)
64
66
 
65
- def list_voices(self, params: Optional[ListVoicesParams] = None) -> Any:
67
+ def list_voices(self, params: Optional[ListVoicesParams] = None) -> VoicesResponse:
66
68
  """GET /v1/audio/voices — list/search voices."""
67
69
  query: Dict[str, Any] = {}
68
70
  if params is not None:
69
71
  query = {k: v for k, v in params.model_dump(exclude_none=True).items()}
70
- return self._http.get("/v1/audio/voices", params=query or None)
72
+ data = self._http.get("/v1/audio/voices", params=query or None)
73
+ return VoicesResponse.model_validate(data)
71
74
 
72
- def get_voice(self, voice_id: str) -> Any:
75
+ def get_voice(self, voice_id: str) -> Voice:
73
76
  """GET /v1/audio/voices/{voice_id}."""
74
- return self._http.get(f"/v1/audio/voices/{voice_id}")
77
+ data = self._http.get(f"/v1/audio/voices/{voice_id}")
78
+ return Voice.model_validate(data)
75
79
 
76
80
 
77
81
  class AsyncAudioResource:
@@ -122,13 +126,15 @@ class AsyncAudioResource:
122
126
  )
123
127
  return TranscriptionResponse.model_validate(data)
124
128
 
125
- async def list_voices(self, params: Optional[ListVoicesParams] = None) -> Any:
129
+ async def list_voices(self, params: Optional[ListVoicesParams] = None) -> VoicesResponse:
126
130
  """GET /v1/audio/voices — list/search voices."""
127
131
  query: Dict[str, Any] = {}
128
132
  if params is not None:
129
133
  query = {k: v for k, v in params.model_dump(exclude_none=True).items()}
130
- return await self._http.get("/v1/audio/voices", params=query or None)
134
+ data = await self._http.get("/v1/audio/voices", params=query or None)
135
+ return VoicesResponse.model_validate(data)
131
136
 
132
- async def get_voice(self, voice_id: str) -> Any:
137
+ async def get_voice(self, voice_id: str) -> Voice:
133
138
  """GET /v1/audio/voices/{voice_id}."""
134
- return await self._http.get(f"/v1/audio/voices/{voice_id}")
139
+ data = await self._http.get(f"/v1/audio/voices/{voice_id}")
140
+ return Voice.model_validate(data)
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "meshapi"
7
- version = "0.1.5"
7
+ version = "0.1.6"
8
8
  description = "Official Python SDK for the MeshAPI AI model gateway"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.9"
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