def _patch_get( self, client_response: aiohttp.ClientResponse) -> aiohttp.ClientResponse: """ Wrap aiohttp.ClientResponse text and json coros in run_until_complete. Monkeypatch text and json with wrappers.""" # May iter through methods and wrap all coro's in the future # however that may not work if a non-coro returns a async context manager for example text = self._wrap_coro_in_callable(client_response.text) json = self._wrap_coro_in_callable(client_response.json) # resign signatures text = forge.copy(aiohttp.ClientResponse.text, exclude="self")(text) json = forge.copy(aiohttp.ClientResponse.json, exclude="self")(json) client_response.text = text client_response.json = json return client_response
def copy_signatures( target_function: Callable, template_functions: List[TemplateFunction], exclude_args: Iterable[str] = None, ) -> Callable: """A decorator that copies function signatures from one or more template functions to a target function. Args: target_function: Function to modify template_functions: Functions containing params to apply to ``target_function`` """ # Start with 'self' parameter if this is an instance method fparams = {} if 'self' in signature(target_function).parameters or ismethod( target_function): fparams['self'] = forge.self # Add and combine parameters from all template functions, excluding duplicates, self, and *args for func in template_functions: new_fparams = { k: v for k, v in forge.copy(func).signature.parameters.items() if k != 'self' and v.kind != Parameter.VAR_POSITIONAL } fparams.update(new_fparams) # Manually remove any excluded parameters for key in ensure_list(exclude_args): fparams.pop(key, None) fparams = deduplicate_var_kwargs(fparams) revision = forge.sign(*fparams.values()) return revision(target_function)
def page_number(func): """ Decorator for adding pagination behavior to a view. That decorator produces a view based on page numbering and it adds three query parameters to control the pagination: page, page_size and count. Page has a default value of first page, page_size default value is defined in :class:`PageNumberResponse` and count defines if the response will define the total number of elements. The output schema is also modified by :class:`PageNumberSchema`, creating a new schema based on it but using the old output schema as the content of its data field. :param func: View to be decorated. :return: Decorated view. """ assert forge is not None, "`python-forge` must be installed to use Paginator." resource_schema = get_output_schema(func) data_schema = marshmallow.fields.Nested(resource_schema, many=True) if resource_schema else marshmallow.fields.Raw() schema = type( "PageNumberPaginated" + resource_schema.__class__.__name__, # Add a prefix to avoid collision (PageNumberSchema,), {"data": data_schema}, # Replace generic with resource schema )() forge_revision_list = ( forge.copy(func), forge.insert(forge.arg("page", default=None, type=int), index=-1), forge.insert(forge.arg("page_size", default=None, type=int), index=-1), forge.insert(forge.arg("count", default=True, type=bool), index=-1), forge.delete("kwargs"), forge.returns(schema), ) try: if asyncio.iscoroutinefunction(func): @forge.compose(*forge_revision_list) @functools.wraps(func) async def decorator(*args, page: int = None, page_size: int = None, count: bool = True, **kwargs): return PageNumberResponse( schema=schema, page=page, page_size=page_size, count=count, content=await func(*args, **kwargs) ) else: @forge.compose(*forge_revision_list) @functools.wraps(func) def decorator(*args, page: int = None, page_size: int = None, count: bool = True, **kwargs): return PageNumberResponse( schema=schema, page=page, page_size=page_size, count=count, content=func(*args, **kwargs) ) except ValueError as e: raise TypeError("Paginated views must define **kwargs param") from e return decorator
def get_combined_revision(*functions: Callable): """Combine the parameters of all revisions into a single revision""" import forge params = {} for func in functions: params.update(forge.copy(func).signature.parameters) params = deduplicate_kwargs(params) return forge.sign(*params.values())
def wrapper(target_function: Callable): try: import forge except ImportError: return target_function revision = forge.copy(template_function, include=include, exclude=exclude) return revision(target_function)
def _get_combined_revision(template_functions: List[TemplateFunction]): """ Create a :py:class:`forge.Revision` from the combined parameters of multiple functions """ import forge # Use forge.copy to create a revision for each template function revisions = [forge.copy(func) for func in template_functions] # Combine the parameters of all revisions into a single revision fparams = [list(rev.signature.parameters.values()) for rev in revisions] return forge.sign(*list(chain.from_iterable(fparams)))
def forge_client_session(init): """ Forge CachedSession.__init__ method signature """ forged_signature = forge.compose( forge.copy(CachedSession.__init__), forge.insert( [ forge.kwarg("retry", default=True, type=bool), forge.kwarg("n_retries", default=3, type=int), ], before=lambda x: x.kind == Parameter.KEYWORD_ONLY, ), )(init) return forged_signature
def decorator(corofunc): if not inspect.iscoroutinefunction(corofunc): raise TypeError("Expected coroutine function") @forge.compose( forge.copy(corofunc), forge.modify(channel_arg_name, name=port_fixture_name, interface_name="port_fixture_value"), ) async def new_corofunc(*, port_fixture_value, **kwargs): import purerpc async with purerpc.insecure_channel("127.0.0.1", port_fixture_value) as channel: await corofunc(**kwargs, channel=channel) return new_corofunc
def decorator(func): if hasattr(func, "__parallelized__") and func.__parallelized__: raise TypeError( "Cannot pass gRPC channel to already parallelized test, grpc_client_parallelize should " "be the last decorator in chain") @forge.compose( forge.copy(func), forge.modify(channel_arg_name, name=port_fixture_name, interface_name="port_fixture_value"), ) def new_func(*, port_fixture_value, **kwargs): import grpc with grpc.insecure_channel( '127.0.0.1:{}'.format(port_fixture_value)) as channel: func(**kwargs, channel=channel) return new_func
import forge import aiohttp from typing import Dict, List, Union # RestClient signature decorators GET_SIGNATURE = forge.compose( forge.copy(aiohttp.ClientSession.get), forge.modify("url", default=None), forge.returns(aiohttp.ClientResponse), forge.insert( [ forge.kwo("parameters", default={}, type=Dict[str, Union[str, List[str]]]), forge.kwo("headers", default={}, type=Dict[str, str]), ], before=lambda arg: arg.kind == forge.FParameter.VAR_KEYWORD, ), ) MGET_SIGNATURE = forge.compose( forge.copy(aiohttp.ClientSession.get, exclude="url"), forge.returns(List[aiohttp.ClientResponse]), forge.insert( forge.pok( "urls", type=forge.fsignature(aiohttp.ClientSession.get)["url"].type, default=None, ), index=1,
def get_combined_revision(functions: Iterable[Callable]) -> forge.Revision: """Combine the parameters of all revisions into a single revision""" params = {} for func in functions: params.update(forge.copy(func).signature.parameters) return forge.sign(*params.values())