def init_injector(self, components=None): app_components = list(WSGI_COMPONENTS + VALIDATION_COMPONENTS) for comp in (components or []): if isinstance(comp, str): comp = import_path(comp, ["components", "COMPONENTS"]) if isinstance(comp, Component): app_components.append(comp) elif isinstance(comp, (list, tuple)): for c in comp: if not isinstance(c, Component): msg = "Could not load component %r" raise exceptions.ConfigurationError(msg % c) app_components += list(comp) else: msg = "Could not load component %r" raise exceptions.ConfigurationError(msg % comp) initial_components = { 'environ': WSGIEnviron, 'start_response': WSGIStartResponse, 'exc': Exception, 'app': App, 'path_params': PathParams, 'route': Route, 'response': Response, 'settings': Settings } self.injector = Injector(app_components, initial_components)
def __init_subclass__(cls, **kwargs): if cls.singleton: if cls.can_handle_parameter is not Component.can_handle_parameter: msg = ( 'Component "%s" should not override `can_handle_parameter`, ' 'since it is a singleton') raise exceptions.ConfigurationError(msg % cls.__name__)
def generate_fields(self, url, method, handler): fields = [] path_names = [ item.strip("{}").lstrip("+") for item in re.findall("{[^}]*}", url) ] body_params = [] parameters = self.injector.resolve_validation_parameters(handler) for name, param in parameters.items(): if name in path_names: fields.append(self.generate_path_field(param)) elif is_schema(param.annotation): if method in ("GET", "DELETE"): fields += self.generate_query_fields_from_schema(param) else: fields.append( document.Field(name=name, location="body", schema=param.annotation)) body_params.append(param) else: fields += self.generate_query_fields(param) if len(body_params) > 1: params = "\n ".join(f"{x.name}: {x.annotation.__name__}" for x in body_params) msg = (f"\n\nUsing multiple body fields in {method} handler " f"`{handler.__module__}.{handler.__name__}` is confusing.\n" f"Use only one of the following parameters:\n {params}\n") raise exceptions.ConfigurationError(msg) return fields
def resolve_validation_parameters(self, func): unique = {} for holder, param in self._resolve_validation_parameters(func, set()): if param.name in unique: cur_holder, cur_param = unique[param.name] if cur_param != param: msg = "\nConflicting parameters:\n%s( .. %s ..)\nand\n%s ( .. %s ..)" raise exceptions.ConfigurationError(msg % (cur_holder, cur_param, holder, param)) elif not cur_param.description and param.description: unique[param.name] = (holder, param) else: unique[param.name] = (holder, param) return {k: v[1] for k, v in unique.items()}
def __init__(self, mod): tuple_settings = [ "COMPONENTS", "TEMPLATE_DIRS", "STATIC_DIRS", ] for setting in dir(mod): if setting.isupper(): setting_value = getattr(mod, setting) if (setting in tuple_settings and not isinstance(setting_value, (list, tuple))): msg = f"The {setting} setting must be a list or a tuple." raise exceptions.ConfigurationError(msg) setattr(self, setting, setting_value)
def _resolve_validation_parameters(self, func, seen_state): parameters = [] signature = inspect.signature(func) for parameter in signature.parameters.values(): if (parameter.annotation in (ReturnValue, inspect.Parameter) or parameter.annotation in self.reverse_initial): continue for component in self.components: if component.can_handle_parameter(parameter): identity = component.identity(parameter) if identity not in seen_state: seen_state.add(identity) params = component.get_validation_parameters(func, parameter) parameters += [(func, Parameter.from_obj(p)) for p in params] parameters += self._resolve_validation_parameters(component.resolve, seen_state) break else: msg = 'No component able to handle parameter "%s" on function "%s".' raise exceptions.ConfigurationError(msg % (parameter.name, func.__qualname__)) return parameters
def can_handle_parameter(self, parameter: inspect.Parameter): # Return `True` if this component can handle the given parameter. # # The default behavior is for components to handle whatever class # is used as the return annotation by the `resolve` method. # # You can override this for more customized styles, for example if you # wanted name-based parameter resolution, or if you want to provide # a value for a range of different types. # # Eg. Include the `Request` instance for any parameter named `request`. if inspect.isclass(self.resolve): return_annotation = self.resolve else: return_annotation = inspect.signature( self.resolve).return_annotation if return_annotation is inspect.Signature.empty: msg = ('Component "%s" must include a return annotation on the ' '`resolve()` method, or override `can_handle_parameter`.' ) % self.__class__.__name__ raise exceptions.ConfigurationError(msg) return parameter.annotation is return_annotation
def resolve_function(self, func, seen_state, output_name=None, parent_parameter=None, set_return=False): steps = [] kwargs = {} consts = {} signature = inspect.signature(func) if output_name is None: if inspect.isclass(func): return_annotation = func else: return_annotation = signature.return_annotation if return_annotation in self.reverse_initial: # some functions can override initial state output_name = self.reverse_initial[return_annotation] else: output_name = 'return_value' for parameter in signature.parameters.values(): if parameter.annotation is ReturnValue: kwargs[parameter.name] = 'return_value' continue # Check if the parameter class exists in 'initial'. if parameter.annotation in self.reverse_initial: initial_kwarg = self.reverse_initial[parameter.annotation] kwargs[parameter.name] = initial_kwarg continue # The 'Parameter' annotation can be used to get the parameter # itself. Used for example in 'Header' components that need the # parameter name in order to lookup a particular value. if parameter.annotation is inspect.Parameter: consts[parameter.name] = parent_parameter continue # Otherwise, find a component to resolve the parameter. for component in self.components: if component.can_handle_parameter(parameter): if component in self.singletons: consts[parameter.name] = self.singletons[component] else: identity = component.identity(parameter) kwargs[parameter.name] = identity if identity not in seen_state: seen_state.add(identity) resolved_steps = self.resolve_function( component.resolve, seen_state, output_name=identity, parent_parameter=parameter ) steps += resolved_steps if getattr(component, 'singleton', False): steps.append(self.resolve_singleton(component, identity)) break else: msg = 'No component able to handle parameter "%s" on function "%s".' raise exceptions.ConfigurationError(msg % (parameter.name, func.__qualname__)) is_async = asyncio.iscoroutinefunction(func) if is_async and not self.allow_async: msg = 'Function "%s" may not be async.' raise exceptions.ConfigurationError(msg % (func.__qualname__, )) step = (func, is_async, kwargs, consts, output_name, set_return) steps.append(step) return steps