X-Git-Url: https://gerrit.fd.io/r/gitweb?a=blobdiff_plain;f=src%2Fvpp-api%2Fpython%2Fvpp_papi%2Fvpp_papi.py;fp=src%2Fvpp-api%2Fpython%2Fvpp_papi%2Fvpp_papi.py;h=1e5d23e59b724676129b14da8444fe5bfed32e82;hb=d9b0c6fbf7aa5bd9af84264105b39c82028a4a29;hp=3465f503e9e8d2e95c41d6d1b4d3e8f94d1f146e;hpb=f90348bcb4afd0af2611cefc43b17ef3042b511c;p=vpp.git diff --git a/src/vpp-api/python/vpp_papi/vpp_papi.py b/src/vpp-api/python/vpp_papi/vpp_papi.py index 3465f503e9e..1e5d23e59b7 100644 --- a/src/vpp-api/python/vpp_papi/vpp_papi.py +++ b/src/vpp-api/python/vpp_papi/vpp_papi.py @@ -30,13 +30,14 @@ import fnmatch import weakref import atexit import time -from . vpp_format import verify_enum_hint -from . vpp_serializer import VPPType, VPPEnumType, VPPEnumFlagType, VPPUnionType -from . vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias +from .vpp_format import verify_enum_hint +from .vpp_serializer import VPPType, VPPEnumType, VPPEnumFlagType, VPPUnionType +from .vpp_serializer import VPPMessage, vpp_get_type, VPPTypeAlias try: import VppTransport except ModuleNotFoundError: + class V: """placeholder for VppTransport as the implementation is dependent on VPPAPIClient's initialization values @@ -44,15 +45,22 @@ except ModuleNotFoundError: VppTransport = V -from . vpp_transport_socket import VppTransport +from .vpp_transport_socket import VppTransport -logger = logging.getLogger('vpp_papi') +logger = logging.getLogger("vpp_papi") logger.addHandler(logging.NullHandler()) -__all__ = ('FuncWrapper', 'VppApiDynamicMethodHolder', - 'VppEnum', 'VppEnumType', 'VppEnumFlag', - 'VPPIOError', 'VPPRuntimeError', 'VPPValueError', - 'VPPApiClient', ) +__all__ = ( + "FuncWrapper", + "VppApiDynamicMethodHolder", + "VppEnum", + "VppEnumType", + "VppEnumFlag", + "VPPIOError", + "VPPRuntimeError", + "VPPValueError", + "VPPApiClient", +) def metaclass(metaclass): @@ -83,7 +91,7 @@ def vpp_atexit(vpp_weakref): """Clean up VPP connection on shutdown.""" vpp_instance = vpp_weakref() if vpp_instance and vpp_instance.transport.connected: - logger.debug('Cleaning up VPP on exit') + logger.debug("Cleaning up VPP on exit") vpp_instance.disconnect() @@ -98,9 +106,9 @@ def add_convenience_methods(): def _vapi_af_name(self): if 6 == self._version: - return 'ip6' + return "ip6" if 4 == self._version: - return 'ip4' + return "ip4" raise ValueError("Invalid _version.") ipaddress._IPAddressBase.vapi_af = property(_vapi_af) @@ -121,7 +129,7 @@ class FuncWrapper: return self._func(**kwargs) def __repr__(self): - return ')>' % (self.__name__, self.__doc__) + return ")>" % (self.__name__, self.__doc__) class VPPApiError(Exception): @@ -161,7 +169,8 @@ class VPPApiJSONFiles: # perhaps we're in the 'src/scripts' or 'src/vpp-api/python' dir; # in which case, plot a course to likely places in the src tree import __main__ as main - if hasattr(main, '__file__'): + + if hasattr(main, "__file__"): # get the path of the calling script localdir = os.path.dirname(os.path.realpath(main.__file__)) else: @@ -171,7 +180,7 @@ class VPPApiJSONFiles: def dmatch(dir): """Match dir against right-hand components of the script dir""" - d = dir.split('/') # param 'dir' assumes a / separator + d = dir.split("/") # param 'dir' assumes a / separator length = len(d) return len(localdir_s) > length and localdir_s[-length:] == d @@ -180,43 +189,45 @@ class VPPApiJSONFiles: 'variant' (typically '' or '_debug')""" # Since 'core' and 'plugin' files are staged # in separate directories, we target the parent dir. - return os.path.sep.join(( - srcdir, - 'build-root', - 'install-vpp%s-native' % variant, - 'vpp', - 'share', - 'vpp', - 'api', - )) + return os.path.sep.join( + ( + srcdir, + "build-root", + "install-vpp%s-native" % variant, + "vpp", + "share", + "vpp", + "api", + ) + ) srcdir = None - if dmatch('src/scripts'): + if dmatch("src/scripts"): srcdir = os.path.sep.join(localdir_s[:-2]) - elif dmatch('src/vpp-api/python'): + elif dmatch("src/vpp-api/python"): srcdir = os.path.sep.join(localdir_s[:-3]) - elif dmatch('test'): + elif dmatch("test"): # we're apparently running tests srcdir = os.path.sep.join(localdir_s[:-1]) if srcdir: # we're in the source tree, try both the debug and release # variants. - dirs.append(sdir(srcdir, '_debug')) - dirs.append(sdir(srcdir, '')) + dirs.append(sdir(srcdir, "_debug")) + dirs.append(sdir(srcdir, "")) # Test for staged copies of the scripts # For these, since we explicitly know if we're running a debug versus # release variant, target only the relevant directory - if dmatch('build-root/install-vpp_debug-native/vpp/bin'): + if dmatch("build-root/install-vpp_debug-native/vpp/bin"): srcdir = os.path.sep.join(localdir_s[:-4]) - dirs.append(sdir(srcdir, '_debug')) - if dmatch('build-root/install-vpp-native/vpp/bin'): + dirs.append(sdir(srcdir, "_debug")) + if dmatch("build-root/install-vpp-native/vpp/bin"): srcdir = os.path.sep.join(localdir_s[:-4]) - dirs.append(sdir(srcdir, '')) + dirs.append(sdir(srcdir, "")) # finally, try the location system packages typically install into - dirs.append(os.path.sep.join(('', 'usr', 'share', 'vpp', 'api'))) + dirs.append(os.path.sep.join(("", "usr", "share", "vpp", "api"))) # check the directories for existence; first one wins for dir in dirs: @@ -226,7 +237,7 @@ class VPPApiJSONFiles: return None @classmethod - def find_api_files(cls, api_dir=None, patterns='*'): # -> list + def find_api_files(cls, api_dir=None, patterns="*"): # -> list """Find API definition files from the given directory tree with the given pattern. If no directory is given then find_api_dir() is used to locate one. If no pattern is given then all definition files found @@ -252,9 +263,9 @@ class VPPApiJSONFiles: raise VPPApiError("api_dir cannot be located") if isinstance(patterns, list) or isinstance(patterns, tuple): - patterns = [p.strip() + '.api.json' for p in patterns] + patterns = [p.strip() + ".api.json" for p in patterns] else: - patterns = [p.strip() + '.api.json' for p in patterns.split(",")] + patterns = [p.strip() + ".api.json" for p in patterns.split(",")] api_files = [] for root, dirnames, files in os.walk(api_dir): @@ -281,39 +292,39 @@ class VPPApiJSONFiles: services = {} messages = {} try: - for t in api['enums']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'enum', 'data': t} + for t in api["enums"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "enum", "data": t} except KeyError: pass try: - for t in api['enumflags']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'enum', 'data': t} + for t in api["enumflags"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "enum", "data": t} except KeyError: pass try: - for t in api['unions']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'union', 'data': t} + for t in api["unions"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "union", "data": t} except KeyError: pass try: - for t in api['types']: - t[0] = 'vl_api_' + t[0] + '_t' - types[t[0]] = {'type': 'type', 'data': t} + for t in api["types"]: + t[0] = "vl_api_" + t[0] + "_t" + types[t[0]] = {"type": "type", "data": t} except KeyError: pass try: - for t, v in api['aliases'].items(): - types['vl_api_' + t + '_t'] = {'type': 'alias', 'data': v} + for t, v in api["aliases"].items(): + types["vl_api_" + t + "_t"] = {"type": "alias", "data": v} except KeyError: pass try: - services.update(api['services']) + services.update(api["services"]) except KeyError: pass @@ -321,30 +332,30 @@ class VPPApiJSONFiles: while True: unresolved = {} for k, v in types.items(): - t = v['data'] + t = v["data"] if not vpp_get_type(k): - if v['type'] == 'enum': + if v["type"] == "enum": try: VPPEnumType(t[0], t[1:]) except ValueError: unresolved[k] = v if not vpp_get_type(k): - if v['type'] == 'enumflag': + if v["type"] == "enumflag": try: VPPEnumFlagType(t[0], t[1:]) except ValueError: unresolved[k] = v - elif v['type'] == 'union': + elif v["type"] == "union": try: VPPUnionType(t[0], t[1:]) except ValueError: unresolved[k] = v - elif v['type'] == 'type': + elif v["type"] == "type": try: VPPType(t[0], t[1:]) except ValueError: unresolved[k] = v - elif v['type'] == 'alias': + elif v["type"] == "alias": try: VPPTypeAlias(k, t) except ValueError: @@ -352,17 +363,16 @@ class VPPApiJSONFiles: if len(unresolved) == 0: break if i > 3: - raise VPPValueError('Unresolved type definitions {}' - .format(unresolved)) + raise VPPValueError("Unresolved type definitions {}".format(unresolved)) types = unresolved i += 1 try: - for m in api['messages']: + for m in api["messages"]: try: messages[m[0]] = VPPMessage(m[0], m[1:]) except VPPNotImplementedError: ### OLE FIXME - logger.error('Not implemented error for {}'.format(m[0])) + logger.error("Not implemented error for {}".format(m[0])) except KeyError: pass return messages, services @@ -380,6 +390,7 @@ class VPPApiClient: provides a means to register a callback function to receive these messages in a background thread. """ + apidir = None VPPApiError = VPPApiError VPPRuntimeError = VPPRuntimeError @@ -387,11 +398,18 @@ class VPPApiClient: VPPNotImplementedError = VPPNotImplementedError VPPIOError = VPPIOError - - def __init__(self, *, apifiles=None, testmode=False, async_thread=True, - logger=None, loglevel=None, - read_timeout=5, use_socket=True, - server_address='/run/vpp/api.sock'): + def __init__( + self, + *, + apifiles=None, + testmode=False, + async_thread=True, + logger=None, + loglevel=None, + read_timeout=5, + use_socket=True, + server_address="/run/vpp/api.sock", + ): """Create a VPP API object. apifiles is a list of files containing API @@ -406,7 +424,8 @@ class VPPApiClient: """ if logger is None: logger = logging.getLogger( - "{}.{}".format(__name__, self.__class__.__name__)) + "{}.{}".format(__name__, self.__class__.__name__) + ) if loglevel is not None: logger.setLevel(loglevel) self.logger = logger @@ -415,8 +434,7 @@ class VPPApiClient: self.services = {} self.id_names = [] self.id_msgdef = [] - self.header = VPPType('header', [['u16', 'msgid'], - ['u32', 'client_index']]) + self.header = VPPType("header", [["u16", "msgid"], ["u32", "client_index"]]) self.apifiles = [] self.event_callback = None self.message_queue = queue.Queue() @@ -449,13 +467,13 @@ class VPPApiClient: # Basic sanity check if len(self.messages) == 0 and not testmode: - raise VPPValueError(1, 'Missing JSON message definitions') - if not(verify_enum_hint(VppEnum.vl_api_address_family_t)): - raise VPPRuntimeError("Invalid address family hints. " - "Cannot continue.") + raise VPPValueError(1, "Missing JSON message definitions") + if not (verify_enum_hint(VppEnum.vl_api_address_family_t)): + raise VPPRuntimeError("Invalid address family hints. " "Cannot continue.") - self.transport = VppTransport(self, read_timeout=read_timeout, - server_address=server_address) + self.transport = VppTransport( + self, read_timeout=read_timeout, server_address=server_address + ) # Make sure we allow VPP to clean up the message rings. atexit.register(vpp_atexit, weakref.ref(self)) @@ -466,6 +484,7 @@ class VPPApiClient: class ContextId: """Multiprocessing-safe provider of unique context IDs.""" + def __init__(self): self.context = mp.Value(ctypes.c_uint, 0) self.lock = mp.Lock() @@ -475,6 +494,7 @@ class VPPApiClient: with self.lock: self.context.value += 1 return self.context.value + get_context = ContextId() def get_type(self, name): @@ -487,17 +507,20 @@ class VPPApiClient: return self._api def make_function(self, msg, i, multipart, do_async): - if (do_async): + if do_async: + def f(**kwargs): return self._call_vpp_async(i, msg, **kwargs) + else: + def f(**kwargs): return self._call_vpp(i, msg, multipart, **kwargs) f.__name__ = str(msg.name) - f.__doc__ = ", ".join(["%s %s" % - (msg.fieldtypes[j], k) - for j, k in enumerate(msg.fields)]) + f.__doc__ = ", ".join( + ["%s %s" % (msg.fieldtypes[j], k) for j, k in enumerate(msg.fields)] + ) f.msg = msg return f @@ -507,7 +530,7 @@ class VPPApiClient: self.id_msgdef = [None] * (self.vpp_dictionary_maxid + 1) self._api = VppApiDynamicMethodHolder() for name, msg in self.messages.items(): - n = name + '_' + msg.crc[2:] + n = name + "_" + msg.crc[2:] i = self.transport.get_msg_index(n) if i > 0: self.id_msgdef[i] = msg @@ -518,28 +541,25 @@ class VPPApiClient: f = self.make_function(msg, i, self.services[name], do_async) setattr(self._api, name, FuncWrapper(f)) else: - self.logger.debug( - 'No such message type or failed CRC checksum: %s', n) + self.logger.debug("No such message type or failed CRC checksum: %s", n) - def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, - do_async): - pfx = chroot_prefix.encode('utf-8') if chroot_prefix else None + def connect_internal(self, name, msg_handler, chroot_prefix, rx_qlen, do_async): + pfx = chroot_prefix.encode("utf-8") if chroot_prefix else None - rv = self.transport.connect(name, pfx, - msg_handler, rx_qlen) + rv = self.transport.connect(name, pfx, msg_handler, rx_qlen) if rv != 0: - raise VPPIOError(2, 'Connect failed') + raise VPPIOError(2, "Connect failed") self.vpp_dictionary_maxid = self.transport.msg_table_max_index() self._register_functions(do_async=do_async) # Initialise control ping - crc = self.messages['control_ping'].crc + crc = self.messages["control_ping"].crc self.control_ping_index = self.transport.get_msg_index( - ('control_ping' + '_' + crc[2:])) - self.control_ping_msgdef = self.messages['control_ping'] + ("control_ping" + "_" + crc[2:]) + ) + self.control_ping_msgdef = self.messages["control_ping"] if self.async_thread: - self.event_thread = threading.Thread( - target=self.thread_msg_handler) + self.event_thread = threading.Thread(target=self.thread_msg_handler) self.event_thread.daemon = True self.event_thread.start() else: @@ -556,8 +576,9 @@ class VPPApiClient: client and server. """ msg_handler = self.transport.get_callback(do_async) - return self.connect_internal(name, msg_handler, chroot_prefix, rx_qlen, - do_async) + return self.connect_internal( + name, msg_handler, chroot_prefix, rx_qlen, do_async + ) def connect_sync(self, name, chroot_prefix=None, rx_qlen=32): """Attach to VPP in synchronous mode. Application must poll for events. @@ -568,8 +589,7 @@ class VPPApiClient: client and server. """ - return self.connect_internal(name, None, chroot_prefix, rx_qlen, - do_async=False) + return self.connect_internal(name, None, chroot_prefix, rx_qlen, do_async=False) def disconnect(self): """Detach from VPP.""" @@ -590,42 +610,43 @@ class VPPApiClient: # If we have a context, then use the context to find any # request waiting for a reply context = 0 - if hasattr(r, 'context') and r.context > 0: + if hasattr(r, "context") and r.context > 0: context = r.context if context == 0: # No context -> async notification that we feed to the callback self.message_queue.put_nowait(r) else: - raise VPPIOError(2, 'RPC reply message received in event handler') + raise VPPIOError(2, "RPC reply message received in event handler") def has_context(self, msg): if len(msg) < 10: return False - header = VPPType('header_with_context', [['u16', 'msgid'], - ['u32', 'client_index'], - ['u32', 'context']]) + header = VPPType( + "header_with_context", + [["u16", "msgid"], ["u32", "client_index"], ["u32", "context"]], + ) (i, ci, context), size = header.unpack(msg, 0) - if self.id_names[i] == 'rx_thread_exit': + if self.id_names[i] == "rx_thread_exit": return # # Decode message and returns a tuple. # msgobj = self.id_msgdef[i] - if 'context' in msgobj.field_by_name and context >= 0: + if "context" in msgobj.field_by_name and context >= 0: return True return False def decode_incoming_msg(self, msg, no_type_conversion=False): if not msg: - logger.warning('vpp_api.read failed') + logger.warning("vpp_api.read failed") return (i, ci), size = self.header.unpack(msg, 0) - if self.id_names[i] == 'rx_thread_exit': + if self.id_names[i] == "rx_thread_exit": return # @@ -633,7 +654,7 @@ class VPPApiClient: # msgobj = self.id_msgdef[i] if not msgobj: - raise VPPIOError(2, 'Reply message undefined') + raise VPPIOError(2, "Reply message undefined") r, size = msgobj.unpack(msg, ntc=no_type_conversion) return r @@ -654,41 +675,39 @@ class VPPApiClient: def _control_ping(self, context): """Send a ping command.""" - self._call_vpp_async(self.control_ping_index, - self.control_ping_msgdef, - context=context) + self._call_vpp_async( + self.control_ping_index, self.control_ping_msgdef, context=context + ) def validate_args(self, msg, kwargs): d = set(kwargs.keys()) - set(msg.field_by_name.keys()) if d: - raise VPPValueError('Invalid argument {} to {}' - .format(list(d), msg.name)) + raise VPPValueError("Invalid argument {} to {}".format(list(d), msg.name)) def _add_stat(self, name, ms): if not name in self.stats: - self.stats[name] = {'max': ms, 'count': 1, 'avg': ms} + self.stats[name] = {"max": ms, "count": 1, "avg": ms} else: - if ms > self.stats[name]['max']: - self.stats[name]['max'] = ms - self.stats[name]['count'] += 1 - n = self.stats[name]['count'] - self.stats[name]['avg'] = self.stats[name]['avg'] * (n - 1) / n + ms / n + if ms > self.stats[name]["max"]: + self.stats[name]["max"] = ms + self.stats[name]["count"] += 1 + n = self.stats[name]["count"] + self.stats[name]["avg"] = self.stats[name]["avg"] * (n - 1) / n + ms / n def get_stats(self): - s = '\n=== API PAPI STATISTICS ===\n' - s += '{:<30} {:>4} {:>6} {:>6}\n'.format('message', 'cnt', 'avg', 'max') - for n in sorted(self.stats.items(), key=lambda v: v[1]['avg'], reverse=True): - s += '{:<30} {:>4} {:>6.2f} {:>6.2f}\n'.format(n[0], n[1]['count'], - n[1]['avg'], n[1]['max']) + s = "\n=== API PAPI STATISTICS ===\n" + s += "{:<30} {:>4} {:>6} {:>6}\n".format("message", "cnt", "avg", "max") + for n in sorted(self.stats.items(), key=lambda v: v[1]["avg"], reverse=True): + s += "{:<30} {:>4} {:>6.2f} {:>6.2f}\n".format( + n[0], n[1]["count"], n[1]["avg"], n[1]["max"] + ) return s def get_field_options(self, msg, fld_name): # when there is an option, the msgdef has 3 elements. # ['u32', 'ring_size', {'default': 1024}] for _def in self.messages[msg].msgdef: - if isinstance(_def, list) and \ - len(_def) == 3 and \ - _def[1] == fld_name: + if isinstance(_def, list) and len(_def) == 3 and _def[1] == fld_name: return _def[2] def _call_vpp(self, i, msgdef, service, **kwargs): @@ -707,25 +726,26 @@ class VPPApiClient: no response within the timeout window. """ ts = time.time() - if 'context' not in kwargs: + if "context" not in kwargs: context = self.get_context() - kwargs['context'] = context + kwargs["context"] = context else: - context = kwargs['context'] - kwargs['_vl_msg_id'] = i + context = kwargs["context"] + kwargs["_vl_msg_id"] = i - no_type_conversion = kwargs.pop('_no_type_conversion', False) - timeout = kwargs.pop('_timeout', None) + no_type_conversion = kwargs.pop("_no_type_conversion", False) + timeout = kwargs.pop("_timeout", None) try: if self.transport.socket_index: - kwargs['client_index'] = self.transport.socket_index + kwargs["client_index"] = self.transport.socket_index except AttributeError: pass self.validate_args(msgdef, kwargs) - s = 'Calling {}({})'.format(msgdef.name, - ','.join(['{!r}:{!r}'.format(k, v) for k, v in kwargs.items()])) + s = "Calling {}({})".format( + msgdef.name, ",".join(["{!r}:{!r}".format(k, v) for k, v in kwargs.items()]) + ) self.logger.debug(s) b = msgdef.pack(kwargs) @@ -733,17 +753,17 @@ class VPPApiClient: self.transport.write(b) - msgreply = service['reply'] - stream = True if 'stream' in service else False + msgreply = service["reply"] + stream = True if "stream" in service else False if stream: - if 'stream_msg' in service: + if "stream_msg" in service: # New service['reply'] = _reply and service['stream_message'] = _details - stream_message = service['stream_msg'] - modern =True + stream_message = service["stream_msg"] + modern = True else: # Old service['reply'] = _details stream_message = msgreply - msgreply = 'control_ping_reply' + msgreply = "control_ping_reply" modern = False # Send a ping after the request - we use its response # to detect that we have seen all results. @@ -751,22 +771,22 @@ class VPPApiClient: # Block until we get a reply. rl = [] - while (True): + while True: r = self.read_blocking(no_type_conversion, timeout) if r is None: - raise VPPIOError(2, 'VPP API client: read failed') + raise VPPIOError(2, "VPP API client: read failed") msgname = type(r).__name__ if context not in r or r.context == 0 or context != r.context: # Message being queued self.message_queue.put_nowait(r) continue if msgname != msgreply and (stream and (msgname != stream_message)): - print('REPLY MISMATCH', msgreply, msgname, stream_message, stream) + print("REPLY MISMATCH", msgreply, msgname, stream_message, stream) if not stream: rl = r break if msgname == msgreply: - if modern: # Return both reply and list + if modern: # Return both reply and list rl = r, rl break @@ -774,7 +794,7 @@ class VPPApiClient: self.transport.resume() - s = 'Return value: {!r}'.format(r) + s = "Return value: {!r}".format(r) if len(s) > 80: s = s[:80] + "..." self.logger.debug(s) @@ -795,17 +815,17 @@ class VPPApiClient: The returned context will help with assigning which call the reply belongs to. """ - if 'context' not in kwargs: + if "context" not in kwargs: context = self.get_context() - kwargs['context'] = context + kwargs["context"] = context else: - context = kwargs['context'] + context = kwargs["context"] try: if self.transport.socket_index: - kwargs['client_index'] = self.transport.socket_index + kwargs["client_index"] = self.transport.socket_index except AttributeError: - kwargs['client_index'] = 0 - kwargs['_vl_msg_id'] = i + kwargs["client_index"] = 0 + kwargs["_vl_msg_id"] = i b = msg.pack(kwargs) self.transport.write(b) @@ -891,26 +911,34 @@ class VPPApiClient: """Return VPPs API message table as name_crc dictionary, filtered by message name list.""" - replies = [self.services[n]['reply'] for n in msglist] + replies = [self.services[n]["reply"] for n in msglist] message_table_filtered = {} for name in msglist + replies: - for k,v in self.transport.message_table.items(): + for k, v in self.transport.message_table.items(): if k.startswith(name): message_table_filtered[k] = v break return message_table_filtered def __repr__(self): - return "" % ( - self._apifiles, self.testmode, self.async_thread, - self.logger, self.read_timeout, self.server_address) + return ( + "" + % ( + self._apifiles, + self.testmode, + self.async_thread, + self.logger, + self.read_timeout, + self.server_address, + ) + ) def details_iter(self, f, **kwargs): cursor = 0 while True: - kwargs['cursor'] = cursor + kwargs["cursor"] = cursor rv, details = f(**kwargs) for d in details: yield d