2525
2626
2727class CursorPage (Generic [T ]):
28- """Synchronous page over an opaque ``page`` cursor."""
28+ """Synchronous page over an opaque ``page`` cursor.
29+
30+ ``extra`` preserves any additional top-level fields the list
31+ envelope carried beyond ``data`` / ``next_page`` (forward
32+ compatibility when the server adds fields); ``extra`` is per page
33+ and does not participate in auto-pagination.
34+ """
2935
3036 def __init__ (
3137 self ,
@@ -34,11 +40,13 @@ def __init__(
3440 next_page : Optional [str ],
3541 request_id : Optional [str ],
3642 fetch_next : Optional [Callable [[str ], "CursorPage[T]" ]] = None ,
43+ extra : Optional [Dict [str , Any ]] = None ,
3744 ) -> None :
3845 self .data = data
3946 self .next_page = next_page
4047 self .request_id = request_id
4148 self ._fetch_next = fetch_next
49+ self .extra : Dict [str , Any ] = dict (extra or {})
4250
4351 def has_next (self ) -> bool :
4452 return bool (self .next_page ) and self ._fetch_next is not None
@@ -66,9 +74,20 @@ def __len__(self) -> int:
6674 """
6775 return len (self .data )
6876
77+ @property
78+ def prefixes (self ) -> List [str ]:
79+ """Directory prefixes of a tree-style listing (memory only)."""
80+
81+ raw = self .extra .get ("prefixes" ) or []
82+ return list (raw ) if isinstance (raw , (list , tuple )) else []
83+
6984
7085class AsyncCursorPage (Generic [T ]):
71- """Asynchronous page over an opaque ``page`` cursor."""
86+ """Asynchronous page over an opaque ``page`` cursor.
87+
88+ See :class:`CursorPage` for the ``extra`` forward-compatibility
89+ bag.
90+ """
7291
7392 def __init__ (
7493 self ,
@@ -77,11 +96,13 @@ def __init__(
7796 next_page : Optional [str ],
7897 request_id : Optional [str ],
7998 fetch_next : Optional [Callable [[str ], "AsyncCursorPage[T]" ]] = None ,
99+ extra : Optional [Dict [str , Any ]] = None ,
80100 ) -> None :
81101 self .data = data
82102 self .next_page = next_page
83103 self .request_id = request_id
84104 self ._fetch_next = fetch_next
105+ self .extra : Dict [str , Any ] = dict (extra or {})
85106
86107 def has_next (self ) -> bool :
87108 return bool (self .next_page ) and self ._fetch_next is not None
@@ -115,6 +136,13 @@ def __len__(self) -> int:
115136 """
116137 return len (self .data )
117138
139+ @property
140+ def prefixes (self ) -> List [str ]:
141+ """Directory prefixes of a tree-style listing (memory only)."""
142+
143+ raw = self .extra .get ("prefixes" ) or []
144+ return list (raw ) if isinstance (raw , (list , tuple )) else []
145+
118146
119147def build_page (
120148 * ,
@@ -124,16 +152,25 @@ def build_page(
124152 request_id : Optional [str ] = None ,
125153 page_cls : type = CursorPage ,
126154) -> Any :
127- """Hydrate a page object from a normalized payload."""
155+ """Hydrate a page object from a normalized payload.
156+
157+ Envelope fields beyond ``data`` / ``next_page`` are preserved on
158+ the page's ``extra`` forward-compatibility bag (mirrors
159+ ``BaseModel.extra``).
160+ """
128161
129162 items_raw = payload .get ("data" ) or []
130163 items : List [T ] = [
131164 item_factory (it ) for it in items_raw if isinstance (it , dict )
132165 ]
133166 next_page = payload .get ("next_page" )
167+ extra = {
168+ k : v for k , v in payload .items () if k not in ("data" , "next_page" )
169+ }
134170 return page_cls (
135171 data = items ,
136172 next_page = next_page ,
137173 request_id = request_id ,
138174 fetch_next = fetch_next ,
175+ extra = extra ,
139176 )
0 commit comments