def prepare_args(raw_response) -> Mapping[str, Parameter]: args = re.findall(r"{(\d+)[^:}]*(:[^.}]*)?[^}]*\}", raw_response) default = [("ctx", Parameter("ctx", Parameter.POSITIONAL_OR_KEYWORD))] if not args: return OrderedDict(default) allowed_builtins = { "bool": bool, "complex": complex, "float": float, "frozenset": frozenset, "int": int, "list": list, "set": set, "str": str, "tuple": tuple, "query": quote_plus, } indices = [int(a[0]) for a in args] low = min(indices) indices = [a - low for a in indices] high = max(indices) if high > 9: raise ArgParseError(_("Too many arguments!")) gaps = set(indices).symmetric_difference(range(high + 1)) if gaps: raise ArgParseError( _("Arguments must be sequential. Missing arguments: ") + ", ".join(str(i + low) for i in gaps) ) fin = [Parameter("_" + str(i), Parameter.POSITIONAL_OR_KEYWORD) for i in range(high + 1)] for arg in args: index = int(arg[0]) - low anno = arg[1][1:] # strip initial colon if anno.lower().endswith("converter"): anno = anno[:-9] if not anno or anno.startswith("_"): # public types only name = "{}_{}".format("text", index if index < high else "final") fin[index] = fin[index].replace(name=name) continue # allow type hinting only for discord.py and builtin types try: anno = getattr(discord, anno) # force an AttributeError if there's no discord.py converter getattr(commands, anno.__name__ + "Converter") except AttributeError: anno = allowed_builtins.get(anno.lower(), Parameter.empty) if ( anno is not Parameter.empty and fin[index].annotation is not Parameter.empty and anno != fin[index].annotation ): raise ArgParseError( _( 'Conflicting colon notation for argument {index}: "{name1}" and "{name2}".' ).format( index=index + low, name1=fin[index].annotation.__name__, name2=anno.__name__, ) ) if anno is not Parameter.empty: fin[index] = fin[index].replace(annotation=anno) # consume rest fin[-1] = fin[-1].replace(kind=Parameter.KEYWORD_ONLY) # name the parameters for the help text for i, param in enumerate(fin): anno = param.annotation name = "{}_{}".format( "text" if anno is Parameter.empty else anno.__name__.lower(), i if i < high else "final", ) fin[i] = fin[i].replace(name=name) # insert ctx parameter for discord.py parsing fin = default + [(p.name, p) for p in fin] return OrderedDict(fin)
def _get_setter_fun( cls, # type: Type property_name, # type: str type_hint, # type: Any default_value, # type: Any private_property_name, # type: str overridden_setter=AUTO # type: Callable ): """ Utility method to find the overridden setter function for a given property, or generate a new one :param cls: :param property_name: :param type_hint: :param default_value: :param private_property_name: :param overridden_setter: an already found overridden setter to use. If AUTO is provided (default), the class will be inspected to find them :return: """ if overridden_setter is AUTO: # If not provided - look for an overridden setter in the class overridden_setters = getmembers(cls, predicate=_has_annotation( __SETTER_OVERRIDE_ANNOTATION, property_name)) if len(overridden_setters) > 1: raise DuplicateOverrideError( 'Setter is overridden more than once for attribute name : %s' % property_name) else: try: overridden_setter = overridden_setters[0][1] except IndexError: pass if overridden_setter is not None: # --use the overridden setter found/provided setter_fun = overridden_setter try: # python 2 - possibly unbind the function setter_fun = setter_fun.im_func except AttributeError: pass # --find the parameter name and check the signature s = signature(setter_fun) p = [ attribute_name for attribute_name, param in s.parameters.items() if attribute_name is not 'self' ] if len(p) != 1: raise IllegalSetterSignatureException( 'overridden setter %s should have 1 and only 1 non-self argument, ' 'found %s' % (setter_fun.__name__, s)) actual_arg_name = p[0] else: # --create the setter: Dynamically compile a wrapper with correct argument name sig = Signature(parameters=[ Parameter('self', kind=Parameter.POSITIONAL_OR_KEYWORD), Parameter(property_name, kind=Parameter.POSITIONAL_OR_KEYWORD, annotation=type_hint, default=default_value) ]) @with_signature(sig) def autoprops_generated_setter(self, **kwargs): """ generated by `autoprops` - setter for a property """ setattr(self, private_property_name, kwargs[property_name]) setter_fun = autoprops_generated_setter actual_arg_name = property_name return setter_fun, actual_arg_name
def make_sig(*names): parms = [ Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names ] return Signature(parms)
# end = time.time() # print(func.__name__, end - start) # return r # return wrapper # # @timethis # def countdown(n): # while n > 0: # n -= 1 # # countdown(100000) from inspect import Signature, Parameter parms = [ Parameter('x', Parameter.POSITIONAL_OR_KEYWORD), Parameter('y', Parameter.POSITIONAL_OR_KEYWORD, default=42), Parameter('z', Parameter.KEYWORD_ONLY, default=None) ] sig = Signature(parms) print(sig) def func(*args, **kwargs): bound_values = sig.bind(*args, **kwargs) for name, value in bound_values.arguments.items(): print(name, value) func(1, 2, z=3)
def parameters(cls): ''' Generate Python ``Parameter`` values suitable for functions that are derived from the glyph. Returns: list(Parameter) ''' arg_params = [] no_more_defaults = False for arg in reversed(cls._args): descriptor = cls.lookup(arg) default = descriptor.class_default(cls) if default is None: no_more_defaults = True # simplify field(x) defaults to just present the column name if isinstance(default, dict) and set(default) == {"field"}: default = default["field"] # make sure we don't hold on to references to actual Models if isinstance(default, Model): default = _ModelRepr(default) param = Parameter( name=arg, kind=Parameter.POSITIONAL_OR_KEYWORD, # For positional arg properties, default=None means no default. default=Parameter.empty if no_more_defaults else default) if default: del default typ = type_link(descriptor.property) arg_params.insert(0, (param, typ, descriptor.__doc__)) # these are not really useful, and should also really be private, just skip them omissions = { 'js_event_callbacks', 'js_property_callbacks', 'subscribed_events' } kwarg_params = [] kws = set(cls.properties()) - set(cls._args) - omissions for kw in kws: descriptor = cls.lookup(kw) default = descriptor.class_default(cls) # simplify field(x) defaults to just present the column name if isinstance(default, dict) and set(default) == {"field"}: default = default["field"] # make sure we don't hold on to references to actual Models if isinstance(default, Model): default = _ModelRepr(default) param = Parameter(name=kw, kind=Parameter.KEYWORD_ONLY, default=default) del default typ = type_link(descriptor.property) kwarg_params.append((param, typ, descriptor.__doc__)) for kw, (typ, doc) in cls._extra_kws.items(): param = Parameter( name=kw, kind=Parameter.KEYWORD_ONLY, ) kwarg_params.append((param, typ, doc)) kwarg_params.sort(key=lambda x: x[0].name) return arg_params + kwarg_params
def signature_from_ast(node: ast.FunctionDef) -> inspect.Signature: """Create a Signature object from AST *node*.""" args = node.args defaults = list(args.defaults) params = [] if hasattr(args, "posonlyargs"): posonlyargs = len(args.posonlyargs) # type: ignore positionals = posonlyargs + len(args.args) else: posonlyargs = 0 positionals = len(args.args) for _ in range(len(defaults), positionals): defaults.insert(0, Parameter.empty) if hasattr(args, "posonlyargs"): for i, arg in enumerate(args.posonlyargs): # type: ignore if defaults[i] is Parameter.empty: default = Parameter.empty else: default = ast_unparse(defaults[i]) annotation = ast_unparse(arg.annotation) or Parameter.empty params.append( Parameter(arg.arg, Parameter.POSITIONAL_ONLY, default=default, annotation=annotation)) for i, arg in enumerate(args.args): if defaults[i + posonlyargs] is Parameter.empty: default = Parameter.empty else: default = ast_unparse(defaults[i + posonlyargs]) annotation = ast_unparse(arg.annotation) or Parameter.empty params.append( Parameter(arg.arg, Parameter.POSITIONAL_OR_KEYWORD, default=default, annotation=annotation)) if args.vararg: annotation = ast_unparse(args.vararg.annotation) or Parameter.empty params.append( Parameter(args.vararg.arg, Parameter.VAR_POSITIONAL, annotation=annotation)) for i, arg in enumerate(args.kwonlyargs): default = ast_unparse(args.kw_defaults[i]) or Parameter.empty annotation = ast_unparse(arg.annotation) or Parameter.empty params.append( Parameter(arg.arg, Parameter.KEYWORD_ONLY, default=default, annotation=annotation)) if args.kwarg: annotation = ast_unparse(args.kwarg.annotation) or Parameter.empty params.append( Parameter(args.kwarg.arg, Parameter.VAR_KEYWORD, annotation=annotation)) return_annotation = ast_unparse(node.returns) or Parameter.empty return inspect.Signature(params, return_annotation=return_annotation)
def _make_signature(names): return Signature( Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names)
def test_instance_method(): r"""Ensure instance methods' signature.""" assert hasattr(ResLSTMBlock, '__init__') assert inspect.signature(ResLSTMBlock.__init__) == Signature( parameters=[ Parameter( name='self', kind=Parameter.POSITIONAL_OR_KEYWORD, default=Parameter.empty, ), Parameter( name='d_hid', kind=Parameter.KEYWORD_ONLY, annotation=int, default=Parameter.empty, ), Parameter( name='n_hid_lyr', kind=Parameter.KEYWORD_ONLY, annotation=int, default=Parameter.empty, ), Parameter( name='p_hid', kind=Parameter.KEYWORD_ONLY, annotation=float, default=Parameter.empty, ), Parameter( name='kwargs', kind=Parameter.VAR_KEYWORD, annotation=Optional[Dict], ), ], return_annotation=Signature.empty, ) assert hasattr(ResLSTMModel, '__init__') assert inspect.signature(ResLSTMModel.__init__) == Signature( parameters=[ Parameter( name='self', kind=Parameter.POSITIONAL_OR_KEYWORD, default=Parameter.empty, ), Parameter( name='d_emb', kind=Parameter.KEYWORD_ONLY, annotation=int, default=Parameter.empty, ), Parameter( name='d_hid', kind=Parameter.KEYWORD_ONLY, annotation=int, default=Parameter.empty, ), Parameter( name='n_hid_lyr', kind=Parameter.KEYWORD_ONLY, annotation=int, default=Parameter.empty, ), Parameter( name='n_post_hid_lyr', kind=Parameter.KEYWORD_ONLY, annotation=int, default=Parameter.empty, ), Parameter( name='n_pre_hid_lyr', kind=Parameter.KEYWORD_ONLY, annotation=int, default=Parameter.empty, ), Parameter( name='p_emb', kind=Parameter.KEYWORD_ONLY, annotation=float, default=Parameter.empty, ), Parameter( name='p_hid', kind=Parameter.KEYWORD_ONLY, annotation=float, default=Parameter.empty, ), Parameter( name='tknzr', kind=Parameter.KEYWORD_ONLY, annotation=BaseTknzr, default=Parameter.empty, ), Parameter( name='kwargs', kind=Parameter.VAR_KEYWORD, annotation=Optional[Dict], ), ], return_annotation=Signature.empty, )
def test_wasi(): # .__init__ assert Wasi.__init__.__text_signature__ == "($self, /, *args, **kwargs)" # .arguments assert Wasi.arguments.__text_signature__ == "($self, arguments)" assert signature(Wasi.arguments) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("arguments", Parameter.POSITIONAL_OR_KEYWORD), ]) # .argument assert Wasi.argument.__text_signature__ == "($self, argument)" assert signature(Wasi.argument) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("argument", Parameter.POSITIONAL_OR_KEYWORD), ]) # .environments assert Wasi.environments.__text_signature__ == "($self, environments)" assert signature(Wasi.environments) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("environments", Parameter.POSITIONAL_OR_KEYWORD), ]) # .environment assert Wasi.environment.__text_signature__ == "($self, key, value)" assert signature(Wasi.environment) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("key", Parameter.POSITIONAL_OR_KEYWORD), Parameter("value", Parameter.POSITIONAL_OR_KEYWORD), ]) # .preopen_directories assert Wasi.preopen_directories.__text_signature__ == "($self, preopen_directories)" assert signature(Wasi.preopen_directories) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("preopen_directories", Parameter.POSITIONAL_OR_KEYWORD), ]) # .preopen_directory assert Wasi.preopen_directory.__text_signature__ == "($self, preopen_directory)" assert signature(Wasi.preopen_directory) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("preopen_directory", Parameter.POSITIONAL_OR_KEYWORD), ]) # .map_directories assert Wasi.map_directories.__text_signature__ == "($self, map_directories)" assert signature(Wasi.map_directories) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("map_directories", Parameter.POSITIONAL_OR_KEYWORD), ]) # .map_directory assert Wasi.map_directory.__text_signature__ == "($self, alias, directory)" assert signature(Wasi.map_directory) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("alias", Parameter.POSITIONAL_OR_KEYWORD), Parameter("directory", Parameter.POSITIONAL_OR_KEYWORD), ]) # .generate_import_object_for_module assert Wasi.generate_import_object_for_module.__text_signature__ == "($self, module, version=0)" assert signature(Wasi.generate_import_object_for_module) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("module", Parameter.POSITIONAL_OR_KEYWORD), Parameter("version", Parameter.POSITIONAL_OR_KEYWORD, default=0), ])
def auth_endpoint(func): send_token = False send_request = False func_sig = signature(func) params = list(func_sig.parameters.values()) for ind, param in enumerate(params.copy()): if param.name == 'request' and param._annotation == Request: send_request = True if param.name == 'token' and param.annotation == str: send_token = True params.pop(ind) token_parameter = Parameter('token', kind=Parameter.POSITIONAL_OR_KEYWORD, default=Depends(self.oauth2_scheme), annotation=str) if not send_request: request_parameter = Parameter( 'request', kind=Parameter.POSITIONAL_OR_KEYWORD, annotation=Request) args_index = [str(p) for p in params] kwarg_index = None for i, v in enumerate(args_index): if '**' in v: kwarg_index = i arg_index = None for i, v in enumerate(args_index): if '*' in v and not i == kwarg_index: arg_index = i if arg_index: if not send_request: params.insert(0, request_parameter) params.insert(arg_index - 1, token_parameter) elif not kwarg_index: if not send_request: params.insert(0, request_parameter) params.append(token_parameter) ## ** kwargs else: if not send_request: params.insert(0, request_parameter) params.insert(kwarg_index - 1, token_parameter) new_sig = func_sig.replace(parameters=params) @wraps(func, new_sig=new_sig) async def mock_function(*args, **kwargs): request = kwargs['request'] token = kwargs['token'] if token == 'NO_TOKEN': if response_class is HTMLResponse or 'text/html' in request.headers[ 'accept']: response = HTMLResponse(self.admin.login_page( welcome_message='Login Required'), status_code=401) response.set_cookie('token', 'INVALID') response.set_cookie('ref', request.__dict__['scope']['path']) return response try: token = self.decode_token(token)[1] self.log.debug(f"decoded token: {token}") except Exception: self.log.exception(f"error decoding token") if response_class is HTMLResponse: response = HTMLResponse(self.admin.login_page( welcome_message='Login Required'), status_code=401) response.set_cookie('token', 'INVALID') response.set_cookie('ref', request.__dict__['scope']['path']) return response raise HTTPException( status_code=401, detail=f"not authorized, invalid or expired") allowed = False for auth_type, values in permissions.items(): if not auth_type in token['permissions']: self.log.warning(f"{auth_type} is required") continue for value in values: if value in token['permissions'][auth_type]: allowed = True break if not allowed: if response_class is HTMLResponse: response = HTMLResponse(self.admin.forbidden_page(), status_code=403) response.set_cookie('token', 'INVALID') return response raise HTTPException( status_code=403, detail= f"not authorized, permissions required: {permissions}") if 'access_token' in kwargs: kwargs['access_token'] = token if not send_token: del kwargs['token'] if not send_request: del kwargs['request'] result = func(*args, **kwargs) if asyncio.iscoroutine(result): return await result return result mock_function.__name__ = func.__name__ route = getattr(self.server, method) route(path, *args, **kwargs)(mock_function) return mock_function
except ImportError: # py2 from funcsigs import Signature, Parameter import pytest __all__ = ['simplified_test_function', 'ignore_warnings', 'expect_warnings'] # Pytest determines the signature of the test function by unpacking any # wrapped functions (this is the default of the signature() function it # uses. We correct this behavior by setting the __signature__ attribute of # the wrapper function to its correct signature. To do that, we cannot use # signature() because its follow_wrapped parameter was introduced only in # Python 3.5. Instead, we build the signature manually. TESTFUNC_SIGNATURE = Signature( parameters=[ Parameter('desc', Parameter.POSITIONAL_OR_KEYWORD), Parameter('kwargs', Parameter.POSITIONAL_OR_KEYWORD), Parameter('exp_exc_types', Parameter.POSITIONAL_OR_KEYWORD), Parameter('exp_warn_types', Parameter.POSITIONAL_OR_KEYWORD), Parameter('condition', Parameter.POSITIONAL_OR_KEYWORD), ] ) def simplified_test_function(test_func): """ A decorator for test functions that simplifies the test function by handling a number of things: * Skipping the test if the `condition` item in the testcase is `False`, * Invoking the Python debugger if the `condition` item in the testcase is
def make_signature(names): # Build a function signature object return Signature( Parameter(name, Parameter.POSITIONAL_OR_KEYWORD) for name in names)
def steps_decorator(test_func): """ The test function decorator. When a function is decorated it - checks that the function is a generator - checks that the function signature does not contain our private name `GENERATOR_MODE_STEP_ARGNAME` "by chance" - wraps the function :param test_func: :return: """ # ------VALIDATION ------- # Check if function is a generator if not isgeneratorfunction(test_func): raise ValueError( "Decorated function is not a generator. You either forgot to add `yield` statements in " "its body, or you are using mode='generator' instead of mode='parametrizer' or 'auto'." "See help(pytest_steps) for details") # check that our name for the additional 'test step' parameter is valid (it does not exist in f signature) f_sig = signature(test_func) test_step_argname = GENERATOR_MODE_STEP_ARGNAME if test_step_argname in f_sig.parameters: raise ValueError( "Your test function relies on arg name %s that is needed by @test_steps in generator " "mode" % test_step_argname) # ------CORE ------- # Transform the steps into ids if needed step_ids = [create_pytest_param_str_id(f) for f in steps] # Create the container that will hold all execution monitors for this function # TODO maybe have later a single 'monitor' instance at plugin level... like in pytest-benchmark all_monitors = StepMonitorsContainer(test_func, step_ids) # Create the function wrapper. # We will expose a new signature with additional 'request' arguments if needed, and the test step orig_sig = signature(test_func) func_needs_request = 'request' in orig_sig.parameters additional_params = ((Parameter( test_step_argname, kind=Parameter.POSITIONAL_OR_KEYWORD), ) + ( (Parameter('request', kind=Parameter.POSITIONAL_OR_KEYWORD), ) if not func_needs_request else ())) # add request parameter last, as first may be 'self' new_sig = add_signature_parameters(orig_sig, last=additional_params) # -- first create the logic @wraps(test_func, new_sig=new_sig) def wrapped_test_function(*args, **kwargs): step_name = kwargs.pop(test_step_argname) request = kwargs['request'] if func_needs_request else kwargs.pop( 'request') if request is None: # we are manually called outside of pytest. let's execute all steps at nce if step_name is None: # print("@test_steps - decorated function '%s' is being called manually. The `%s` parameter is set " # "to None so all steps will be executed in order" % (f, test_step_argname)) step_names = step_ids else: # print("@test_steps - decorated function '%s' is being called manually. The `%s` parameter is set " # "to %s so only these steps will be executed in order. Note that the order should be # feasible" # "" % (f, test_step_argname, step_name)) if not isinstance(step_name, (list, tuple)): step_names = [create_pytest_param_str_id(step_name)] else: step_names = [ create_pytest_param_str_id(f) for f in step_name ] steps_monitor = StepsMonitor(step_ids, test_func, args, kwargs) for i, (step_name, ref_step_name) in enumerate(zip(step_names, step_ids)): if step_name != ref_step_name: raise ValueError( "Incorrect sequence of steps provided for manual execution. Step #%s should" " be named '%s', found '%s'" % (i + 1, ref_step_name, step_name)) steps_monitor.execute(step_name, args, kwargs) else: # Retrieve or create the corresponding execution monitor steps_monitor = all_monitors.get_execution_monitor( request.node, args, kwargs) # execute the step # print("DEBUG - executing step %s" % step_name) steps_monitor.execute(step_name, args, kwargs) # With this hack we will be ordered correctly by pytest https://github.com/pytest-dev/pytest/issues/4429 wrapped_test_function.place_as = test_func # Parametrize the wrapper function with the test step ids parametrizer = pytest.mark.parametrize(test_step_argname, step_ids, ids=str) # finally apply parametrizer parametrized_step_function_wrapper = parametrizer( wrapped_test_function) return parametrized_step_function_wrapper
from inspect import signature, Signature, Parameter def func(a, b=42, *args, d=None, **kwargs): pass if __name__ == '__main__': sig1 = signature(func) print(sig1) parms = [ Parameter('a', Parameter.POSITIONAL_OR_KEYWORD), Parameter('b', Parameter.POSITIONAL_OR_KEYWORD, default=42), Parameter('args', Parameter.VAR_POSITIONAL), Parameter('d', Parameter.KEYWORD_ONLY, default=None), Parameter('kwargs', Parameter.VAR_KEYWORD) ] sig2 = Signature(parms) print(sig2) print(f'sig1 == sig2: {sig1 == sig2}')
def saved_fixture( store='fixture_store', # type: Union[str, Dict[str, Any]] key=None, # type: str views=None, # type: Dict[str, Callable[[Any], Any]] save_raw=None, # type: bool fixture_fun=DECORATED): """ Decorates a fixture so that it is saved in `store`. `store` can be a dict-like variable or a string representing a fixture name to a dict-like session-scoped fixture. By default it uses the global 'fixture_store' fixture provided by this plugin. After executing all tests, `<store>` will contain a new item under key `<key>` (default is the name of the fixture). This item is a dictionary <test_id>: <fixture_value> for each test node where the fixture was setup. ```python import pytest from pytest_harvest import saved_fixture @pytest.fixture @saved_fixture def dummy(): return 1 def test_dummy(dummy): pass def test_synthesis(fixture_store): print(fixture_store['dummy']) ``` Note that for session-scoped and module-scoped fixtures, not all test ids will appear in the store - only those for which the fixture was (re)created. Users can save additional views created from the fixture instance by applying transforms (callable functions). To do this, users can provide a dictionary under the `views` argument, containing a `{<key>: <procedure>}` dict-like. For each entry, `<procedure>` will be applied on the fixture instance, and the result will be stored under `<key>`. `save_raw` controls whether the fixture instance should still be saved in this case (default: `True` if `views is None`, `False` otherwise). :param store: a dict-like object or a fixture name corresponding to a dict-like object. in this dictionary, a new entry will be added for the fixture. This entry will contain a dictionary <test_id>: <fixture_value> for each test node. By default fixtures are stored in the `fixture_store``fixture. :param key: the name associated with the stored fixture in the store. By default this is the fixture name. :param views: an optional dictionary that can be provided to store views created from the fixture, rather than (or in addition to, if `save_raw=True`) the fixture itself. The dict should contain a `{<key>: <procedure>}` dict-like. For each entry, `<procedure>` will be applied on the fixture instance, and the result will be stored under `<key>`. :param save_raw: controls whether the fixture instance should be saved. `None` (Default) is an automatic behaviour meaning "`True` if `views is None`, `False` otherwise". :return: a fixture that will be stored """ # default: if views is None, we save the raw fixture. If user wants to save views, we do not save the raw if save_raw is None: save_raw = views is None # Simply get the function name fixture_name = getattr(fixture_fun, '__name__', None) or str(fixture_fun) # Name to use for storage key = key or fixture_name # is the store a fixture or an object ? store_is_a_fixture = isinstance(store, string_types) # if the store object is already available, we can ensure that it is initialized. Otherwise trust pytest for that if not store_is_a_fixture: if key in store.keys(): raise ValueError( "Key '%s' already exists in store object. Please make sure that your store object is " "properly initialized as an empty dict-like object, and/or provide a different custom " "`name` if two stored fixtures share the same storage key name." ) # Note: we can not init the storage[key] entry here because when storage is a fixture, it does not yet exist. def _init_and_check(request, store): """Performs a few checks and returns the key to use for saving (the node id)""" # find the current node id depending on the scope scope = get_scope(request) if scope == 'function': nodeid = request.node.nodeid else: # session- or module-scope # raise Exception("The `@saved_fixture` decorator is only applicable to function-scope fixtures. `%s`" # " seems to have scope='%s'. Consider removing `@saved_fixture` or changing " # "the scope to 'function'." % (fixture_fun, scope)) nodeid = request._pyfuncitem.nodeid # Init storage if needed if save_raw: # init if key not in store: store[key] = OrderedDict() # Check that the node id is unique if nodeid in store[key]: raise KeyError( "Internal Error - This fixture '%s' was already " "stored for test id '%s'" % (key, nodeid)) if views is not None: for k in views.keys(): if k not in store: store[k] = OrderedDict() # Check that the node id is unique if nodeid in store[k]: raise KeyError( "Internal Error - This fixture view '%s' was already " "stored for test id '%s'" % (k, nodeid)) return nodeid # We will expose a new signature with additional arguments orig_sig = signature(fixture_fun) func_needs_request = 'request' in orig_sig.parameters new_args = ((Parameter('request', kind=Parameter.POSITIONAL_OR_KEYWORD),) if not func_needs_request else ()) \ + ((Parameter(store, kind=Parameter.POSITIONAL_OR_KEYWORD),) if store_is_a_fixture else ()) new_sig = add_signature_parameters(orig_sig, first=new_args) # Wrap the fixture in the correct mode (generator or not) if not isgeneratorfunction(fixture_fun): @wraps(fixture_fun, new_sig=new_sig) def stored_fixture_function(*args, **kwargs): """Wraps a fixture so as to store it before it is returned""" # get the actual store object if store_is_a_fixture: store_ = kwargs.pop(store) # read and remove it else: # use the variable from outer scope (from `make_saved_fixture`) store_ = store request = kwargs['request'] if func_needs_request else kwargs.pop( 'request') nodeid = _init_and_check(request, store_) fixture_value = fixture_fun(*args, **kwargs) # Get the fixture _store_fixture_and_views(store_, nodeid, key, fixture_value, views, save_raw) # Store it return fixture_value # Return it else: @wraps(fixture_fun, new_sig=new_sig) def stored_fixture_function(*args, **kwargs): """Wraps a fixture so as to store it before it is returned (generator mode)""" # get the actual store object if store_is_a_fixture: store_ = kwargs.pop(store) else: # use the variable from outer scope (from `make_saved_fixture`) store_ = store request = kwargs['request'] if func_needs_request else kwargs.pop( 'request') nodeid = _init_and_check(request, store_) gen = fixture_fun(*args, **kwargs) fixture_value = next(gen) # Get the fixture _store_fixture_and_views(store_, nodeid, key, fixture_value, views, save_raw) # Store it yield fixture_value # Return it # Make sure to terminate the underlying generator try: next(gen) except StopIteration: pass return stored_fixture_function
def helper_single_param(func, *, param_name: str): """Check text_signature and signature for function with one parameter (except `self`)""" assert func.__text_signature__ == "({})".format(param_name) assert signature(func) == Signature( [Parameter(param_name, Parameter.POSITIONAL_OR_KEYWORD)])
def ufunc(np_ufunc, return_type_override=None): if not isinstance(np_ufunc, np.ufunc): raise TypeError("Must be an instance of `np.ufunc`, got {}".format( type(np_ufunc))) def wf_ufunc(*args): if len(args) != np_ufunc.nin: raise TypeError( "Invalid number of arguments to function `{}`".format( np_ufunc.__name__)) # Since typecheck_promote doesn't support variadic arguments, manually # attempt to promote each argument to an Array or scalar promoted = [] for i, arg in enumerate(args): try: if isinstance(arg, BaseArray): promoted.append(arg) elif isinstance(arg, np.ma.core.MaskedArray): promoted.append(MaskedArray._promote(arg)) elif isinstance(arg, np.ndarray): promoted.append(Array._promote(arg)) else: promoted.append( _promote(arg, (Bool, Int, Float, Scalar), i, np_ufunc.__name__)) # TODO(gabe) not great to be relying on internal `_promote` here except (ProxyTypeError, TypeError): raise ProxyTypeError( "Argument {} to function {} must be a Workflows Array, Scalar, Int, Float," "Bool, or a type promotable to one of those, not {}". format(i + 1, np_ufunc.__name__, type(arg))) return_type = _ufunc_result_type( *promoted, return_type_override=return_type_override) return return_type._from_apply("wf.numpy." + np_ufunc.__name__, *promoted) HANDLED_UFUNCS[np_ufunc.__name__] = wf_ufunc copy_docstring_from_numpy(wf_ufunc, np_ufunc) # create an inspect.Signature object return_annotation = (return_type_override if return_type_override is not None else Union[Array, MaskedArray, Scalar, Int, Float, Bool]) if np_ufunc.nin == 2: signature = Signature( [ Parameter( "x1", Parameter.POSITIONAL_OR_KEYWORD, annotation=Union[Array, MaskedArray, Scalar, Int, Float, Bool], ), Parameter( "x2", Parameter.POSITIONAL_OR_KEYWORD, annotation=Union[Array, MaskedArray, Scalar, Int, Float, Bool], ), ], return_annotation=return_annotation, ) else: signature = Signature( [ Parameter( "x", Parameter.POSITIONAL_OR_KEYWORD, annotation=Union[Array, MaskedArray, Scalar, Int, Float, Bool], ) ], return_annotation=return_annotation, ) # set __annotations__ if "sphinx" not in sys.modules: wf_ufunc.__annotations__ = { name: param.annotation for name, param in signature.parameters.items() } wf_ufunc.__annotations__["return"] = signature.return_annotation # set __signature__ to the stringified version wf_ufunc.__signature__ = stringify_signature(signature) # forward attributes wf_ufunc.nin = np_ufunc.nin wf_ufunc.nargs = np_ufunc.nargs wf_ufunc.nout = np_ufunc.nout wf_ufunc.ntypes = np_ufunc.ntypes wf_ufunc.identity = np_ufunc.identity wf_ufunc.signature = np_ufunc.signature wf_ufunc.types = np_ufunc.types # NOTE: we currently don't support other attributes of ufuncs (besides `__call__`) wf_ufunc.reduce = lambda *args: raise_( NotImplementedError( "The `reduce` ufunc method is not supported by Workflows types")) wf_ufunc.reduceat = lambda *args: raise_( NotImplementedError( "The `reduceat` ufunc method is not supported by Workflows types")) wf_ufunc.accumulate = lambda *args: raise_( NotImplementedError( "The `accumulate` ufunc method is not supported by Workflows types" )) wf_ufunc.outer = lambda *args: raise_( NotImplementedError( "The `outer` ufunc method is not supported by Workflows types")) return wf_ufunc
def helper_self(func): assert func.__text_signature__ == "($self)" assert signature(func) == Signature( [Parameter("self", Parameter.POSITIONAL_ONLY)])
def __init__(self, backends: Sequence[BaseAuthentication], user_db: BaseUserDatabase): self.backends = backends self.user_db = user_db parameters = [ Parameter(name=name_to_variable_name(backend.name), kind=Parameter.POSITIONAL_OR_KEYWORD, default=Depends(backend.scheme)) for backend in self.backends ] signature = Signature(parameters) @with_signature(signature, func_name='get_optional_current_user') async def get_optional_current_user(*args, **kwargs): return await self.authenticate(*args, **kwargs) @with_signature(signature, func_name='get_optional_current_active_user') async def get_optional_current_active_user(*args, **kwargs): user = await get_optional_current_user(*args, **kwargs) if not user or not user.is_active: return None return user @with_signature(signature, func_name='get_optional_current_superuser') async def get_optional_current_superuser(*args, **kwargs): user = await get_optional_current_active_user(*args, **kwargs) if not user or not user.is_superuser: return None return user @with_signature(signature, func_name='get_current_user') async def get_current_user(*args, **kwargs): user = await get_optional_current_user(*args, **kwargs) if user is None: raise HTTPException(status_code=403, detail='You do not have the permission') return user @with_signature(signature, func_name='get_current_active_user') async def get_current_active_user(*args, **kwargs): user = await get_optional_current_active_user(*args, **kwargs) if user is None: raise HTTPException(status_code=403, detail='You do not have the permission') return user @with_signature(signature, func_name='get_current_superuser') async def get_current_superuser(*args, **kwargs): user = await get_optional_current_active_user(*args, **kwargs) if user is None: raise HTTPException(status_code=403, detail='You do not have the permission') if not user.is_superuser: raise HTTPException(status_code=403, detail='You need to be superuser') return user self.get_current_user = get_current_user self.get_current_active_user = get_current_active_user self.get_optional_current_active_user = get_optional_current_active_user self.get_optional_current_superuser = get_optional_current_superuser self.get_optional_current_user = get_optional_current_user self.get_current_superuser = get_current_superuser
def helper_offset(func): assert func.__text_signature__ == "($self, offset=0)" assert signature(func) == Signature([ Parameter("self", Parameter.POSITIONAL_ONLY), Parameter("offset", Parameter.POSITIONAL_OR_KEYWORD, default=0), ])
class Sample: __signature__ = Signature(parameters=[Parameter('test', Parameter.POSITIONAL_OR_KEYWORD)])
def register_single_type_tests(plugin, list_of_params): for generator in list_of_params: for idx, param in enumerate(generator, 1): action_id = f'test_{generator.__name__}_{idx}' function_parameters = [] qiime_annotations = {} defaults = {} # Required param_name = param.base_name function_parameters.append( Parameter(param_name, Parameter.POSITIONAL_OR_KEYWORD, annotation=param.view_type)) qiime_annotations[param_name] = param.qiime_type # Optional param_name = f'optional_{param.base_name}' function_parameters.append( Parameter(param_name, Parameter.POSITIONAL_OR_KEYWORD, annotation=param.view_type, default=None)) qiime_annotations[param_name] = param.qiime_type defaults[param_name] = None if (not is_semantic_type(param.qiime_type) and not is_metadata_type(param.qiime_type)): # Defaults for i_, default in enumerate(param.domain, 1): param_name = f'default{i_}_{param.base_name}' function_parameters.append( Parameter(param_name, Parameter.POSITIONAL_OR_KEYWORD, annotation=param.view_type, default=default)) qiime_annotations[param_name] = param.qiime_type defaults[param_name] = default func = function_template_1output disguise_function(func, action_id, function_parameters, 1) qiime_inputs = {} qiime_parameters = {} qiime_outputs = [('only_output', EchoOutput)] for name, val in qiime_annotations.items(): if is_semantic_type(val): qiime_inputs[name] = val else: qiime_parameters[name] = val usage_examples = {} for i, arg in enumerate(param.domain): usage_examples[f'example_{i}'] = UsageInstantiator( action_id, {param.base_name: param}, {param.base_name: arg}, qiime_outputs, defaults) # one more with defaults all passed a different value new_inputs = { # the last iteration is fine, we aren't testing this param param.base_name: arg } shifted_args = deque([arg, *param.domain]) shifted_args.rotate(1) for key, arg in zip(defaults, shifted_args): new_inputs[key] = arg usage_examples[f'example_{i+1}'] = UsageInstantiator( action_id, {k: param for k in chain([param.base_name], defaults)}, new_inputs, qiime_outputs) # prove we can still pass defaults manually usage_examples[f'example_{i+2}'] = UsageInstantiator( action_id, {k: param for k in chain([param.base_name], defaults)}, { param.base_name: arg, **defaults }, qiime_outputs) plugin.methods.register_function( function=func, inputs=qiime_inputs, parameters=qiime_parameters, outputs=qiime_outputs, input_descriptions={}, parameter_descriptions={}, output_descriptions={}, name=f'Phrase describing {action_id.replace("_", "-")}', description=LOREM_IPSUM, examples=usage_examples)
def _mk_param(nm, **kwargs): return Parameter(nm, kind=Parameter.POSITIONAL_OR_KEYWORD, **kwargs)
This script shows how to check the passed arguments to see if they match a specific signature when using general purpose functions using kwargs and args. To do this you can use the signature features in the 'inspect' module. The classes to use are Signature and Parameter. Below an example of a function signature is shown """ from inspect import Signature, Parameter import inspect # Make a signature for a func(x, y=42, *, z=None) parms = [ Parameter("x", Parameter.POSITIONAL_OR_KEYWORD), Parameter("y", Parameter.POSITIONAL_OR_KEYWORD, default=42), Parameter("z", Parameter.KEYWORD_ONLY, default=None), ] sig = Signature(parms) print(sig) # Bind signature to *args and **kwargs using bind() def func(*args, **kwargs): bound_vales = sig.bind(*args, **kwargs) for name, value in bound_vales.arguments.items(): print(name, value)
def decorator(func): # Build new signature. original_sig = signature(func) var_keyword = '' var_positional = '' parameters = OrderedDict() original_positional = OrderedDict() # Add positional arguments and keywords first. for name, param in original_sig.parameters.items(): if param.kind == param.VAR_KEYWORD: var_keyword = name elif param.kind == param.VAR_POSITIONAL: var_positional = name else: default = default_kwargs.get(name, param.default) param = Parameter(name, param.kind, default=default) parameters[name] = param original_positional[name] = param # Add var positional. if var_positional: parameters[var_positional] = Parameter(var_positional, Parameter.VAR_POSITIONAL) # Add remainder defualt_kwargs as keyword only variables. for name, value in default_kwargs.items(): if name not in parameters: param = Parameter(name, Parameter.KEYWORD_ONLY, default=value) parameters[name] = param # Add var keyword. if var_keyword: parameters[var_keyword] = Parameter(var_keyword, Parameter.VAR_KEYWORD) # Build our inner wrapper signature. wrapper_sig = original_sig.replace(parameters=parameters.values()) # Remove cloaked var arguments. if var_positional and hide_var_positional: parameters.pop(var_positional) if var_keyword and hide_var_keyword: parameters.pop(var_keyword) cloak_sig = original_sig.replace(parameters=parameters.values()) # Wrapper function. function_defaults = set(original_sig.parameters) override_defaults = set(default_kwargs).intersection(function_defaults) def wrapper(*args, **kwargs): # Build new kwargs and args. arguments = OrderedDict(wrapper_sig.bind(*args, **kwargs).arguments) kwargs = {} updates = {} # Build the positional arguments. Override func's default values. ordered_args = OrderedDict() for name in original_positional: if name in arguments: ordered_args[name] = arguments[name] if name in default_kwargs: updates[name] = arguments[name] elif name in default_kwargs: ordered_args[name] = default_kwargs[name] updates[name] = default_kwargs[name] # Now handle the keyword only and var arguments args = list(ordered_args.values()) for name in set(arguments).difference(ordered_args): value = arguments[name] if name == var_positional: args.extend(value) elif name == var_keyword: for k, v in value.items(): kwargs[k] = v else: # Update keyword only values if name in default_kwargs: updates[name] = value kwargs[name] = value default_kwargs.update(updates) if var_keyword: # Add default_kwargs keyword values not defined in kwargs. for k in set(default_kwargs).difference(kwargs): if k not in original_positional: kwargs[k] = default_kwargs[k] else: # Remove kwargs that func doesn't have defined. for k in set(kwargs).difference(function_defaults): kwargs.pop(k) return func(*args, **kwargs) # Return wrapped up func with the cloaked signature. functools.update_wrapper(wrapper, func) wrapper.__signature__ = cloak_sig return wrapper
def _decorate_fixture_plus( fixture_func, scope="function", # type: str autouse=False, # type: bool name=None, # type: str unpack_into=None, # type: Iterable[str] hook=None, # type: Callable[[Callable], Callable] _caller_module_offset_when_unpack=3, # type: int **kwargs): """ decorator to mark a fixture factory function. Identical to `@pytest.fixture` decorator, except that - it supports multi-parametrization with `@pytest.mark.parametrize` as requested in https://github.com/pytest-dev/pytest/issues/3960. As a consequence it does not support the `params` and `ids` arguments anymore. - it supports a new argument `unpack_into` where you can provide names for fixtures where to unpack this fixture into. :param scope: the scope for which this fixture is shared, one of "function" (default), "class", "module" or "session". :param autouse: if True, the fixture func is activated for all tests that can see it. If False (the default) then an explicit reference is needed to activate the fixture. :param name: the name of the fixture. This defaults to the name of the decorated function. Note: If a fixture is used in the same module in which it is defined, the function name of the fixture will be shadowed by the function arg that requests the fixture; one way to resolve this is to name the decorated function ``fixture_<fixturename>`` and then use ``@pytest.fixture(name='<fixturename>')``. :param unpack_into: an optional iterable of names, or string containing coma-separated names, for additional fixtures to create to represent parts of this fixture. See `unpack_fixture` for details. :param hook: an optional hook to apply to each fixture function that is created during this call. The hook function will be called everytime a fixture is about to be created. It will receive a single argument (the function implementing the fixture) and should return the function to use. For example you can use `saved_fixture` from `pytest-harvest` as a hook in order to save all such created fixtures in the fixture store. :param kwargs: other keyword arguments for `@pytest.fixture` """ if name is not None: # Compatibility for the 'name' argument if PYTEST3_OR_GREATER: # pytest version supports "name" keyword argument kwargs['name'] = name elif name is not None: # 'name' argument is not supported in this old version, use the __name__ trick. fixture_func.__name__ = name # if unpacking is requested, do it first if unpack_into is not None: # get the future fixture name if needed if name is None: name = fixture_func.__name__ # get caller module to create the symbols caller_module = get_caller_module( frame_offset=_caller_module_offset_when_unpack) _make_unpack_fixture(caller_module, unpack_into, name, hook=hook) # (1) Collect all @pytest.mark.parametrize markers (including those created by usage of @cases_data) parametrizer_marks = get_pytest_parametrize_marks(fixture_func) if len(parametrizer_marks) < 1: # make the fixture union-aware wrapped_fixture_func = ignore_unused(fixture_func) # resolve possibly infinite generators of ids here if 'params' in kwargs and 'ids' in kwargs: kwargs['ids'] = resolve_ids(kwargs['ids'], kwargs['params'], full_resolve=False) # transform the created wrapper into a fixture return pytest_fixture(scope=scope, autouse=autouse, hook=hook, **kwargs)(wrapped_fixture_func) else: if 'params' in kwargs: raise ValueError( "With `fixture_plus` you cannot mix usage of the keyword argument `params` and of " "the pytest.mark.parametrize marks") # (2) create the huge "param" containing all params combined # --loop (use the same order to get it right) params_names_or_name_combinations = [] params_values = [] params_ids = [] params_marks = [] for pmark in parametrizer_marks: # -- pmark is a single @pytest.parametrize mark. -- # check number of parameter names in this parameterset if len(pmark.param_names) < 1: raise ValueError( "Fixture function '%s' decorated with '@fixture_plus' has an empty parameter " "name in a @pytest.mark.parametrize mark") # remember the argnames params_names_or_name_combinations.append(pmark.param_names) # separate specific configuration (pytest.param()) from the values custom_pids, _pmarks, _pvalues = extract_parameterset_info( pmark.param_names, pmark.param_values, check_nb=True) # get the ids by merging/creating the various possibilities _paramids = make_test_ids(argnames=pmark.param_names, argvalues=_pvalues, global_ids=pmark.param_ids, id_marks=custom_pids) # Finally store the ids, marks, and values for this parameterset params_ids.append(_paramids) params_marks.append(tuple(_pmarks)) params_values.append(tuple(_pvalues)) # (3) generate the ids and values, possibly reapplying marks if len(params_names_or_name_combinations) == 1: # we can simplify - that will be more readable final_ids = params_ids[0] final_marks = params_marks[0] final_values = list(params_values[0]) # reapply the marks for i, marks in enumerate(final_marks): if marks is not None: final_values[i] = make_marked_parameter_value( (final_values[i], ), marks=marks) else: final_values = list(product(*params_values)) final_ids = combine_ids(product(*params_ids)) final_marks = tuple(product(*params_marks)) # reapply the marks for i, marks in enumerate(final_marks): ms = [m for mm in marks if mm is not None for m in mm] if len(ms) > 0: final_values[i] = make_marked_parameter_value( (final_values[i], ), marks=ms) if len(final_values) != len(final_ids): raise ValueError( "Internal error related to fixture parametrization- please report") # (4) wrap the fixture function so as to remove the parameter names and add 'request' if needed all_param_names = tuple(v for pnames in params_names_or_name_combinations for v in pnames) # --create the new signature that we want to expose to pytest old_sig = signature(fixture_func) for p in all_param_names: if p not in old_sig.parameters: raise ValueError( "parameter '%s' not found in fixture signature '%s%s'" "" % (p, fixture_func.__name__, old_sig)) new_sig = remove_signature_parameters(old_sig, *all_param_names) # add request if needed func_needs_request = 'request' in old_sig.parameters if not func_needs_request: new_sig = add_signature_parameters( new_sig, first=Parameter('request', kind=Parameter.POSITIONAL_OR_KEYWORD)) # --common routine used below. Fills kwargs with the appropriate names and values from fixture_params def _map_arguments(*_args, **_kwargs): request = _kwargs['request'] if func_needs_request else _kwargs.pop( 'request') # populate the parameters if len(params_names_or_name_combinations) == 1: _params = [request.param] # remove the simplification else: _params = request.param for p_names, fixture_param_value in zip( params_names_or_name_combinations, _params): if len(p_names) == 1: # a single parameter for that generated fixture (@pytest.mark.parametrize with a single name) _kwargs[p_names[0]] = get_lazy_args(fixture_param_value, request) else: # several parameters for that generated fixture (@pytest.mark.parametrize with several names) # unpack all of them and inject them in the kwargs for old_p_name, old_p_value in zip(p_names, fixture_param_value): _kwargs[old_p_name] = get_lazy_args(old_p_value, request) return _args, _kwargs # --Finally create the fixture function, a wrapper of user-provided fixture with the new signature if not isgeneratorfunction(fixture_func): # normal function with return statement @wraps(fixture_func, new_sig=new_sig) def wrapped_fixture_func(*_args, **_kwargs): if not is_used_request(_kwargs['request']): return NOT_USED else: _args, _kwargs = _map_arguments(*_args, **_kwargs) return fixture_func(*_args, **_kwargs) else: # generator function (with a yield statement) @wraps(fixture_func, new_sig=new_sig) def wrapped_fixture_func(*_args, **_kwargs): if not is_used_request(_kwargs['request']): yield NOT_USED else: _args, _kwargs = _map_arguments(*_args, **_kwargs) for res in fixture_func(*_args, **_kwargs): yield res # transform the created wrapper into a fixture _make_fix = pytest_fixture(scope=scope, params=final_values, autouse=autouse, hook=hook, ids=final_ids, **kwargs) return _make_fix(wrapped_fixture_func)
def _create_builder(cls, element, completions): def builder(cls, spec=None, **kws): spec = element if spec is None else '%s.%s' % (element, spec) prefix = 'In opts.{element}(...), '.format(element=element) backend = kws.get('backend', None) keys = set(kws.keys()) if backend: allowed_kws = cls._element_keywords(backend, elements=[element ])[element] invalid = keys - set(allowed_kws) else: mismatched = {} all_valid_kws = set() for loaded_backend in Store.loaded_backends(): valid = set( cls._element_keywords(loaded_backend).get(element, [])) all_valid_kws |= set(valid) if keys <= valid: # Found a backend for which all keys are valid return Options(spec, **kws) mismatched[loaded_backend] = list(keys - valid) invalid = keys - all_valid_kws # Keys not found for any backend if mismatched and not invalid: # Keys found across multiple backends msg = ( '{prefix} keywords supplied are mixed across backends. ' 'Keyword(s) {info}') info = ', '.join('%s are invalid for %s' % (', '.join(repr(el) for el in v), k) for k, v in mismatched.items()) raise ValueError(msg.format(info=info, prefix=prefix)) allowed_kws = completions reraise = False if invalid: try: cls._options_error( list(invalid)[0], element, backend, allowed_kws) except ValueError as e: msg = str(e)[0].lower() + str(e)[1:] reraise = True if reraise: raise ValueError(prefix + msg) return Options(spec, **kws) filtered_keywords = [ k for k in completions if k not in cls._no_completion ] sorted_kw_set = sorted(set(filtered_keywords)) if sys.version_info.major == 2: kws = ', '.join('{opt}=None'.format(opt=opt) for opt in sorted_kw_set) builder.__doc__ = '{element}({kws})'.format(element=element, kws=kws) else: from inspect import Parameter, Signature signature = Signature( [Parameter('spec', Parameter.POSITIONAL_OR_KEYWORD)] + [ Parameter(kw, Parameter.KEYWORD_ONLY) for kw in sorted_kw_set ]) builder.__signature__ = signature return classmethod(builder)
def wrap_class(cls, params: Tuple[str, ...]): """ Wraps a class to comply with the bindable decoration: wraps all methods and adds bind & unbind functionality :param cls: Class to decorate :param params: Parameters to allow binding for :return: Decorated class """ implicit_resolution: ImplicitResolution = ImplicitResolution( Bindable.implicitly_resolve, *params) for func_name, (func, bound_func) in variables(cls).items(): # Check function is not reserved if func_name in ('bind', 'unbind', 'bind_like', 'bound_params'): continue # Decorate all non-protected functions if callable(bound_func) and (not func_name.startswith('_') or getattr(func, 'override_protection', False))\ and not isinstance(bound_func, staticmethod): setattr(cls, func_name, implicit_resolution(func)) # Update params to include previous bindings params: Tuple[str, ...] = tuple( set(getattr(cls, '_bindable_params', ()) + params)) setattr(cls, '_bindable_params', params) original_init = getattr(cls, '__init__', lambda self, *args, **kwargs: None) @wraps(original_init) def new_init(self, *args, **kwargs): # Add _bound_params dictionary to instance original_init(self, *args, **kwargs) self._bound_params = {} setattr(cls, '__init__', new_init) # Many possible bound parameters def bind(self: T, **kwargs): """Binds (the) parameters {0} to the instance""" problem: str = next( (kwarg for kwarg in kwargs if kwarg not in params), None) if problem: raise Exception( f"Cannot bind parameter [{problem}], was not declared bindable" ) for k, v in kwargs.items(): self._bound_params[k] = v def bind_like(self: T, *others: T): """Binds this instance like another instance""" if len(others) == 0: self._bound_params = {} return self._bound_params = getattr(others[0], '_bound_params', {}).copy() for other in others[1:]: for key, value in getattr(other, 'bound_params', {}).items(): if key in self._bound_params and self._bound_params.get( key) != value: self._bound_params.pop(key) def unbind(self: T, *names: str): """Unbinds (the) parameters {0} from the instance, pass no names to unbind all""" problem: str = next((name for name in names if name not in params), None) if problem: raise Exception( f"Cannot unbind parameter [{problem}], was not declared bindable" ) if len(names) == 0: names = tuple(self._bound_params.keys()) for name in names: if name in self._bound_params: self._bound_params.pop(name) @cached_property def bound_params(_self): class Proxy(Mapping): def __contains__(self, key: str): return key in getattr(_self, '_bound_params', {}) def __getitem__(self, key: str): return getattr(_self, '_bound_params', {})[key] def __iter__(self): return iter(getattr(_self, '_bound_params', {})) def __len__(self) -> int: return len(list(iter(self))) return Proxy() bind.__doc__ = bind.__doc__.format(params) unbind.__doc__ = unbind.__doc__.format(params) # Update binding signature to match possible bound parameters bind.__signature__ = signature(bind).replace(parameters=[ Parameter(name='self', kind=Parameter.POSITIONAL_OR_KEYWORD) ] + [ Parameter(name=name, kind=Parameter.KEYWORD_ONLY) for name in params ]) # Set name for cached property bound_params.__set_name__(cls, 'bound_params') # Add bind & unbind to the class setattr(cls, 'bind', bind) setattr(cls, 'unbind', unbind) setattr(cls, 'bind_like', bind_like) setattr(cls, 'bound_params', bound_params) return cls
def _preprocess_data(func=None, *, replace_names=None, label_namer=None): """ A decorator to add a 'data' kwarg to a function. When applied:: @_preprocess_data() def func(ax, *args, **kwargs): ... the signature is modified to ``decorated(ax, *args, data=None, **kwargs)`` with the following behavior: - if called with ``data=None``, forward the other arguments to ``func``; - otherwise, *data* must be a mapping; for any argument passed in as a string ``name``, replace the argument by ``data[name]`` (if this does not throw an exception), then forward the arguments to ``func``. In either case, any argument that is a `MappingView` is also converted to a list. Parameters ---------- replace_names : list of str or None, default: None The list of parameter names for which lookup into *data* should be attempted. If None, replacement is attempted for all arguments. label_namer : str, default: None If set e.g. to "namer" (which must be a kwarg in the function's signature -- not as ``**kwargs``), if the *namer* argument passed in is a (string) key of *data* and no *label* kwarg is passed, then use the (string) value of the *namer* as *label*. :: @_preprocess_data(label_namer="foo") def func(foo, label=None): ... func("key", data={"key": value}) # is equivalent to func.__wrapped__(value, label="key") """ if func is None: # Return the actual decorator. return functools.partial( _preprocess_data, replace_names=replace_names, label_namer=label_namer) sig = inspect.signature(func) varargs_name = None varkwargs_name = None arg_names = [] params = list(sig.parameters.values()) for p in params: if p.kind is Parameter.VAR_POSITIONAL: varargs_name = p.name elif p.kind is Parameter.VAR_KEYWORD: varkwargs_name = p.name else: arg_names.append(p.name) data_param = Parameter("data", Parameter.KEYWORD_ONLY, default=None) if varkwargs_name: params.insert(-1, data_param) else: params.append(data_param) new_sig = sig.replace(parameters=params) arg_names = arg_names[1:] # remove the first "ax" / self arg assert {*arg_names}.issuperset(replace_names or []) or varkwargs_name, ( "Matplotlib internal error: invalid replace_names ({!r}) for {!r}" .format(replace_names, func.__name__)) assert label_namer is None or label_namer in arg_names, ( "Matplotlib internal error: invalid label_namer ({!r}) for {!r}" .format(label_namer, func.__name__)) @functools.wraps(func) def inner(ax, *args, data=None, **kwargs): if data is None: return func(ax, *map(sanitize_sequence, args), **kwargs) bound = new_sig.bind(ax, *args, **kwargs) needs_label = (label_namer and "label" not in bound.arguments and "label" not in bound.kwargs) auto_label = (bound.arguments.get(label_namer) or bound.kwargs.get(label_namer)) for k, v in bound.arguments.items(): if k == varkwargs_name: for k1, v1 in v.items(): if replace_names is None or k1 in replace_names: v[k1] = _replacer(data, v1) elif k == varargs_name: if replace_names is None: bound.arguments[k] = tuple(_replacer(data, v1) for v1 in v) else: if replace_names is None or k in replace_names: bound.arguments[k] = _replacer(data, v) bound.apply_defaults() del bound.arguments["data"] if needs_label: all_kwargs = {**bound.arguments, **bound.kwargs} # label_namer will be in all_kwargs as we asserted above that # `label_namer is None or label_namer in arg_names`. label = _label_from_arg(all_kwargs[label_namer], auto_label) if "label" in arg_names: bound.arguments["label"] = label try: bound.arguments.move_to_end(varkwargs_name) except KeyError: pass else: bound.arguments.setdefault(varkwargs_name, {})["label"] = label return func(*bound.args, **bound.kwargs) inner.__doc__ = _add_data_doc(inner.__doc__, replace_names) inner.__signature__ = new_sig return inner
def add_arg(name: str, kind: Any, default: Optional[ast.expr]) -> None: default_val = Parameter.empty if default is None else _ValueFormatter(default) parameters.append(Parameter(name, kind, default=default_val))