88from typing import TYPE_CHECKING , Generic , Literal , overload
99from warnings import warn
1010
11+ import msgspec
1112from httpx import HTTPStatusError
12- from pydantic import BaseModel , TypeAdapter
13- from pydantic import JsonValue as DataType
1413from typing_extensions import deprecated
1514
1615from contiguity ._auth import get_data_key , get_project_id
1716from contiguity ._client import ApiError , AsyncApiClient
1817
1918from .common import (
2019 UNSET ,
20+ DataType ,
2121 DefaultItemT ,
2222 ItemT ,
2323 QueryResponse ,
3333
3434if TYPE_CHECKING :
3535 from httpx import Response as HttpxResponse
36- from typing_extensions import Self
3736
3837
3938class AsyncBase (Generic [ItemT ]):
@@ -42,7 +41,7 @@ class AsyncBase(Generic[ItemT]):
4241
4342 @overload
4443 def __init__ (
45- self : Self ,
44+ self ,
4645 name : str ,
4746 / ,
4847 * ,
@@ -57,7 +56,7 @@ def __init__(
5756 @overload
5857 @deprecated ("The `project_key` parameter has been renamed to `data_key`." )
5958 def __init__ (
60- self : Self ,
59+ self ,
6160 name : str ,
6261 / ,
6362 * ,
@@ -70,7 +69,7 @@ def __init__(
7069 ) -> None : ...
7170
7271 def __init__ ( # noqa: PLR0913
73- self : Self ,
72+ self ,
7473 name : str ,
7574 / ,
7675 * ,
@@ -80,7 +79,7 @@ def __init__( # noqa: PLR0913
8079 project_id : str | None = None ,
8180 host : str | None = None ,
8281 api_version : str = "v1" ,
83- json_decoder : type [json .JSONDecoder ] = json .JSONDecoder , # Only used when item_type is not a Pydantic model .
82+ json_decoder : type [json .JSONDecoder ] = json .JSONDecoder , # Only used when item_type is not a msgspec struct .
8483 ) -> None :
8584 if not name :
8685 msg = f"invalid Base name '{ name } '"
@@ -102,23 +101,23 @@ def __init__( # noqa: PLR0913
102101
103102 @overload
104103 def _response_as_item_type (
105- self : Self ,
104+ self ,
106105 response : HttpxResponse ,
107106 / ,
108107 * ,
109108 sequence : Literal [False ] = False ,
110109 ) -> ItemT : ...
111110 @overload
112111 def _response_as_item_type (
113- self : Self ,
112+ self ,
114113 response : HttpxResponse ,
115114 / ,
116115 * ,
117116 sequence : Literal [True ] = True ,
118117 ) -> Sequence [ItemT ]: ...
119118
120119 def _response_as_item_type (
121- self : Self ,
120+ self ,
122121 response : HttpxResponse ,
123122 / ,
124123 * ,
@@ -130,12 +129,12 @@ def _response_as_item_type(
130129 raise ApiError (exc .response .text ) from exc
131130 if self .item_type :
132131 if sequence :
133- return TypeAdapter ( Sequence [self .item_type ]). validate_json ( response . content )
134- return TypeAdapter ( self . item_type ). validate_json (response .content )
132+ return msgspec . json . decode ( response . content , type = Sequence [self .item_type ])
133+ return msgspec . json . decode (response .content , type = self . item_type )
135134 return response .json (cls = self .json_decoder )
136135
137136 def _insert_expires_attr (
138- self : Self ,
137+ self ,
139138 item : ItemT | Mapping [str , DataType ],
140139 expire_in : int | None = None ,
141140 expire_at : TimestampType | None = None ,
@@ -144,7 +143,7 @@ def _insert_expires_attr(
144143 msg = "cannot use both expire_in and expire_at"
145144 raise ValueError (msg )
146145
147- item_dict = item . model_dump ( ) if isinstance (item , BaseModel ) else dict (item )
146+ item_dict = msgspec . structs . asdict ( item ) if isinstance (item , msgspec . Struct ) else dict (item )
148147
149148 if not expire_in and not expire_at :
150149 return item_dict
@@ -160,16 +159,16 @@ def _insert_expires_attr(
160159 return item_dict
161160
162161 @overload
163- async def get (self : Self , key : str , / ) -> ItemT | None : ...
162+ async def get (self , key : str , / ) -> ItemT | None : ...
164163
165164 @overload
166- async def get (self : Self , key : str , / , * , default : ItemT ) -> ItemT : ...
165+ async def get (self , key : str , / , * , default : ItemT ) -> ItemT : ...
167166
168167 @overload
169- async def get (self : Self , key : str , / , * , default : DefaultItemT ) -> ItemT | DefaultItemT : ...
168+ async def get (self , key : str , / , * , default : DefaultItemT ) -> ItemT | DefaultItemT : ...
170169
171170 async def get (
172- self : Self ,
171+ self ,
173172 key : str ,
174173 / ,
175174 * ,
@@ -189,7 +188,7 @@ async def get(
189188
190189 return self ._response_as_item_type (response , sequence = False )
191190
192- async def delete (self : Self , key : str , / ) -> None :
191+ async def delete (self , key : str , / ) -> None :
193192 """Delete an item from the Base."""
194193 key = check_key (key )
195194 response = await self ._client .delete (f"/items/{ key } " )
@@ -199,7 +198,7 @@ async def delete(self: Self, key: str, /) -> None:
199198 raise ApiError (exc .response .text ) from exc
200199
201200 async def insert (
202- self : Self ,
201+ self ,
203202 item : ItemT ,
204203 / ,
205204 * ,
@@ -218,7 +217,7 @@ async def insert(
218217 return returned_item [0 ]
219218
220219 async def put (
221- self : Self ,
220+ self ,
222221 * items : ItemT ,
223222 expire_in : int | None = None ,
224223 expire_at : TimestampType | None = None ,
@@ -239,7 +238,7 @@ async def put(
239238
240239 @deprecated ("This method will be removed in a future release. You can pass multiple items to `put`." )
241240 async def put_many (
242- self : Self ,
241+ self ,
243242 items : Sequence [ItemT ],
244243 / ,
245244 * ,
@@ -249,7 +248,7 @@ async def put_many(
249248 return await self .put (* items , expire_in = expire_in , expire_at = expire_at )
250249
251250 async def update (
252- self : Self ,
251+ self ,
253252 updates : Mapping [str , DataType | UpdateOperation ],
254253 / ,
255254 * ,
@@ -273,14 +272,14 @@ async def update(
273272 expire_at = expire_at ,
274273 )
275274
276- response = await self ._client .patch (f"/items/{ key } " , json = {"updates" : payload . model_dump ( )})
275+ response = await self ._client .patch (f"/items/{ key } " , json = {"updates" : msgspec . structs . asdict ( payload )})
277276 if response .status_code == HTTPStatus .NOT_FOUND :
278277 raise ItemNotFoundError (key )
279278
280279 return self ._response_as_item_type (response , sequence = False )
281280
282281 async def query (
283- self : Self ,
282+ self ,
284283 * queries : QueryType ,
285284 limit : int = 1000 ,
286285 last : str | None = None ,
@@ -302,15 +301,11 @@ async def query(
302301 response .raise_for_status ()
303302 except HTTPStatusError as exc :
304303 raise ApiError (exc .response .text ) from exc
305- query_response = QueryResponse [ItemT ].model_validate_json (response .content )
306- if self .item_type :
307- # HACK: Pydantic model_validate_json doesn't validate Sequence[ItemT] properly. # noqa: FIX004
308- query_response .items = TypeAdapter (Sequence [self .item_type ]).validate_python (query_response .items )
309- return query_response
304+ return msgspec .json .decode (response .content , type = QueryResponse [ItemT ])
310305
311306 @deprecated ("This method has been renamed to `query` and will be removed in a future release." )
312307 async def fetch (
313- self : Self ,
308+ self ,
314309 * queries : QueryType ,
315310 limit : int = 1000 ,
316311 last : str | None = None ,
0 commit comments