@@ -74,10 +74,27 @@ def pack_stream(
7474 * ,
7575 float32 : bool = False ,
7676 ext_hook : Callable [[object ], ExtType | None ] | None = None ,
77+ max_depth : int = - 1 ,
7778) -> None :
79+ """Pack object into stream."""
80+ _pack_stream (stream , obj , float32 , ext_hook , max_depth )
81+
82+
83+ def _pack_stream (
84+ stream : BinaryIO ,
85+ obj : object ,
86+ float32 : bool = False ,
87+ ext_hook : Callable [[object ], ExtType | None ] | None = None ,
88+ max_depth : int = - 1 ,
89+ / , # perf: avoid kwargs overhead in recursive calls
90+ ) -> None :
91+ if max_depth == 0 :
92+ raise RecursionError ("max depth exceeded" )
93+ max_depth -= 1
94+
7895 _type = type (obj )
7996 if _type is int : # int
80- i = obj
97+ i : int = obj # type: ignore
8198 if 0 <= i <= 0x7F : # positive fixint
8299 stream .write (_B [i ])
83100 elif - 32 <= i < 0 : # negative fixint
@@ -114,7 +131,7 @@ def pack_stream(
114131 elif _type is bool : # true / false
115132 stream .write (b"\xc3 " if obj else b"\xc2 " )
116133 elif _type is str : # str
117- s = obj .encode ("utf-8" )
134+ s : bytes = obj .encode ("utf-8" ) # type: ignore
118135 sl = len (s )
119136 if sl <= 0x1F : # fixstr
120137 stream .write (_B [0xA0 | sl ])
@@ -128,7 +145,7 @@ def pack_stream(
128145 raise ValueError ("str too large" , obj )
129146 stream .write (s )
130147 elif _type is bytes : # bin
131- bl = len (obj )
148+ bl = len (obj ) # type: ignore
132149 if bl <= 0xFF : # bin8
133150 stream .write (b"\xc4 " + _B [bl ])
134151 elif bl <= 0xFF_FF : # bin16
@@ -137,9 +154,9 @@ def pack_stream(
137154 stream .write (b"\xc6 " + u32_b_pack (bl ))
138155 else :
139156 raise ValueError ("bin too large" , obj )
140- stream .write (obj )
157+ stream .write (obj ) # type: ignore
141158 elif _type is dict : # map
142- ml = len (obj )
159+ ml = len (obj ) # type: ignore
143160 if ml <= 0x0F : # fixmap
144161 stream .write (_B [0x80 | ml ])
145162 elif ml <= 0xFF_FF : # map16
@@ -148,11 +165,11 @@ def pack_stream(
148165 stream .write (b"\xdf " + u32_b_pack (ml ))
149166 else :
150167 raise ValueError ("map too large" , obj )
151- for k , v in obj .items ():
152- pack_stream (stream , k , float32 = float32 , ext_hook = ext_hook )
153- pack_stream (stream , v , float32 = float32 , ext_hook = ext_hook )
168+ for k , v in obj .items (): # type: ignore
169+ _pack_stream (stream , k , float32 , ext_hook , max_depth )
170+ _pack_stream (stream , v , float32 , ext_hook , max_depth )
154171 elif _type is list : # array
155- al = len (obj )
172+ al = len (obj ) # type: ignore
156173 if al <= 0x0F : # fixarray
157174 stream .write (_B [0x90 | al ])
158175 elif al <= 0xFF_FF : # array16
@@ -161,13 +178,13 @@ def pack_stream(
161178 stream .write (b"\xdd " + u32_b_pack (al ))
162179 else :
163180 raise ValueError ("array too large" , obj )
164- for v in obj :
165- pack_stream (stream , v , float32 = float32 , ext_hook = ext_hook )
181+ for v in obj : # type: ignore
182+ _pack_stream (stream , v , float32 , ext_hook , max_depth )
166183 elif _type is datetime : # timestamp
167- if obj .tzinfo is None :
184+ if obj .tzinfo is None : # type: ignore
168185 raise ValueError ("datetime object must be timezone-aware" , obj )
169186
170- seconds_from_epoch = obj .timestamp ()
187+ seconds_from_epoch = obj .timestamp () # type: ignore
171188 # floor rather than int (handles negative timestamps correctly)
172189 seconds = floor (seconds_from_epoch )
173190 nanoseconds = int ((seconds_from_epoch - seconds ) * 1_000_000_000 )
@@ -184,8 +201,8 @@ def pack_stream(
184201 # timestamp96
185202 stream .write (b"\xc7 \x0c \xff " + u32_b_pack (nanoseconds ) + s64_b_pack (seconds ))
186203 elif _type is ExtType : # ext
187- data = obj .data
188- p_code = s8_b_pack (obj .code )
204+ data : bytes = obj .data # type: ignore
205+ p_code = s8_b_pack (obj .code ) # type: ignore
189206 extl = len (data )
190207 if extl <= 16 and extl in _PO2 : # fixext (0xD4 - 0xD8)
191208 stream .write (_PO2 [extl ] + p_code )
@@ -204,7 +221,7 @@ def pack_stream(
204221 if result is not None :
205222 # pack the ext type (doesn't exactly need to be an ExtType)
206223 # if the same type is returned it will cause infinite recursion
207- pack_stream (stream , result , float32 = float32 , ext_hook = ext_hook )
224+ _pack_stream (stream , result , float32 , ext_hook , max_depth )
208225 return
209226 raise TypeError ("unsupported type" , _type , obj )
210227
@@ -213,7 +230,22 @@ def unpack_stream(
213230 stream : BinaryIO ,
214231 * ,
215232 ext_hook : Callable [[ExtType ], object | None ] | None = None ,
233+ max_depth : int = - 1 ,
216234) -> object :
235+ """Unpack object from stream."""
236+ return _unpack_stream (stream , ext_hook , max_depth )
237+
238+
239+ def _unpack_stream (
240+ stream : BinaryIO ,
241+ ext_hook : Callable [[ExtType ], object | None ] | None = None ,
242+ max_depth : int = - 1 ,
243+ / , # perf: avoid kwargs overhead in recursive calls
244+ ) -> object :
245+ if max_depth == 0 :
246+ raise RecursionError ("max depth exceeded" )
247+ max_depth -= 1
248+
217249 b = stream .read (1 )
218250 first_byte = b [0 ]
219251 if first_byte <= 0x7F : # positive fixint
@@ -223,12 +255,12 @@ def unpack_stream(
223255 elif first_byte <= 0x8F : # fixmap
224256 ml = first_byte & 0x0F
225257 obj = {
226- unpack_stream (stream , ext_hook = ext_hook ): unpack_stream (stream , ext_hook = ext_hook )
258+ _unpack_stream (stream , ext_hook , max_depth ): _unpack_stream (stream , ext_hook , max_depth )
227259 for _ in range (ml )
228260 }
229261 elif first_byte <= 0x9F : # fixarray
230262 al = first_byte & 0x0F
231- obj = [unpack_stream (stream , ext_hook = ext_hook ) for _ in range (al )]
263+ obj = [_unpack_stream (stream , ext_hook , max_depth ) for _ in range (al )]
232264 elif first_byte <= 0xBF : # fixstr
233265 sl = first_byte & 0x1F
234266 obj = stream .read (sl ).decode ("utf-8" )
@@ -337,14 +369,14 @@ def unpack_stream(
337369 al = u16_b_unpack (stream ) # array16
338370 else :
339371 al = u32_b_unpack (stream ) # array32
340- obj = [unpack_stream (stream , ext_hook = ext_hook ) for _ in range (al )]
372+ obj = [_unpack_stream (stream , ext_hook , max_depth ) for _ in range (al )]
341373 elif first_byte <= 0xDF : # map
342374 if first_byte == 0xDE :
343375 ml = u16_b_unpack (stream ) # map16
344376 else :
345377 ml = u32_b_unpack (stream ) # map32
346378 obj = {
347- unpack_stream (stream , ext_hook = ext_hook ): unpack_stream (stream , ext_hook = ext_hook )
379+ _unpack_stream (stream , ext_hook , max_depth ): _unpack_stream (stream , ext_hook , max_depth )
348380 for _ in range (ml )
349381 }
350382 else :
0 commit comments