rtbnext 1.0.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.
- rtbnext/__init__.py +26 -0
- rtbnext/_version.py +7 -0
- rtbnext/core/cache.py +102 -0
- rtbnext/core/http_client.py +153 -0
- rtbnext/core/loader.py +173 -0
- rtbnext/core/parser.py +84 -0
- rtbnext/core/rate_limiter.py +86 -0
- rtbnext/defaults.py +26 -0
- rtbnext/endpoint/base.py +80 -0
- rtbnext/endpoint/filter.py +112 -0
- rtbnext/endpoint/list.py +85 -0
- rtbnext/endpoint/mover.py +33 -0
- rtbnext/endpoint/profile.py +158 -0
- rtbnext/endpoint/stats.py +156 -0
- rtbnext/endpoint/system.py +25 -0
- rtbnext/py.typed +0 -0
- rtbnext/resource/base.py +206 -0
- rtbnext/resource/collectable.py +201 -0
- rtbnext/resource/collection.py +313 -0
- rtbnext/resource/dateable.py +59 -0
- rtbnext/resource/indexable.py +91 -0
- rtbnext/resource/series.py +266 -0
- rtbnext/rtbnext.py +107 -0
- rtbnext/schema/assets.py +101 -0
- rtbnext/schema/filter.py +35 -0
- rtbnext/schema/generic.py +105 -0
- rtbnext/schema/list.py +64 -0
- rtbnext/schema/mover.py +40 -0
- rtbnext/schema/profile.py +137 -0
- rtbnext/schema/stats.py +133 -0
- rtbnext/schema/system.py +15 -0
- rtbnext/utils.py +36 -0
- rtbnext-1.0.0.dist-info/METADATA +161 -0
- rtbnext-1.0.0.dist-info/RECORD +36 -0
- rtbnext-1.0.0.dist-info/WHEEL +4 -0
- rtbnext-1.0.0.dist-info/licenses/LICENSE +21 -0
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Base Collections
|
|
3
|
+
|
|
4
|
+
Implements the base collections shared by resource collections.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Callable, Generic, Iterator, Self, TypeVar, cast
|
|
10
|
+
|
|
11
|
+
from rtbnext.defaults import DEFAULT_PER_PAGE
|
|
12
|
+
from rtbnext.utils import ymd
|
|
13
|
+
|
|
14
|
+
R = TypeVar( "R" )
|
|
15
|
+
T = TypeVar( "T" )
|
|
16
|
+
U = TypeVar( "U" )
|
|
17
|
+
|
|
18
|
+
type ItemFactory[ T, R ] = Callable[ [ T ], R ]
|
|
19
|
+
type DateResolver[ T ] = Callable[ [ T ], str ]
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class CollectionBase( Generic[ T, R ] ):
|
|
23
|
+
"""
|
|
24
|
+
Provides common immutable collection operations.
|
|
25
|
+
|
|
26
|
+
This class serves as a base for resource collections, providing methods to
|
|
27
|
+
manipulate and access the underlying items while maintaining immutability.
|
|
28
|
+
"""
|
|
29
|
+
|
|
30
|
+
def __init__(
|
|
31
|
+
self, items: list[ T ], *,
|
|
32
|
+
factory: ItemFactory[ T, R ] = lambda item: cast( R, item ),
|
|
33
|
+
total: int | None = None
|
|
34
|
+
) -> None:
|
|
35
|
+
self._items, self._factory = items, factory
|
|
36
|
+
self._total = len( items ) if total is None else total
|
|
37
|
+
|
|
38
|
+
def _clone( self, items: list[ T ] ) -> Self:
|
|
39
|
+
"""Creates a new collection instance with replaced items."""
|
|
40
|
+
|
|
41
|
+
return self.__class__( items, factory= self._factory, total= self._total )
|
|
42
|
+
|
|
43
|
+
def __getitem__( self, index: int ) -> Self | R:
|
|
44
|
+
return (
|
|
45
|
+
self._clone( self._items[ index ] )
|
|
46
|
+
if isinstance( index, slice )
|
|
47
|
+
else self._factory( self._items[ index ] )
|
|
48
|
+
)
|
|
49
|
+
|
|
50
|
+
def __contains__( self, item: object ) -> bool:
|
|
51
|
+
return item in self._items
|
|
52
|
+
|
|
53
|
+
def __len__( self ) -> int:
|
|
54
|
+
return self.count
|
|
55
|
+
|
|
56
|
+
def __iter__( self ) -> Iterator[ R ]:
|
|
57
|
+
return map( self._factory, self._items )
|
|
58
|
+
|
|
59
|
+
def __reversed__( self ) -> Iterator[ R ]:
|
|
60
|
+
return map( self._factory, self._items[ ::-1 ] )
|
|
61
|
+
|
|
62
|
+
@property
|
|
63
|
+
def items( self ) -> list[ T ]:
|
|
64
|
+
"""Returns the raw items."""
|
|
65
|
+
|
|
66
|
+
return self._items.copy()
|
|
67
|
+
|
|
68
|
+
@property
|
|
69
|
+
def total( self ) -> int:
|
|
70
|
+
"""Returns the total number of available items."""
|
|
71
|
+
|
|
72
|
+
return self._total
|
|
73
|
+
|
|
74
|
+
@property
|
|
75
|
+
def count( self ) -> int:
|
|
76
|
+
"""Returns the number of items currently contained."""
|
|
77
|
+
|
|
78
|
+
return len( self._items )
|
|
79
|
+
|
|
80
|
+
@property
|
|
81
|
+
def first( self ) -> R | None:
|
|
82
|
+
"""Returns the first item."""
|
|
83
|
+
|
|
84
|
+
return None if not self._items else self._factory( self._items[ 0 ] )
|
|
85
|
+
|
|
86
|
+
@property
|
|
87
|
+
def last( self ) -> R | None:
|
|
88
|
+
"""Returns the last item."""
|
|
89
|
+
|
|
90
|
+
return None if not self._items else self._factory( self._items[ -1 ] )
|
|
91
|
+
|
|
92
|
+
def to_array( self ) -> list[ R ]:
|
|
93
|
+
"""Returns all resolved items as list."""
|
|
94
|
+
|
|
95
|
+
return [ self._factory( item ) for item in self._items ]
|
|
96
|
+
|
|
97
|
+
def map( self, callback: Callable[ [ R, int ], U ] ) -> list[ U ]:
|
|
98
|
+
"""Maps resolved items."""
|
|
99
|
+
|
|
100
|
+
return [ callback( item, index ) for index, item in enumerate( self ) ]
|
|
101
|
+
|
|
102
|
+
def reversed( self ) -> Self:
|
|
103
|
+
"""Returns a collection containing reversed items."""
|
|
104
|
+
|
|
105
|
+
return self._clone( self._items[ ::-1 ] )
|
|
106
|
+
|
|
107
|
+
def take( self, count: int ) -> Self:
|
|
108
|
+
"""Returns a collection containing the first items."""
|
|
109
|
+
|
|
110
|
+
return self._clone( self._items[ : count ] )
|
|
111
|
+
|
|
112
|
+
def skip( self, count: int ) -> Self:
|
|
113
|
+
"""Returns a collection without the first items."""
|
|
114
|
+
|
|
115
|
+
return self._clone( self._items[ count : ] )
|
|
116
|
+
|
|
117
|
+
def slice( self, start: int | None = None, end: int | None = None ) -> Self:
|
|
118
|
+
"""Returns a collection containing a sliced range of items."""
|
|
119
|
+
|
|
120
|
+
return self._clone( self._items[ start : end ] )
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
class CursorCollectionBase( CollectionBase[ T, R ], Generic[ T, R ] ):
|
|
124
|
+
"""
|
|
125
|
+
Provides cursor-related immutable collection operations.
|
|
126
|
+
|
|
127
|
+
This class extends the base collection adding methods to handle cursor
|
|
128
|
+
operations for items.
|
|
129
|
+
"""
|
|
130
|
+
|
|
131
|
+
def __init__(
|
|
132
|
+
self, items: list[ T ], *,
|
|
133
|
+
factory: ItemFactory[ T, R ] = lambda item: cast( R, item ),
|
|
134
|
+
total: int | None = None
|
|
135
|
+
) -> None:
|
|
136
|
+
super().__init__( items, factory= factory, total= total )
|
|
137
|
+
self._cursor: int = -1
|
|
138
|
+
|
|
139
|
+
@property
|
|
140
|
+
def position( self ) -> int:
|
|
141
|
+
"""Returns the current cursor position."""
|
|
142
|
+
|
|
143
|
+
return self._cursor
|
|
144
|
+
|
|
145
|
+
@property
|
|
146
|
+
def current( self ) -> R | None:
|
|
147
|
+
"""Returns the current resolved item."""
|
|
148
|
+
|
|
149
|
+
return self.at( self._cursor )
|
|
150
|
+
|
|
151
|
+
@property
|
|
152
|
+
def next( self ) -> R | None:
|
|
153
|
+
"""Returns the next resolved item."""
|
|
154
|
+
|
|
155
|
+
self._cursor += 1
|
|
156
|
+
return self.at( self._cursor )
|
|
157
|
+
|
|
158
|
+
@property
|
|
159
|
+
def prev( self ) -> R | None:
|
|
160
|
+
"""Returns the previous resolved item."""
|
|
161
|
+
|
|
162
|
+
self._cursor -= 1
|
|
163
|
+
return self.at( self._cursor )
|
|
164
|
+
|
|
165
|
+
@property
|
|
166
|
+
def has_next( self ) -> bool:
|
|
167
|
+
"""Returns whether a next item exists."""
|
|
168
|
+
|
|
169
|
+
return self._cursor + 1 < self.count
|
|
170
|
+
|
|
171
|
+
@property
|
|
172
|
+
def has_prev( self ) -> bool:
|
|
173
|
+
"""Returns whether a previous item exists."""
|
|
174
|
+
|
|
175
|
+
return self._cursor > 0
|
|
176
|
+
|
|
177
|
+
def reset( self ) -> Self:
|
|
178
|
+
"""Resets the cursor."""
|
|
179
|
+
|
|
180
|
+
self._cursor = -1
|
|
181
|
+
return self
|
|
182
|
+
|
|
183
|
+
def seek( self, pos: int ) -> Self:
|
|
184
|
+
"""Moves the cursor to a specific index."""
|
|
185
|
+
|
|
186
|
+
self._cursor = pos
|
|
187
|
+
return self
|
|
188
|
+
|
|
189
|
+
def at( self, index: int ) -> R | None:
|
|
190
|
+
"""Returns the resolved item at the given index."""
|
|
191
|
+
|
|
192
|
+
return self._factory( self._items[ index ] ) if 0 <= index < self.count else None
|
|
193
|
+
|
|
194
|
+
def page( self, page: int, per_page: int = DEFAULT_PER_PAGE ) -> Self:
|
|
195
|
+
"""Returns a collection containing one page."""
|
|
196
|
+
|
|
197
|
+
start = max( page - 1, 0 ) * per_page
|
|
198
|
+
return self._clone( self._items[ start : start + per_page ] )
|
|
199
|
+
|
|
200
|
+
def pages( self, per_page: int = DEFAULT_PER_PAGE ) -> list[ Self ]:
|
|
201
|
+
"""Returns all pages."""
|
|
202
|
+
|
|
203
|
+
return [
|
|
204
|
+
self._clone( self._items[ i : i + per_page ] )
|
|
205
|
+
for i in range( 0, self.count, per_page )
|
|
206
|
+
]
|
|
207
|
+
|
|
208
|
+
|
|
209
|
+
class DateCollectionBase( CursorCollectionBase[ T, R ], Generic[ T, R ] ):
|
|
210
|
+
"""
|
|
211
|
+
Provides date-related immutable collection operations.
|
|
212
|
+
|
|
213
|
+
This class extends the base collection adding special methods dealing with
|
|
214
|
+
dates, like `year`, `month`, or `between`.
|
|
215
|
+
"""
|
|
216
|
+
|
|
217
|
+
def __init__(
|
|
218
|
+
self, items: list[ T ], *,
|
|
219
|
+
factory: ItemFactory[ T, R ] = lambda item: cast( R, item ),
|
|
220
|
+
date: DateResolver[ T ] = lambda item: cast( str, item ),
|
|
221
|
+
total: int | None = None
|
|
222
|
+
) -> None:
|
|
223
|
+
super().__init__( items, factory= factory, total= total )
|
|
224
|
+
self._date = date or ( lambda item: item )
|
|
225
|
+
|
|
226
|
+
@property
|
|
227
|
+
def dates( self ) -> list[ str ]:
|
|
228
|
+
"""Returns the resolved date values."""
|
|
229
|
+
|
|
230
|
+
return [ self._date( item ) for item in self._items ]
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def latest( self ) -> R | None:
|
|
234
|
+
"""Returns the latest dated item."""
|
|
235
|
+
|
|
236
|
+
return self.first
|
|
237
|
+
|
|
238
|
+
@property
|
|
239
|
+
def oldest( self ) -> R | None:
|
|
240
|
+
"""Returns the oldest dated item."""
|
|
241
|
+
|
|
242
|
+
return self.last
|
|
243
|
+
|
|
244
|
+
def seek( self, pos: str ) -> Self:
|
|
245
|
+
"""Moves the cursor to a specific date."""
|
|
246
|
+
|
|
247
|
+
target = ymd( pos )
|
|
248
|
+
self._cursor = next( (
|
|
249
|
+
index for index, item in enumerate( self._items )
|
|
250
|
+
if self._date( item ) == target
|
|
251
|
+
), -1 )
|
|
252
|
+
|
|
253
|
+
return self
|
|
254
|
+
|
|
255
|
+
def find( self, date: str ) -> R | None:
|
|
256
|
+
"""Gets the item by exact date."""
|
|
257
|
+
|
|
258
|
+
target = ymd( date )
|
|
259
|
+
|
|
260
|
+
return next( (
|
|
261
|
+
self._factory( item ) for item in self._items
|
|
262
|
+
if self._date( item ) == target
|
|
263
|
+
), None )
|
|
264
|
+
|
|
265
|
+
def year( self, year: int ) -> Self:
|
|
266
|
+
"""Filters resources by year."""
|
|
267
|
+
|
|
268
|
+
prefix = f"{ year }-"
|
|
269
|
+
|
|
270
|
+
return self._clone( [
|
|
271
|
+
item for item in self._items
|
|
272
|
+
if self._date( item ).startswith( prefix )
|
|
273
|
+
] )
|
|
274
|
+
|
|
275
|
+
def month( self, year: int, month: int ) -> Self:
|
|
276
|
+
"""Filters resources by month."""
|
|
277
|
+
|
|
278
|
+
prefix = f"{ year }-{month:02d}-"
|
|
279
|
+
|
|
280
|
+
return self._clone( [
|
|
281
|
+
item for item in self._items
|
|
282
|
+
if self._date( item ).startswith( prefix )
|
|
283
|
+
] )
|
|
284
|
+
|
|
285
|
+
def before( self, date: str ) -> Self:
|
|
286
|
+
"""Returns resources before a date."""
|
|
287
|
+
|
|
288
|
+
target = ymd( date )
|
|
289
|
+
return self._clone( [ item for item in self._items if self._date( item ) < target ] )
|
|
290
|
+
|
|
291
|
+
def after( self, date: str ) -> Self:
|
|
292
|
+
"""Returns resources after a date."""
|
|
293
|
+
|
|
294
|
+
target = ymd( date )
|
|
295
|
+
return self._clone( [ item for item in self._items if self._date( item ) > target ] )
|
|
296
|
+
|
|
297
|
+
def since( self, date: str ) -> Self:
|
|
298
|
+
"""Returns resources from a date onward."""
|
|
299
|
+
|
|
300
|
+
target = ymd( date )
|
|
301
|
+
return self._clone( [ item for item in self._items if self._date( item ) >= target ] )
|
|
302
|
+
|
|
303
|
+
def until( self, date: str ) -> Self:
|
|
304
|
+
"""Returns resources up to a date."""
|
|
305
|
+
|
|
306
|
+
target = ymd( date )
|
|
307
|
+
return self._clone( [ item for item in self._items if self._date( item ) <= target ] )
|
|
308
|
+
|
|
309
|
+
def between( self, start: str, end: str ) -> Self:
|
|
310
|
+
"""Returns resources inside a date range."""
|
|
311
|
+
|
|
312
|
+
s, e = ymd( start ), ymd( end )
|
|
313
|
+
return self._clone( [ item for item in self._items if s <= self._date( item ) <= e ] )
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Dateable Resource
|
|
3
|
+
|
|
4
|
+
Implements the resource wrapper for date-indexed endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Callable, Generic, TypedDict, TypeVar
|
|
10
|
+
|
|
11
|
+
from rtbnext.core.loader import ResourceStateLoader
|
|
12
|
+
from rtbnext.core.parser import ParserFn
|
|
13
|
+
from rtbnext.resource.base import Resource
|
|
14
|
+
from rtbnext.resource.collection import DateCollectionBase
|
|
15
|
+
|
|
16
|
+
DateData = TypedDict( "DateData", { "dates": list[ str ] } )
|
|
17
|
+
|
|
18
|
+
D = TypeVar( "D", bound= DateData )
|
|
19
|
+
R = TypeVar( "R" )
|
|
20
|
+
|
|
21
|
+
type DateFn[ R ] = Callable[ [ str ], R ]
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
class DateCollection( DateCollectionBase[ str, R ], Generic[ R ] ):
|
|
25
|
+
"""
|
|
26
|
+
Collection wrapper for date-indexed resources.
|
|
27
|
+
|
|
28
|
+
This class provides convenient filtering and mapping operations for resources
|
|
29
|
+
that are indexed by date values. It allows for lazy resolution of resources
|
|
30
|
+
based on their date keys, while maintaining the underlying collection of date
|
|
31
|
+
strings.
|
|
32
|
+
"""
|
|
33
|
+
|
|
34
|
+
|
|
35
|
+
class DateableResource( Resource[ D ], Generic[ D, R ] ):
|
|
36
|
+
"""
|
|
37
|
+
Resource wrapper for date-indexed endpoints.
|
|
38
|
+
|
|
39
|
+
This class provides lazy traversal over API endpoints that are indexed by
|
|
40
|
+
date values. It allows for convenient access to resources based on their
|
|
41
|
+
date keys, while maintaining the underlying resource data.
|
|
42
|
+
"""
|
|
43
|
+
|
|
44
|
+
def __init__(
|
|
45
|
+
self, path: str, loader: ResourceStateLoader, parser: ParserFn[ D ], *,
|
|
46
|
+
date: DateFn[ R ]
|
|
47
|
+
) -> None:
|
|
48
|
+
super().__init__( path, loader, parser )
|
|
49
|
+
self._date = date
|
|
50
|
+
|
|
51
|
+
def _collect_dates( self, dates: list[ str ] ) -> DateCollection[ R ]:
|
|
52
|
+
"""Creates a date collection."""
|
|
53
|
+
|
|
54
|
+
return DateCollection( dates, factory= self._date )
|
|
55
|
+
|
|
56
|
+
async def get( self ) -> DateCollection[ R ]:
|
|
57
|
+
"""Returns the indexed date collection."""
|
|
58
|
+
|
|
59
|
+
return await self._transform( lambda data: self._collect_dates( data[ "dates" ][ ::-1 ] ) )
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Indexable Resource
|
|
3
|
+
|
|
4
|
+
Implement the resource wrapper for nested indexable endpoints.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Callable, Generic, TypeVar
|
|
10
|
+
|
|
11
|
+
from rtbnext.core.loader import ResourceStateLoader
|
|
12
|
+
from rtbnext.core.parser import ParserFn
|
|
13
|
+
from rtbnext.resource.base import Resource
|
|
14
|
+
|
|
15
|
+
D = TypeVar( "D" )
|
|
16
|
+
R = TypeVar( "R" )
|
|
17
|
+
|
|
18
|
+
type Path = tuple[ str, ... ]
|
|
19
|
+
type IndexFn[ R ] = Callable[ [ Path ], R ]
|
|
20
|
+
type KeysFn = Callable[ [ object ], list[ str ] | None ]
|
|
21
|
+
|
|
22
|
+
|
|
23
|
+
class _IndexAccessor( Generic[ R ] ):
|
|
24
|
+
"""Lazy accessor object for index traversal."""
|
|
25
|
+
|
|
26
|
+
def __init__( self, factory: IndexFn[ R ], keys: KeysFn, path: Path, value: object ) -> None:
|
|
27
|
+
self._factory, self._keys, self._path, self._value = factory, keys, path, value
|
|
28
|
+
|
|
29
|
+
def __getitem__( self, key: str ) -> R | _IndexAccessor[ R ]:
|
|
30
|
+
"""Resolves a nested index key."""
|
|
31
|
+
|
|
32
|
+
available = self._keys( self._value )
|
|
33
|
+
|
|
34
|
+
if available is None or key not in available:
|
|
35
|
+
raise AttributeError( f"Unknown index key: { key }" )
|
|
36
|
+
|
|
37
|
+
path = ( *self._path, key )
|
|
38
|
+
|
|
39
|
+
if isinstance( self._value, dict ) and self._keys( child := self._value[ key ] ):
|
|
40
|
+
return _IndexAccessor( self._factory, self._keys, path, child )
|
|
41
|
+
|
|
42
|
+
return self._factory( path )
|
|
43
|
+
|
|
44
|
+
def __dir__( self ) -> list[ str ]:
|
|
45
|
+
"""Returns available index keys."""
|
|
46
|
+
|
|
47
|
+
return sorted( [ *super().__dir__(), *( self._keys( self._value ) or [] ) ] )
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
class IndexableResource( Resource[ D ], Generic[ D, R ] ):
|
|
51
|
+
"""
|
|
52
|
+
Resource wrapper for nested indexable endpoints.
|
|
53
|
+
|
|
54
|
+
This class provides lazy traversal over API indexes by exposing generated
|
|
55
|
+
accessors for nested keys.
|
|
56
|
+
|
|
57
|
+
The underlying resource data is loaded only once and transformed into a
|
|
58
|
+
reusable accessor tree.
|
|
59
|
+
"""
|
|
60
|
+
|
|
61
|
+
def __init__(
|
|
62
|
+
self, path: str, loader: ResourceStateLoader, parser: ParserFn[ D ], *,
|
|
63
|
+
index: IndexFn[ R ],
|
|
64
|
+
keys: KeysFn | None = None
|
|
65
|
+
) -> None:
|
|
66
|
+
super().__init__( path, loader, parser )
|
|
67
|
+
|
|
68
|
+
self._factory = index
|
|
69
|
+
self._keys = keys or self._default_keys
|
|
70
|
+
|
|
71
|
+
@staticmethod
|
|
72
|
+
def _default_keys( value: object ) -> list[ str ] | None:
|
|
73
|
+
"""Extracts keys from common index structures."""
|
|
74
|
+
|
|
75
|
+
if isinstance( value, dict ):
|
|
76
|
+
return list( value.keys() )
|
|
77
|
+
|
|
78
|
+
if isinstance( value, list ):
|
|
79
|
+
return [ str( item ) for item in value ]
|
|
80
|
+
|
|
81
|
+
return None
|
|
82
|
+
|
|
83
|
+
def _traverse( self, value: object, path: Path = () ) -> _IndexAccessor[ R ]:
|
|
84
|
+
"""Creates the lazy accessor tree."""
|
|
85
|
+
|
|
86
|
+
return _IndexAccessor( self._factory, self._keys, path, value )
|
|
87
|
+
|
|
88
|
+
async def get( self ) -> R:
|
|
89
|
+
"""Returns the lazily generated index tree."""
|
|
90
|
+
|
|
91
|
+
return await self._transform( self._traverse )
|