meshagent-api 0.24.5__py3-none-any.whl → 0.25.0__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.
@@ -1,12 +1,13 @@
1
1
  from meshagent.api.protocol import Protocol, ClientProtocol
2
- from meshagent.api.specs.service import ContainerMountSpec
2
+ from meshagent.api.specs.service import ContainerMountSpec, ServiceSpec
3
3
  import json
4
4
  import asyncio
5
5
  import logging
6
6
  import os
7
+ import aiohttp
7
8
  from meshagent.api.websocket_protocol import WebSocketClientProtocol
8
9
  from meshagent.api.participant_token import ApiScope
9
- from pydantic import BaseModel, Field, JsonValue, ConfigDict
10
+ from pydantic import BaseModel, Field, JsonValue, ConfigDict, TypeAdapter
10
11
  from typing import (
11
12
  Optional,
12
13
  Callable,
@@ -18,6 +19,8 @@ from typing import (
18
19
  TypeVar,
19
20
  AsyncIterator,
20
21
  Awaitable,
22
+ Annotated,
23
+ Union,
21
24
  )
22
25
 
23
26
  import base64
@@ -332,6 +335,7 @@ class RoomClient:
332
335
  self,
333
336
  *,
334
337
  protocol: Optional[ClientProtocol] = None,
338
+ session: aiohttp.ClientSession | None = None,
335
339
  oauth_token_request_handler: Optional[
336
340
  Callable[["OAuthTokenRequest"], Awaitable]
337
341
  ] = None,
@@ -342,7 +346,9 @@ class RoomClient:
342
346
 
343
347
  if room_name is not None and token is not None:
344
348
  protocol = WebSocketClientProtocol(
345
- url=websocket_room_url(room_name=room_name), token=token
349
+ url=websocket_room_url(room_name=room_name),
350
+ token=token,
351
+ session=session,
346
352
  )
347
353
 
348
354
  if protocol is None:
@@ -374,6 +380,7 @@ class RoomClient:
374
380
  self.secrets = SecretsClient(
375
381
  room=self, oauth_token_request_handler=oauth_token_request_handler
376
382
  )
383
+ self.services = ServicesClient(room=self)
377
384
 
378
385
  self._room_url = None
379
386
  self._room_name = None
@@ -776,6 +783,32 @@ class ToolkitDescription:
776
783
  }
777
784
 
778
785
 
786
+ class _ListServicesResponse(BaseModel):
787
+ services: list[ServiceSpec]
788
+
789
+
790
+ class ServicesClient:
791
+ def __init__(self, *, room: RoomClient):
792
+ self.room = room
793
+
794
+ async def list(
795
+ self,
796
+ ) -> List[ServiceSpec]:
797
+ """
798
+ Fetch a list of services.
799
+ """
800
+
801
+ response = await self.room.send_request(
802
+ "services.list",
803
+ {},
804
+ )
805
+
806
+ if not isinstance(response, JsonResponse):
807
+ raise RoomException("Invalid return type from list services call")
808
+
809
+ return _ListServicesResponse.model_validate(response.json).services
810
+
811
+
779
812
  class AgentsClient:
780
813
  def __init__(self, *, room: RoomClient):
781
814
  self.room = room
@@ -1727,26 +1760,12 @@ class DeveloperClient:
1727
1760
  await self._room.send_request(type="developer.unwatch", request={})
1728
1761
 
1729
1762
 
1730
- _data_types = dict()
1731
-
1732
-
1733
- class DataType(ABC):
1734
- def __init__(
1735
- self, *, nullable: Optional[bool] = None, metadata: Optional[dict] = None
1736
- ):
1737
- self.nullable = nullable
1738
- self.metadata = metadata
1739
-
1740
- @abstractmethod
1741
- def to_json(self) -> dict:
1742
- return {
1743
- "nullable": self.nullable,
1744
- "metadata": self.metadata,
1745
- }
1763
+ class DataType(BaseModel, ABC):
1764
+ type: str
1765
+ nullable: Optional[bool] = None
1766
+ metadata: Optional[dict] = None
1746
1767
 
1747
- @staticmethod
1748
- def from_json(data: dict) -> "DataType":
1749
- return _data_types[data["type"]].from_json(data)
1768
+ model_config = ConfigDict(extra="allow")
1750
1769
 
1751
1770
  def _maybe_nullable_schema(self, t: str):
1752
1771
  if self.nullable:
@@ -1759,58 +1778,21 @@ class DataType(ABC):
1759
1778
 
1760
1779
 
1761
1780
  class IntDataType(DataType):
1762
- def __init__(self, **kwargs):
1763
- super().__init__(**kwargs)
1764
-
1765
- @staticmethod
1766
- def from_json(data: dict):
1767
- assert data["type"] == "int"
1768
- return IntDataType(nullable=data.get("nullable"), metadata=data.get("metadata"))
1769
-
1770
- def to_json(self):
1771
- return {"type": "int", **super().to_json()}
1781
+ type: Literal["int"] = "int"
1772
1782
 
1773
1783
  def to_json_schema(self):
1774
1784
  return {"type": self._maybe_nullable_schema("number")}
1775
1785
 
1776
1786
 
1777
- _data_types["int"] = IntDataType
1778
-
1779
-
1780
1787
  class BoolDataType(DataType):
1781
- def __init__(self, **kwargs):
1782
- super().__init__(**kwargs)
1783
-
1784
- @staticmethod
1785
- def from_json(data: dict):
1786
- assert data["type"] == "bool"
1787
- return BoolDataType(
1788
- nullable=data.get("nullable"), metadata=data.get("metadata")
1789
- )
1790
-
1791
- def to_json(self):
1792
- return {"type": "bool", **super().to_json()}
1788
+ type: Literal["bool"] = "bool"
1793
1789
 
1794
1790
  def to_json_schema(self):
1795
1791
  return {"type": self._maybe_nullable_schema("boolean")}
1796
1792
 
1797
1793
 
1798
- _data_types["bool"] = BoolDataType
1799
-
1800
-
1801
1794
  class DateDataType(DataType):
1802
- def __init__(self, **kwargs):
1803
- super().__init__(**kwargs)
1804
-
1805
- @staticmethod
1806
- def from_json(data: dict):
1807
- assert data["type"] == "date"
1808
- return DateDataType(
1809
- nullable=data.get("nullable"), metadata=data.get("metadata")
1810
- )
1811
-
1812
- def to_json(self):
1813
- return {"type": "date", **super().to_json()}
1795
+ type: Literal["date"] = "date"
1814
1796
 
1815
1797
  def to_json_schema(self):
1816
1798
  return {
@@ -1819,22 +1801,8 @@ class DateDataType(DataType):
1819
1801
  }
1820
1802
 
1821
1803
 
1822
- _data_types["date"] = DateDataType
1823
-
1824
-
1825
1804
  class TimestampDataType(DataType):
1826
- def __init__(self, **kwargs):
1827
- super().__init__(**kwargs)
1828
-
1829
- @staticmethod
1830
- def from_json(data: dict):
1831
- assert data["type"] == "timestamp"
1832
- return TimestampDataType(
1833
- nullable=data.get("nullable"), metadata=data.get("metadata")
1834
- )
1835
-
1836
- def to_json(self):
1837
- return {"type": "timestamp", **super().to_json()}
1805
+ type: Literal["timestamp"] = "timestamp"
1838
1806
 
1839
1807
  def to_json_schema(self):
1840
1808
  return {
@@ -1843,22 +1811,8 @@ class TimestampDataType(DataType):
1843
1811
  }
1844
1812
 
1845
1813
 
1846
- _data_types["timestamp"] = TimestampDataType
1847
-
1848
-
1849
1814
  class FloatDataType(DataType):
1850
- def __init__(self, **kwargs):
1851
- super().__init__(**kwargs)
1852
-
1853
- @staticmethod
1854
- def from_json(data: dict):
1855
- assert data["type"] == "float"
1856
- return FloatDataType(
1857
- nullable=data.get("nullable"), metadata=data.get("metadata")
1858
- )
1859
-
1860
- def to_json(self):
1861
- return {"type": "float", **super().to_json()}
1815
+ type: Literal["float"] = "float"
1862
1816
 
1863
1817
  def to_json_schema(self):
1864
1818
  return {
@@ -1866,32 +1820,10 @@ class FloatDataType(DataType):
1866
1820
  }
1867
1821
 
1868
1822
 
1869
- _data_types["float"] = FloatDataType
1870
-
1871
-
1872
1823
  class VectorDataType(DataType):
1873
- def __init__(self, *, size: int, element_type: DataType, **kwargs):
1874
- super().__init__(**kwargs)
1875
- self.size = size
1876
- self.element_type = element_type
1877
-
1878
- @staticmethod
1879
- def from_json(data: dict):
1880
- assert data["type"] == "vector"
1881
- return VectorDataType(
1882
- size=data["size"],
1883
- element_type=DataType.from_json(
1884
- data["element_type"],
1885
- ),
1886
- )
1887
-
1888
- def to_json(self):
1889
- return {
1890
- "type": "vector",
1891
- "size": self.size,
1892
- "element_type": self.element_type.to_json(),
1893
- **super().to_json(),
1894
- }
1824
+ type: Literal["vector"] = "vector"
1825
+ size: int
1826
+ element_type: "DataTypeUnion"
1895
1827
 
1896
1828
  def to_json_schema(self):
1897
1829
  return {
@@ -1901,25 +1833,8 @@ class VectorDataType(DataType):
1901
1833
  }
1902
1834
 
1903
1835
 
1904
- _data_types["vector"] = VectorDataType
1905
-
1906
-
1907
1836
  class TextDataType(DataType):
1908
- def __init__(self, **kwargs):
1909
- super().__init__(**kwargs)
1910
-
1911
- @staticmethod
1912
- def from_json(data: dict):
1913
- assert data["type"] == "text"
1914
- return TextDataType(
1915
- nullable=data.get("nullable"), metadata=data.get("metadata")
1916
- )
1917
-
1918
- def to_json(self):
1919
- return {
1920
- "type": "text",
1921
- **super().to_json(),
1922
- }
1837
+ type: Literal["text"] = "text"
1923
1838
 
1924
1839
  def to_json_schema(self):
1925
1840
  return {
@@ -1927,25 +1842,8 @@ class TextDataType(DataType):
1927
1842
  }
1928
1843
 
1929
1844
 
1930
- _data_types["text"] = TextDataType
1931
-
1932
-
1933
1845
  class BinaryDataType(DataType):
1934
- def __init__(self, **kwargs):
1935
- super().__init__(**kwargs)
1936
-
1937
- @staticmethod
1938
- def from_json(data: dict):
1939
- assert data["type"] == "binary"
1940
- return BinaryDataType(
1941
- nullable=data.get("nullable"), metadata=data.get("metadata")
1942
- )
1943
-
1944
- def to_json(self):
1945
- return {
1946
- "type": "binary",
1947
- **super().to_json(),
1948
- }
1846
+ type: Literal["binary"] = "binary"
1949
1847
 
1950
1848
  def to_json_schema(self):
1951
1849
  return {
@@ -1955,11 +1853,156 @@ class BinaryDataType(DataType):
1955
1853
  }
1956
1854
 
1957
1855
 
1958
- _data_types["binary"] = BinaryDataType
1856
+ DataTypeUnion = Annotated[
1857
+ Union[
1858
+ IntDataType,
1859
+ BoolDataType,
1860
+ DateDataType,
1861
+ TimestampDataType,
1862
+ FloatDataType,
1863
+ VectorDataType,
1864
+ TextDataType,
1865
+ BinaryDataType,
1866
+ ],
1867
+ Field(discriminator="type"),
1868
+ ]
1869
+ _data_type_adapter = TypeAdapter(DataTypeUnion)
1870
+ VectorDataType.model_rebuild()
1959
1871
 
1960
1872
  CreateMode = Literal["create", "overwrite", "create_if_not_exists"]
1961
1873
 
1962
1874
 
1875
+ class _CreateTableRequest(BaseModel):
1876
+ model_config = ConfigDict(populate_by_name=True)
1877
+
1878
+ name: str
1879
+ data: Optional[Any] = None
1880
+ table_schema: Optional[Dict[str, DataTypeUnion]] = Field(
1881
+ default=None, alias="schema"
1882
+ )
1883
+ mode: CreateMode = "create"
1884
+ namespace: Optional[list[str]] = None
1885
+ metadata: Optional[dict] = None
1886
+
1887
+
1888
+ class _ListTablesRequest(BaseModel):
1889
+ namespace: Optional[list[str]] = None
1890
+
1891
+
1892
+ class _TableRequest(BaseModel):
1893
+ table: str
1894
+ namespace: Optional[list[str]] = None
1895
+
1896
+
1897
+ class _InspectTableRequest(_TableRequest):
1898
+ pass
1899
+
1900
+
1901
+ class _DropTableRequest(BaseModel):
1902
+ name: str
1903
+ ignore_missing: bool = False
1904
+ namespace: Optional[list[str]] = None
1905
+
1906
+
1907
+ class _DropIndexRequest(_TableRequest):
1908
+ name: str
1909
+
1910
+
1911
+ class _AddColumnsRequest(_TableRequest):
1912
+ new_columns: Dict[str, str | DataTypeUnion]
1913
+
1914
+
1915
+ class _AlterColumnsRequest(_TableRequest):
1916
+ columns: Dict[str, DataTypeUnion]
1917
+
1918
+
1919
+ class _DropColumnsRequest(_TableRequest):
1920
+ columns: List[str]
1921
+
1922
+
1923
+ class _InsertRequest(_TableRequest):
1924
+ records: List[Dict[str, Any]]
1925
+
1926
+
1927
+ class _UpdateRequest(_TableRequest):
1928
+ where: str
1929
+ values: Optional[Dict[str, Any]] = None
1930
+ values_sql: Optional[Dict[str, str]] = None
1931
+
1932
+
1933
+ class _DeleteRequest(_TableRequest):
1934
+ where: str
1935
+
1936
+
1937
+ class _MergeRequest(_TableRequest):
1938
+ on: str
1939
+ records: Any
1940
+
1941
+
1942
+ class _SearchRequest(_TableRequest):
1943
+ text: Optional[str] = None
1944
+ vector: Optional[list[float]] = None
1945
+ text_columns: Optional[list[str]] = None
1946
+ where: Optional[str] = None
1947
+ offset: Optional[int] = None
1948
+ limit: Optional[int] = None
1949
+ select: Optional[List[str]] = None
1950
+
1951
+
1952
+ class _CountRequest(_TableRequest):
1953
+ text: Optional[str] = None
1954
+ vector: Optional[list[float]] = None
1955
+ text_columns: Optional[list[str]] = None
1956
+ where: Optional[str] = None
1957
+
1958
+
1959
+ class _OptimizeRequest(_TableRequest):
1960
+ pass
1961
+
1962
+
1963
+ class _RestoreRequest(_TableRequest):
1964
+ version: int
1965
+
1966
+
1967
+ class _CheckoutRequest(_TableRequest):
1968
+ version: int
1969
+
1970
+
1971
+ class _ListVersionsRequest(_TableRequest):
1972
+ pass
1973
+
1974
+
1975
+ class _CreateVectorIndexRequest(_TableRequest):
1976
+ column: str
1977
+ replace: Optional[bool] = None
1978
+
1979
+
1980
+ class _CreateScalarIndexRequest(_TableRequest):
1981
+ column: str
1982
+ replace: Optional[bool] = None
1983
+
1984
+
1985
+ class _CreateFullTextSearchIndexRequest(_TableRequest):
1986
+ column: str
1987
+ replace: Optional[bool] = None
1988
+
1989
+
1990
+ class _ListIndexesRequest(_TableRequest):
1991
+ pass
1992
+
1993
+
1994
+ class SqlTableReference(BaseModel):
1995
+ name: str
1996
+ namespace: Optional[list[str]] = None
1997
+ alias: Optional[str] = None
1998
+
1999
+
2000
+ class _SqlRequest(BaseModel):
2001
+ query: str
2002
+ tables: List[SqlTableReference]
2003
+ params: Optional[Dict[str, Any]] = None
2004
+
2005
+
1963
2006
  class DatabaseClient:
1964
2007
  """
1965
2008
  A client for interacting with the 'database' extension on the room server.
@@ -1977,25 +2020,24 @@ class DatabaseClient:
1977
2020
 
1978
2021
  :return: A list of table names.
1979
2022
  """
2023
+ request_model = _ListTablesRequest(namespace=namespace)
1980
2024
  response: JsonResponse = await self.room.send_request(
1981
- "database.list_tables",
1982
- {
1983
- "namespace": namespace,
1984
- },
2025
+ "database.list_tables", request_model.model_dump()
1985
2026
  )
1986
2027
  return response.json.get("tables", [])
1987
2028
 
1988
2029
  async def inspect(
1989
2030
  self, *, table: str, namespace: Optional[list[str]] = None
1990
2031
  ) -> dict[str, DataType]:
2032
+ request_model = _InspectTableRequest(table=table, namespace=namespace)
1991
2033
  response: JsonResponse = await self.room.send_request(
1992
- "database.inspect", {"table": table, "namespace": namespace}
2034
+ "database.inspect", request_model.model_dump()
1993
2035
  )
1994
2036
 
1995
2037
  schema = dict[str, DataType]()
1996
2038
 
1997
2039
  for k, v in response.json["schema"].items():
1998
- schema[k] = DataType.from_json(v)
2040
+ schema[k] = _data_type_adapter.validate_python(v)
1999
2041
 
2000
2042
  return schema
2001
2043
 
@@ -2019,22 +2061,17 @@ class DatabaseClient:
2019
2061
  :return: Server response dict containing "status", "table", etc.
2020
2062
  """
2021
2063
 
2022
- schema_dict = None
2023
-
2024
- if schema is not None:
2025
- schema_dict = {}
2026
- for k in schema.keys():
2027
- schema_dict[k] = schema[k].to_json()
2028
-
2029
- payload = {
2030
- "name": name,
2031
- "data": data,
2032
- "schema": schema_dict,
2033
- "mode": mode,
2034
- "namespace": namespace,
2035
- "metadata": metadata,
2036
- }
2037
- await self.room.send_request("database.create_table", payload)
2064
+ request_model = _CreateTableRequest(
2065
+ name=name,
2066
+ data=data,
2067
+ table_schema=schema,
2068
+ mode=mode,
2069
+ namespace=namespace,
2070
+ metadata=metadata,
2071
+ )
2072
+ await self.room.send_request(
2073
+ "database.create_table", request_model.model_dump(by_alias=True)
2074
+ )
2038
2075
  return None
2039
2076
 
2040
2077
  async def create_table_with_schema(
@@ -2086,12 +2123,10 @@ class DatabaseClient:
2086
2123
  :param name: Table name.
2087
2124
  :param ignore_missing: If True, ignore if table doesn't exist.
2088
2125
  """
2089
- payload = {
2090
- "name": name,
2091
- "ignore_missing": ignore_missing,
2092
- "namespace": namespace,
2093
- }
2094
- await self.room.send_request("database.drop_table", payload)
2126
+ request_model = _DropTableRequest(
2127
+ name=name, ignore_missing=ignore_missing, namespace=namespace
2128
+ )
2129
+ await self.room.send_request("database.drop_table", request_model.model_dump())
2095
2130
  return None
2096
2131
 
2097
2132
  async def drop_index(
@@ -2103,8 +2138,8 @@ class DatabaseClient:
2103
2138
  :param table: table name
2104
2139
  :param name: index name.
2105
2140
  """
2106
- payload = {"table": table, "name": name, "namespace": namespace}
2107
- await self.room.send_request("database.drop_index", payload)
2141
+ request_model = _DropIndexRequest(table=table, name=name, namespace=namespace)
2142
+ await self.room.send_request("database.drop_index", request_model.model_dump())
2108
2143
  return None
2109
2144
 
2110
2145
  async def add_columns(
@@ -2121,17 +2156,10 @@ class DatabaseClient:
2121
2156
  :param new_columns: Dict of {column_name: default_value_expression}.
2122
2157
  """
2123
2158
 
2124
- columns = {}
2125
-
2126
- for c in new_columns.keys():
2127
- if isinstance(new_columns[c], DataType):
2128
- columns[c] = new_columns[c].to_json()
2129
- else:
2130
- columns[c] = new_columns[c]
2131
-
2132
- payload = {"table": table, "new_columns": columns, "namespace": namespace}
2133
-
2134
- await self.room.send_request("database.add_columns", payload)
2159
+ request_model = _AddColumnsRequest(
2160
+ table=table, new_columns=new_columns, namespace=namespace
2161
+ )
2162
+ await self.room.send_request("database.add_columns", request_model.model_dump())
2135
2163
  return None
2136
2164
 
2137
2165
  # TODO: not ready yet on lance side
@@ -2149,17 +2177,12 @@ class DatabaseClient:
2149
2177
  :param new_columns: Dict of {column_name: default_value_expression}.
2150
2178
  """
2151
2179
 
2152
- cs = {}
2153
-
2154
- for c in columns.keys():
2155
- if isinstance(columns[c], DataType):
2156
- cs[c] = columns[c].to_json()
2157
- else:
2158
- raise RoomException("columns must be of type DataType")
2159
-
2160
- payload = {"table": table, "columns": cs, "namespace": namespace}
2161
-
2162
- await self.room.send_request("database.alter_columns", payload)
2180
+ request_model = _AlterColumnsRequest(
2181
+ table=table, columns=columns, namespace=namespace
2182
+ )
2183
+ await self.room.send_request(
2184
+ "database.alter_columns", request_model.model_dump()
2185
+ )
2163
2186
  return None
2164
2187
 
2165
2188
  async def drop_columns(
@@ -2171,9 +2194,12 @@ class DatabaseClient:
2171
2194
  :param table: Table name.
2172
2195
  :param columns: List of column names to drop.
2173
2196
  """
2174
- payload = {"table": table, "columns": columns, "namespace": namespace}
2175
-
2176
- await self.room.send_request("database.drop_columns", payload)
2197
+ request_model = _DropColumnsRequest(
2198
+ table=table, columns=columns, namespace=namespace
2199
+ )
2200
+ await self.room.send_request(
2201
+ "database.drop_columns", request_model.model_dump()
2202
+ )
2177
2203
  return None
2178
2204
 
2179
2205
  async def insert(
@@ -2190,12 +2216,12 @@ class DatabaseClient:
2190
2216
  :param records: The record(s) to insert (list or dict).
2191
2217
  """
2192
2218
 
2193
- payload = {
2194
- "table": table,
2195
- "records": encode_records(records),
2196
- "namespace": namespace,
2197
- }
2198
- await self.room.send_request("database.insert", payload)
2219
+ request_model = _InsertRequest(
2220
+ table=table,
2221
+ records=encode_records(records),
2222
+ namespace=namespace,
2223
+ )
2224
+ await self.room.send_request("database.insert", request_model.model_dump())
2199
2225
 
2200
2226
  async def update(
2201
2227
  self,
@@ -2214,14 +2240,14 @@ class DatabaseClient:
2214
2240
  :param values: Dict of column updates, e.g. {"col1": "new_value"}.
2215
2241
  :param values_sql: Dict of SQL expressions for updates, e.g. {"col2": "col2 + 1"}.
2216
2242
  """
2217
- payload = {
2218
- "table": table,
2219
- "where": where,
2220
- "values": values,
2221
- "values_sql": values_sql,
2222
- "namespace": namespace,
2223
- }
2224
- await self.room.send_request("database.update", payload)
2243
+ request_model = _UpdateRequest(
2244
+ table=table,
2245
+ where=where,
2246
+ values=values,
2247
+ values_sql=values_sql,
2248
+ namespace=namespace,
2249
+ )
2250
+ await self.room.send_request("database.update", request_model.model_dump())
2225
2251
 
2226
2252
  async def delete(
2227
2253
  self, *, table: str, where: str, namespace: Optional[list[str]] = None
@@ -2232,8 +2258,8 @@ class DatabaseClient:
2232
2258
  :param table: Table name.
2233
2259
  :param where: SQL WHERE clause (e.g. "id = 123").
2234
2260
  """
2235
- payload = {"table": table, "where": where, "namespace": namespace}
2236
- await self.room.send_request("database.delete", payload)
2261
+ request_model = _DeleteRequest(table=table, where=where, namespace=namespace)
2262
+ await self.room.send_request("database.delete", request_model.model_dump())
2237
2263
 
2238
2264
  return None
2239
2265
 
@@ -2252,10 +2278,42 @@ class DatabaseClient:
2252
2278
  :param on: Column name to match on (e.g. "id").
2253
2279
  :param records: The record(s) to merge.
2254
2280
  """
2255
- payload = {"table": table, "on": on, "records": records, "namespace": namespace}
2256
- await self.room.send_request("database.merge", payload)
2281
+ request_model = _MergeRequest(
2282
+ table=table, on=on, records=records, namespace=namespace
2283
+ )
2284
+ await self.room.send_request("database.merge", request_model.model_dump())
2257
2285
  return None
2258
2286
 
2287
+ async def sql(
2288
+ self,
2289
+ *,
2290
+ query: str,
2291
+ tables: List[SqlTableReference | str],
2292
+ params: Optional[Dict[str, Any]] = None,
2293
+ ) -> list[Dict[str, Any]]:
2294
+ """
2295
+ Execute a SQL query against one or more tables.
2296
+
2297
+ :param query: SQL statement to execute.
2298
+ :param tables: Tables to register for the query.
2299
+ :param params: Typed parameters for DataFusion parameter binding.
2300
+ """
2301
+ table_refs = [
2302
+ SqlTableReference(name=table) if isinstance(table, str) else table
2303
+ for table in tables
2304
+ ]
2305
+ request_model = _SqlRequest(
2306
+ query=query,
2307
+ tables=table_refs,
2308
+ params=params,
2309
+ )
2310
+ response = await self.room.send_request(
2311
+ "database.sql", request_model.model_dump()
2312
+ )
2313
+ if isinstance(response, JsonResponse):
2314
+ return decode_records(response.json["results"])
2315
+ return []
2316
+
2259
2317
  async def search(
2260
2318
  self,
2261
2319
  *,
@@ -2282,27 +2340,19 @@ class DatabaseClient:
2282
2340
  where = " AND ".join(
2283
2341
  map(lambda x: f"{x} = {json.dumps(where[x])}", where.keys())
2284
2342
  )
2285
- payload = {
2286
- "table": table,
2287
- "where": where,
2288
- "text": text,
2289
- }
2290
- if limit is not None:
2291
- payload["limit"] = limit
2292
-
2293
- if offset is not None:
2294
- payload["offset"] = offset
2295
-
2296
- if select is not None:
2297
- payload["select"] = select
2298
-
2299
- if vector is not None:
2300
- payload["vector"] = vector
2301
-
2302
- if namespace is not None:
2303
- payload["namespace"] = namespace
2304
-
2305
- response = await self.room.send_request("database.search", payload)
2343
+ request_model = _SearchRequest(
2344
+ table=table,
2345
+ where=where,
2346
+ text=text,
2347
+ vector=vector,
2348
+ offset=offset,
2349
+ limit=limit,
2350
+ select=select,
2351
+ namespace=namespace,
2352
+ )
2353
+ response = await self.room.send_request(
2354
+ "database.search", request_model.model_dump()
2355
+ )
2306
2356
  if isinstance(response, JsonResponse):
2307
2357
  return decode_records(response.json["results"])
2308
2358
  return []
@@ -2328,18 +2378,16 @@ class DatabaseClient:
2328
2378
  where = " AND ".join(
2329
2379
  map(lambda x: f"{x} = {json.dumps(where[x])}", where.keys())
2330
2380
  )
2331
- payload = {
2332
- "table": table,
2333
- "where": where,
2334
- "text": text,
2335
- }
2336
- if vector is not None:
2337
- payload["vector"] = vector
2338
-
2339
- if namespace is not None:
2340
- payload["namespace"] = namespace
2341
-
2342
- response = await self.room.send_request("database.count", payload)
2381
+ request_model = _CountRequest(
2382
+ table=table,
2383
+ where=where,
2384
+ text=text,
2385
+ vector=vector,
2386
+ namespace=namespace,
2387
+ )
2388
+ response = await self.room.send_request(
2389
+ "database.count", request_model.model_dump()
2390
+ )
2343
2391
  if isinstance(response, JsonResponse):
2344
2392
  return response.json["count"]
2345
2393
  return []
@@ -2352,11 +2400,8 @@ class DatabaseClient:
2352
2400
 
2353
2401
  :param table: Table name.
2354
2402
  """
2355
- payload = {
2356
- "table": table,
2357
- "namespace": namespace,
2358
- }
2359
- await self.room.send_request("database.optimize", payload)
2403
+ request_model = _OptimizeRequest(table=table, namespace=namespace)
2404
+ await self.room.send_request("database.optimize", request_model.model_dump())
2360
2405
  return None
2361
2406
 
2362
2407
  async def restore(
@@ -2367,12 +2412,10 @@ class DatabaseClient:
2367
2412
 
2368
2413
  :param table: Table name.
2369
2414
  """
2370
- payload = {
2371
- "table": table,
2372
- "version": version,
2373
- "namespace": namespace,
2374
- }
2375
- await self.room.send_request("database.restore", payload)
2415
+ request_model = _RestoreRequest(
2416
+ table=table, version=version, namespace=namespace
2417
+ )
2418
+ await self.room.send_request("database.restore", request_model.model_dump())
2376
2419
  return None
2377
2420
 
2378
2421
  async def checkout(
@@ -2383,12 +2426,10 @@ class DatabaseClient:
2383
2426
 
2384
2427
  :param table: Table name.
2385
2428
  """
2386
- payload = {
2387
- "table": table,
2388
- "version": version,
2389
- "namespace": namespace,
2390
- }
2391
- await self.room.send_request("database.checkout", payload)
2429
+ request_model = _CheckoutRequest(
2430
+ table=table, version=version, namespace=namespace
2431
+ )
2432
+ await self.room.send_request("database.checkout", request_model.model_dump())
2392
2433
  return None
2393
2434
 
2394
2435
  async def list_versions(
@@ -2399,11 +2440,10 @@ class DatabaseClient:
2399
2440
 
2400
2441
  :param table: Table name.
2401
2442
  """
2402
- payload = {
2403
- "table": table,
2404
- "namespace": namespace,
2405
- }
2406
- resp = await self.room.send_request("database.list_versions", payload)
2443
+ request_model = _ListVersionsRequest(table=table, namespace=namespace)
2444
+ resp = await self.room.send_request(
2445
+ "database.list_versions", request_model.model_dump()
2446
+ )
2407
2447
  return [TableVersion.model_validate(v) for v in resp.json["versions"]]
2408
2448
 
2409
2449
  async def create_vector_index(
@@ -2420,13 +2460,15 @@ class DatabaseClient:
2420
2460
  :param table: Table name.
2421
2461
  :param column: Vector column name.
2422
2462
  """
2423
- payload = {
2424
- "table": table,
2425
- "column": column,
2426
- "replace": replace,
2427
- "namespace": namespace,
2428
- }
2429
- await self.room.send_request("database.create_vector_index", payload)
2463
+ request_model = _CreateVectorIndexRequest(
2464
+ table=table,
2465
+ column=column,
2466
+ replace=replace,
2467
+ namespace=namespace,
2468
+ )
2469
+ await self.room.send_request(
2470
+ "database.create_vector_index", request_model.model_dump()
2471
+ )
2430
2472
  return None
2431
2473
 
2432
2474
  async def create_scalar_index(
@@ -2443,13 +2485,15 @@ class DatabaseClient:
2443
2485
  :param table: Table name.
2444
2486
  :param column: Column name.
2445
2487
  """
2446
- payload = {
2447
- "table": table,
2448
- "column": column,
2449
- "replace": replace,
2450
- "namespace": namespace,
2451
- }
2452
- await self.room.send_request("database.create_scalar_index", payload)
2488
+ request_model = _CreateScalarIndexRequest(
2489
+ table=table,
2490
+ column=column,
2491
+ replace=replace,
2492
+ namespace=namespace,
2493
+ )
2494
+ await self.room.send_request(
2495
+ "database.create_scalar_index", request_model.model_dump()
2496
+ )
2453
2497
  return None
2454
2498
 
2455
2499
  async def create_full_text_search_index(
@@ -2466,13 +2510,16 @@ class DatabaseClient:
2466
2510
  :param table: Table name.
2467
2511
  :param column: Text column name.
2468
2512
  """
2469
- payload = {
2470
- "table": table,
2471
- "column": column,
2472
- "replace": replace,
2473
- "namespace": namespace,
2474
- }
2475
- await self.room.send_request("database.create_full_text_search_index", payload)
2513
+ request_model = _CreateFullTextSearchIndexRequest(
2514
+ table=table,
2515
+ column=column,
2516
+ replace=replace,
2517
+ namespace=namespace,
2518
+ )
2519
+ await self.room.send_request(
2520
+ "database.create_full_text_search_index",
2521
+ request_model.model_dump(),
2522
+ )
2476
2523
  return None
2477
2524
 
2478
2525
  async def list_indexes(
@@ -2483,9 +2530,11 @@ class DatabaseClient:
2483
2530
 
2484
2531
  :param table: Table name.
2485
2532
  """
2486
- payload = {"table": table, "namespace": namespace}
2487
- response = await self.room.send_request("database.list_indexes", payload)
2488
- if hasattr(response, "json"):
2533
+ request_model = _ListIndexesRequest(table=table, namespace=namespace)
2534
+ response = await self.room.send_request(
2535
+ "database.list_indexes", request_model.model_dump()
2536
+ )
2537
+ if isinstance(response, JsonResponse):
2489
2538
  return [TableIndex.model_validate(i) for i in response.json["indexes"]]
2490
2539
 
2491
2540
  raise RoomException("unexpected return type")
@@ -2623,8 +2672,7 @@ class RoomContainer(BaseModel):
2623
2672
  service_id: Optional[str] = None
2624
2673
 
2625
2674
  # Accept arbitrary extras (names, created, state, etc.)
2626
- class Config:
2627
- extra = "allow"
2675
+ model_config = ConfigDict(extra="allow")
2628
2676
 
2629
2677
 
2630
2678
  # ---------------------------
@@ -3176,6 +3224,7 @@ class _SetSecretRequest(BaseModel):
3176
3224
  type: Optional[str] = Field(default=None)
3177
3225
  name: Optional[str] = Field(default=None)
3178
3226
  delegated_to: Optional[str] = Field(default=None)
3227
+ for_identity: Optional[str] = Field(default=None)
3179
3228
 
3180
3229
 
3181
3230
  class _GetSecretRequest(BaseModel):
@@ -3438,6 +3487,7 @@ class SecretsClient:
3438
3487
  type: Optional[str] = None,
3439
3488
  name: Optional[str] = None,
3440
3489
  delegated_to: Optional[str] = None,
3490
+ for_identity: Optional[str] = None,
3441
3491
  data: Optional[bytes] = None,
3442
3492
  ) -> None:
3443
3493
  """
@@ -3448,6 +3498,7 @@ class SecretsClient:
3448
3498
  type=type,
3449
3499
  name=name,
3450
3500
  delegated_to=delegated_to,
3501
+ for_identity=for_identity,
3451
3502
  )
3452
3503
 
3453
3504
  response = await self.room.send_request(