meshagent-api 0.41.9__tar.gz → 0.42.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.
Files changed (66) hide show
  1. {meshagent_api-0.41.9/meshagent_api.egg-info → meshagent_api-0.42.2}/PKG-INFO +1 -1
  2. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/client.py +11 -0
  3. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/client_test.py +71 -0
  4. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/room_server_client.py +67 -3
  5. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/room_server_client_test.py +65 -1
  6. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/specs/service.py +16 -0
  7. meshagent_api-0.42.2/meshagent/api/version.py +1 -0
  8. {meshagent_api-0.41.9 → meshagent_api-0.42.2/meshagent_api.egg-info}/PKG-INFO +1 -1
  9. meshagent_api-0.41.9/meshagent/api/version.py +0 -1
  10. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/LICENSE +0 -0
  11. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/MANIFEST.in +0 -0
  12. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/README.md +0 -0
  13. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/__init__.py +0 -0
  14. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/agent_content.py +0 -0
  15. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/aiohttp_redaction.py +0 -0
  16. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/chan.py +0 -0
  17. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/crdt.py +0 -0
  18. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/dataset_schema.py +0 -0
  19. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/entrypoint.js +0 -0
  20. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/error_codes.py +0 -0
  21. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/helpers.py +0 -0
  22. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/http.py +0 -0
  23. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/image_runtime.py +0 -0
  24. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/keys.py +0 -0
  25. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/managed_agents.py +0 -0
  26. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/managed_agents_test.py +0 -0
  27. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/messaging.py +0 -0
  28. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/messaging_test.py +0 -0
  29. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/oauth.py +0 -0
  30. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/oauth_scopes.py +0 -0
  31. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/oauth_scopes_test.py +0 -0
  32. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/participant.py +0 -0
  33. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/participant_token.py +0 -0
  34. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/participant_token_test.py +0 -0
  35. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/port_forward.py +0 -0
  36. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/port_forward_test.py +0 -0
  37. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/protocol.py +0 -0
  38. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/protocol_test.py +0 -0
  39. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/py.typed +0 -0
  40. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/reasoning_schema.py +0 -0
  41. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/registry_auth.py +0 -0
  42. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/room_ports.py +0 -0
  43. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/runtime.py +0 -0
  44. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/runtime_test.py +0 -0
  45. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/schema.py +0 -0
  46. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/schema_document.py +0 -0
  47. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/schema_document_test.py +0 -0
  48. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/schema_registry.py +0 -0
  49. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/schema_test.py +0 -0
  50. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/schema_util.py +0 -0
  51. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/service_spec_test.py +0 -0
  52. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/service_template_test.py +0 -0
  53. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/services.py +0 -0
  54. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/sql.py +0 -0
  55. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/sql_test.py +0 -0
  56. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/token_test.py +0 -0
  57. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/urls.py +0 -0
  58. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/webhooks.py +0 -0
  59. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/websocket_protocol.py +0 -0
  60. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent/api/websocket_protocol_test.py +0 -0
  61. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent_api.egg-info/SOURCES.txt +0 -0
  62. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent_api.egg-info/dependency_links.txt +0 -0
  63. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent_api.egg-info/requires.txt +0 -0
  64. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/meshagent_api.egg-info/top_level.txt +0 -0
  65. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/pyproject.toml +0 -0
  66. {meshagent_api-0.41.9 → meshagent_api-0.42.2}/setup.cfg +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.41.9
3
+ Version: 0.42.2
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -1333,6 +1333,17 @@ class Meshagent:
1333
1333
  await self._raise_for_status(resp)
1334
1334
  return await resp.json()
1335
1335
 
1336
+ async def get_project_by_key(self, project_key: str) -> Dict[str, Any]:
1337
+ """
1338
+ Corresponds to: GET /accounts/projects/by-key/{project_key}
1339
+ Returns a JSON dict with { "id", "owner_user_id", "name", "project_key" }.
1340
+ """
1341
+ url = f"{self.base_url}/accounts/projects/by-key/{quote(project_key, safe='')}"
1342
+
1343
+ async with self._session.get(url, headers=self._get_headers()) as resp:
1344
+ await self._raise_for_status(resp)
1345
+ return await resp.json()
1346
+
1336
1347
  async def get_project_info(self, project_id: str) -> ProjectInfo:
1337
1348
  url = f"{self.base_url}/accounts/projects/{project_id}"
1338
1349
 
@@ -12,6 +12,11 @@ from meshagent.api.managed_agents import (
12
12
  from meshagent.api.participant_token import ApiScope
13
13
  from meshagent.api.client import ManagedAgentGrant, Meshagent
14
14
  from meshagent.api.specs.service import (
15
+ RouteBackendSpec,
16
+ RouteMetadata,
17
+ RoutePathSpec,
18
+ RouteRoomBackendSpec,
19
+ RouteSpec,
15
20
  ScheduledTaskQueueSpec,
16
21
  ScheduledTaskSpec,
17
22
  ServiceMetadata,
@@ -98,6 +103,72 @@ async def test_render_template_accepts_decoded_json_response():
98
103
  ]
99
104
 
100
105
 
106
+ @pytest.mark.asyncio
107
+ async def test_create_route_omits_default_strip_prefix_from_paths():
108
+ session = _FakeSession([_FakeResponse(status=200, payload={})])
109
+ client = Meshagent(base_url="http://example.test", token="token", session=session)
110
+
111
+ await client.create_route(
112
+ project_id="project-1",
113
+ spec=RouteSpec(
114
+ metadata=RouteMetadata(name="app.example.test"),
115
+ domain="app.example.test",
116
+ backend=RouteBackendSpec(room=RouteRoomBackendSpec(name="room-1")),
117
+ paths=[RoutePathSpec(path="/", targetPort=3000)],
118
+ ),
119
+ )
120
+
121
+ assert session.calls == [
122
+ (
123
+ "post",
124
+ "http://example.test/accounts/projects/project-1/routes",
125
+ {
126
+ "spec": {
127
+ "version": "v1",
128
+ "kind": "Route",
129
+ "metadata": {"name": "app.example.test", "annotations": {}},
130
+ "domain": "app.example.test",
131
+ "backend": {"room": {"name": "room-1"}, "agent": None},
132
+ "paths": [
133
+ {
134
+ "path": "/",
135
+ "pathType": "prefix",
136
+ "targetPort": 3000,
137
+ }
138
+ ],
139
+ }
140
+ },
141
+ )
142
+ ]
143
+
144
+
145
+ @pytest.mark.asyncio
146
+ async def test_create_route_serializes_true_strip_prefix():
147
+ session = _FakeSession([_FakeResponse(status=200, payload={})])
148
+ client = Meshagent(base_url="http://example.test", token="token", session=session)
149
+
150
+ await client.create_route(
151
+ project_id="project-1",
152
+ spec=RouteSpec(
153
+ metadata=RouteMetadata(name="app.example.test"),
154
+ domain="app.example.test",
155
+ backend=RouteBackendSpec(room=RouteRoomBackendSpec(name="room-1")),
156
+ paths=[
157
+ RoutePathSpec(path="/api", targetPort=3000, stripPrefix=True),
158
+ ],
159
+ ),
160
+ )
161
+
162
+ assert session.calls[0][2]["spec"]["paths"] == [
163
+ {
164
+ "path": "/api",
165
+ "pathType": "prefix",
166
+ "stripPrefix": True,
167
+ "targetPort": 3000,
168
+ }
169
+ ]
170
+
171
+
101
172
  @pytest.mark.asyncio
102
173
  async def test_exchange_oauth_token_posts_form_encoded_token_request():
103
174
  session = _FakeSession(
@@ -1123,6 +1123,14 @@ class RoomClient:
1123
1123
  with contextlib.suppress(Exception):
1124
1124
  await protocol.__aexit__(None, None, None)
1125
1125
 
1126
+ async def _close_protocol_shielded(self, protocol: Protocol) -> None:
1127
+ close_task = asyncio.create_task(self._close_protocol(protocol))
1128
+ try:
1129
+ await asyncio.shield(close_task)
1130
+ except asyncio.CancelledError:
1131
+ await asyncio.shield(close_task)
1132
+ raise
1133
+
1126
1134
  def _replace_protocol(self, next_protocol: Protocol) -> None:
1127
1135
  current_protocol = self._protocol_instance
1128
1136
  self.protocol._unbind(current_protocol)
@@ -1644,13 +1652,19 @@ class RoomClient:
1644
1652
  self._close_watcher_task = None
1645
1653
  if close_watcher is not None:
1646
1654
  close_watcher.cancel()
1655
+ close_cancelled = False
1647
1656
  try:
1648
- await self._close_protocol(self._protocol_instance)
1657
+ try:
1658
+ await self._close_protocol_shielded(self._protocol_instance)
1659
+ except asyncio.CancelledError:
1660
+ close_cancelled = True
1649
1661
  lifecycle_task = self._lifecycle_task
1650
1662
  if lifecycle_task is not None:
1651
1663
  await asyncio.gather(lifecycle_task, return_exceptions=True)
1652
1664
  await self.sync.stop()
1653
1665
  await self.messaging.stop()
1666
+ if close_cancelled:
1667
+ raise asyncio.CancelledError
1654
1668
  finally:
1655
1669
  self._lifecycle_task = None
1656
1670
  self._entered = False
@@ -7639,11 +7653,35 @@ class ImportedImage(BaseModel):
7639
7653
  refs: List[str] = Field(default_factory=list)
7640
7654
 
7641
7655
 
7656
+ class PublishedImageLayer(BaseModel):
7657
+ digest: str
7658
+ size_bytes: int
7659
+ media_type: Optional[str] = None
7660
+
7661
+
7662
+ class PublishedImageStats(BaseModel):
7663
+ manifest_media_type: str
7664
+ layer_count: int
7665
+ total_layer_size_bytes: int
7666
+ total_size_bytes: int
7667
+ layers: List[PublishedImageLayer] = Field(default_factory=list)
7668
+ config_size_bytes: Optional[int] = None
7669
+
7670
+
7671
+ class PublishedBuildImage(BaseModel):
7672
+ tag: str
7673
+ digest: str
7674
+ resolved_ref: str
7675
+ optimized: bool
7676
+ stats: Optional[PublishedImageStats] = None
7677
+
7678
+
7642
7679
  class BuildJob(BaseModel):
7643
7680
  id: str
7644
7681
  tag: str
7645
7682
  status: Literal["queued", "running", "failed", "cancelled", "succeeded"]
7646
7683
  exit_code: Optional[int] = None
7684
+ published_images: List[PublishedBuildImage] = Field(default_factory=list)
7647
7685
 
7648
7686
 
7649
7687
  class ContainerStartedBy(BaseModel):
@@ -7651,16 +7689,38 @@ class ContainerStartedBy(BaseModel):
7651
7689
  name: str
7652
7690
 
7653
7691
 
7692
+ class RoomContainerPort(BaseModel):
7693
+ container_port: int
7694
+ host_port: int
7695
+
7696
+
7697
+ class RoomContainerStats(BaseModel):
7698
+ cpu_usage_nano_cores: Optional[int] = None
7699
+ memory_usage_bytes: Optional[int] = None
7700
+ memory_working_set_bytes: Optional[int] = None
7701
+ timestamp_ns: Optional[int] = None
7702
+
7703
+
7704
+ class ContainerExitStatus(BaseModel):
7705
+ exit_code: int
7706
+ reason: Optional[str] = None
7707
+ message: Optional[str] = None
7708
+ oom_killed: Optional[bool] = None
7709
+
7710
+
7654
7711
  class RoomContainer(BaseModel):
7655
7712
  id: str
7656
7713
  image: Optional[str] = None
7714
+ image_id: Optional[str] = None
7657
7715
  status: Optional[str] = None
7658
7716
  name: Optional[str] = None
7659
- ports: list[int] = Field(default_factory=list)
7717
+ ports: list[RoomContainerPort] = Field(default_factory=list)
7660
7718
  started_by: ContainerStartedBy
7661
7719
  state: Literal["CREATED", "RUNNING", "EXITED", "UNKNOWN"]
7662
7720
  private: bool
7663
7721
  service_id: Optional[str] = None
7722
+ stats: Optional[RoomContainerStats] = None
7723
+ exit_status: Optional[ContainerExitStatus] = None
7664
7724
 
7665
7725
  # Accept arbitrary extras (names, created, state, etc.)
7666
7726
  model_config = ConfigDict(extra="allow")
@@ -8601,6 +8661,10 @@ class ContainersClient:
8601
8661
  )
8602
8662
 
8603
8663
  async def wait_for_exit(self, *, container_id: str) -> int:
8664
+ status = await self.wait_for_exit_status(container_id=container_id)
8665
+ return status.exit_code
8666
+
8667
+ async def wait_for_exit_status(self, *, container_id: str) -> ContainerExitStatus:
8604
8668
  response = await self.room.invoke(
8605
8669
  toolkit="containers",
8606
8670
  tool="wait_for_exit",
@@ -8611,7 +8675,7 @@ class ContainersClient:
8611
8675
  exit_code = response.json.get("exit_code")
8612
8676
  if isinstance(exit_code, bool) or not isinstance(exit_code, int):
8613
8677
  raise self._unexpected_response_error(operation="wait_for_exit")
8614
- return exit_code
8678
+ return ContainerExitStatus.model_validate(response.json)
8615
8679
 
8616
8680
  async def delete(self, *, container_id: str) -> None:
8617
8681
  await self.room.invoke(
@@ -220,6 +220,18 @@ class _CloseableProtocol(_FakeProtocol):
220
220
  return self._close_reason
221
221
 
222
222
 
223
+ class _SlowExitProtocol(_FakeProtocol):
224
+ def __init__(self) -> None:
225
+ super().__init__()
226
+ self.exit_started = asyncio.Event()
227
+ self.allow_exit = asyncio.Event()
228
+
229
+ async def __aexit__(self, exc_type, exc, tb) -> None:
230
+ self.exit_started.set()
231
+ await self.allow_exit.wait()
232
+ await super().__aexit__(exc_type, exc, tb)
233
+
234
+
223
235
  def test_room_exception_defaults_to_invalid_request_code() -> None:
224
236
  ex = RoomException("boom")
225
237
  assert ex.code == ErrorCode.INVALID_REQUEST
@@ -2051,6 +2063,26 @@ async def test_room_client_exit_fails_pending_requests_and_cancels_close_watcher
2051
2063
  assert client._close_watcher_task is None
2052
2064
 
2053
2065
 
2066
+ @pytest.mark.asyncio
2067
+ async def test_room_client_exit_shields_protocol_close_from_cancellation() -> None:
2068
+ protocol = _SlowExitProtocol()
2069
+ client = RoomClient(protocol_factory=protocol.create_factory())
2070
+
2071
+ exit_task = asyncio.create_task(client.__aexit__(None, None, None))
2072
+ await asyncio.wait_for(protocol.exit_started.wait(), timeout=1)
2073
+
2074
+ exit_task.cancel()
2075
+ await asyncio.sleep(0)
2076
+ assert not exit_task.done()
2077
+
2078
+ protocol.allow_exit.set()
2079
+ with pytest.raises(asyncio.CancelledError):
2080
+ await asyncio.wait_for(exit_task, timeout=1)
2081
+
2082
+ assert protocol.exited is True
2083
+ assert client._close_watcher_task is None
2084
+
2085
+
2054
2086
  @pytest.mark.asyncio
2055
2087
  async def test_room_client_wait_for_close_ignores_unexpected_disconnects() -> None:
2056
2088
  controller = _ReconnectRoomController(schema=_simple_thread_schema())
@@ -4049,6 +4081,32 @@ async def test_containers_client_uses_room_invoke_with_strict_payloads() -> None
4049
4081
  "tag": "example:latest",
4050
4082
  "status": "running",
4051
4083
  "exit_code": None,
4084
+ "published_images": [
4085
+ {
4086
+ "tag": "example:latest",
4087
+ "digest": "sha256:digest",
4088
+ "resolved_ref": "example@sha256:digest",
4089
+ "optimized": True,
4090
+ "stats": {
4091
+ "manifest_media_type": (
4092
+ "application/vnd.oci.image.manifest.v1+json"
4093
+ ),
4094
+ "layer_count": 1,
4095
+ "total_layer_size_bytes": 200,
4096
+ "total_size_bytes": 300,
4097
+ "config_size_bytes": 100,
4098
+ "layers": [
4099
+ {
4100
+ "digest": "sha256:layer",
4101
+ "size_bytes": 200,
4102
+ "media_type": (
4103
+ "application/vnd.oci.image.layer.v1.tar"
4104
+ ),
4105
+ }
4106
+ ],
4107
+ },
4108
+ }
4109
+ ],
4052
4110
  }
4053
4111
  ]
4054
4112
  }
@@ -4070,6 +4128,7 @@ async def test_containers_client_uses_room_invoke_with_strict_payloads() -> None
4070
4128
  configs=[ConfigMountSpec()],
4071
4129
  empty_dirs=[EmptyDirMountSpec(path="/cache")],
4072
4130
  ),
4131
+ template="agent",
4073
4132
  )
4074
4133
  await client.build(
4075
4134
  tags=["example:latest"],
@@ -4086,7 +4145,7 @@ async def test_containers_client_uses_room_invoke_with_strict_payloads() -> None
4086
4145
  await client.run_service(service_id="svc-1", env={"A": "1"})
4087
4146
  images = await client.list_images()
4088
4147
  inspection = await client.inspect_image(image_id="img-1")
4089
- await client.list_builds()
4148
+ builds = await client.list_builds()
4090
4149
  await client.cancel_build(build_id="build-1")
4091
4150
  await client.delete_build(build_id="build-1")
4092
4151
  await client.list()
@@ -4119,6 +4178,10 @@ async def test_containers_client_uses_room_invoke_with_strict_payloads() -> None
4119
4178
  assert inspection.image.references == ["demo:latest"]
4120
4179
  assert inspection.target.digest == "sha256:target"
4121
4180
  assert inspection.content_size == 235
4181
+ assert builds[0].published_images[0].resolved_ref == "example@sha256:digest"
4182
+ assert builds[0].published_images[0].stats is not None
4183
+ assert builds[0].published_images[0].stats.layer_count == 1
4184
+ assert builds[0].published_images[0].stats.total_size_bytes == 300
4122
4185
 
4123
4186
  pull_input = room.requests[0]["input"]
4124
4187
  assert isinstance(pull_input, dict)
@@ -4130,6 +4193,7 @@ async def test_containers_client_uses_room_invoke_with_strict_payloads() -> None
4130
4193
  assert isinstance(run_input, dict)
4131
4194
  assert run_input["env"] == [{"key": "KEY", "value": "VALUE"}]
4132
4195
  assert run_input["ports"] == [{"container_port": 8080, "host_port": 80}]
4196
+ assert run_input["template"] == "agent"
4133
4197
  assert isinstance(run_input["mounts"], dict)
4134
4198
  assert run_input["mounts"]["configs"] == [{"path": "/var/run/meshagent"}]
4135
4199
  assert run_input["mounts"]["empty_dirs"] == [{"path": "/cache", "read_only": False}]
@@ -4,6 +4,7 @@ from pydantic import (
4
4
  ConfigDict,
5
5
  Field,
6
6
  field_validator,
7
+ model_serializer,
7
8
  model_validator,
8
9
  )
9
10
  from typing import Any, Optional, Literal
@@ -461,8 +462,16 @@ class RoutePathSpec(BaseModel):
461
462
 
462
463
  path: str = "/"
463
464
  pathType: Literal["prefix", "exact"] = "prefix"
465
+ stripPrefix: bool = False
464
466
  targetPort: str | int
465
467
 
468
+ @model_serializer(mode="wrap")
469
+ def serialize_route_path(self, handler: Any) -> dict[str, Any]:
470
+ data: dict[str, Any] = handler(self)
471
+ if not self.stripPrefix:
472
+ data.pop("stripPrefix", None)
473
+ return data
474
+
466
475
  @field_validator("path")
467
476
  @classmethod
468
477
  def validate_path(cls, value: str) -> str:
@@ -633,6 +642,13 @@ class EndpointSpec(BaseModel):
633
642
  class PortSpec(BaseModel):
634
643
  model_config = ConfigDict(extra="forbid")
635
644
  num: Literal["*"] | PositiveInt = "*"
645
+ host_port: Optional[PositiveInt] = Field(
646
+ None,
647
+ description=(
648
+ "optional room host port to publish this container port on; when set, "
649
+ "published defaults to true unless explicitly set"
650
+ ),
651
+ )
636
652
  type: Optional[Literal["http", "tcp"]] = "http"
637
653
  endpoints: list[EndpointSpec] = Field(
638
654
  default_factory=list, description="a list of endpoints exposed under this port"
@@ -0,0 +1 @@
1
+ __version__ = "0.42.2"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: meshagent-api
3
- Version: 0.41.9
3
+ Version: 0.42.2
4
4
  Summary: Python Server API for Meshagent
5
5
  License-Expression: Apache-2.0
6
6
  Project-URL: Documentation, https://docs.meshagent.com
@@ -1 +0,0 @@
1
- __version__ = "0.41.9"
File without changes
File without changes
File without changes