class CliApp(App): INJECTOR_CLS = dependency.DependencyInjector BUILTIN_COMMANDS = [ Command('new', commands.new), ] BUILTIN_COMPONENTS = [ Component(CommandLineClient, init=commandline.ArgParseCommandLineClient), Component(Console, init=console.PrintConsole) ] def __init__(self, routes: RouteConfig = None, commands: CommandConfig = None, components: typing.List[Component] = None, settings: typing.Dict[str, typing.Any] = None) -> None: if routes is None: routes = [] if commands is None: commands = [] if components is None: components = [] if settings is None: settings = {} commands = [*self.BUILTIN_COMMANDS, *commands] components = [*self.BUILTIN_COMPONENTS, *components] initial_state = { RouteConfig: routes, CommandConfig: commands, Settings: settings, App: self, } self.components, self.preloaded_state = self.preload_components( component_config=components, initial_state=initial_state) # Setup everything that we need in order to run `self.main()`. self.commandline = self.preloaded_state[CommandLineClient] self.console = self.preloaded_state[Console] self.cli_injector = self.create_cli_injector() def preload_components( self, component_config: typing.List[Component], initial_state: typing.Dict[type, typing.Any] ) -> typing.Tuple[typing.Dict[type, typing.Callable], typing.Dict[ type, typing.Any]]: """ Create any components that can be preloaded at the point of instantiating the app. This ensures that the dependency injection will not need to re-create these on every incoming request or command-line invocation. Args: components: The components that have been configured for this app. initial_state: The initial state that has been configured for this app. Return: A tuple of the components that could not be preloaded, and the initial state, which may include preloaded components. """ components = {cls: init for cls, init, preload in component_config} should_preload = { cls: preload for cls, init, preload in component_config } injector = self.INJECTOR_CLS(components, initial_state) for cls, func in list(components.items()): if not should_preload[cls]: continue try: component = injector.run(func) except exceptions.CouldNotResolveDependency: continue del components[cls] initial_state[cls] = component injector = self.INJECTOR_CLS(components, initial_state) return (components, initial_state) def create_cli_injector(self) -> Injector: """ Create the dependency injector for running handlers in response to command-line invocation. Args: components: Any components that are created per-request. initial_state: Any preloaded components and other initial state. """ return self.INJECTOR_CLS(components=self.components, initial_state=self.preloaded_state, required_state={ KeywordArgs: 'kwargs', }, resolvers=[dependency.CliResolver()]) def main(self, args: typing.Sequence[str] = None, standalone_mode: bool = True): if args is None: # pragma: nocover args = sys.argv[1:] state = {} try: handler, kwargs = self.commandline.parse(args) state['kwargs'] = kwargs ret = self.cli_injector.run(handler, state=state) except exceptions.CommandLineExit as exc: ret = exc.message except exceptions.CommandLineError as exc: if standalone_mode: # pragma: nocover sys.stderr.write('Error: %s\n' % exc) sys.exit(exc.exit_code) raise except (EOFError, KeyboardInterrupt): # pragma: nocover sys.stderr.write('Aborted!\n') sys.exit(1) if standalone_mode and ret is not None: # pragma: nocover self.console.echo(ret) if not standalone_mode: return ret
class WSGIApp(CliApp): INJECTOR_CLS = dependency.DependencyInjector BUILTIN_COMMANDS = [ Command('new', commands.new), Command('run', commands.run_wsgi), Command('schema', commands.schema), Command('test', commands.test) ] BUILTIN_COMPONENTS = [ Component(Schema, init=schema.CoreAPISchema), Component(Templates, init=templates.Jinja2Templates), Component(StaticFiles, init=statics.WhiteNoiseStaticFiles), Component(Router, init=router.WerkzeugRouter), Component(CommandLineClient, init=commandline.ArgParseCommandLineClient), Component(Console, init=console.PrintConsole), Component(SessionStore, init=sessions.LocalMemorySessionStore), ] HTTP_COMPONENTS = [ Component(http.Method, init=wsgi.get_method), Component(http.URL, init=wsgi.get_url), Component(http.Scheme, init=wsgi.get_scheme), Component(http.Host, init=wsgi.get_host), Component(http.Port, init=wsgi.get_port), Component(http.Path, init=wsgi.get_path), Component(http.Headers, init=wsgi.get_headers), Component(http.Header, init=wsgi.get_header), Component(http.QueryString, init=wsgi.get_querystring), Component(http.QueryParams, init=wsgi.get_queryparams), Component(http.QueryParam, init=wsgi.get_queryparam), Component(http.Body, init=wsgi.get_body), Component(http.Request, init=http.Request), Component(http.RequestStream, init=wsgi.get_stream), Component(http.RequestData, init=wsgi.get_request_data), Component(FileWrapper, init=wsgi.get_file_wrapper), Component(http.Session, init=sessions.get_session), ] def __init__(self, **kwargs): super().__init__(**kwargs) # Setup everything that we need in order to run `self.__call__()` self.router = self.preloaded_state[Router] self.http_injector = self.create_http_injector() def create_http_injector(self) -> Injector: """ Create the dependency injector for running handlers in response to incoming HTTP requests. Args: components: Any components that are created per-request. initial_state: Any preloaded components and other initial state. """ http_components = { component.cls: component.init for component in self.HTTP_COMPONENTS } return self.INJECTOR_CLS(components={ **http_components, **self.components }, initial_state=self.preloaded_state, required_state={ WSGIEnviron: 'wsgi_environ', KeywordArgs: 'kwargs', Exception: 'exc', http.ResponseHeaders: 'response_headers' }, resolvers=[dependency.HTTPResolver()]) def __call__(self, environ: typing.Dict[str, typing.Any], start_response: typing.Callable): headers = http.ResponseHeaders() state = { 'wsgi_environ': environ, 'kwargs': None, 'exc': None, 'response_headers': headers } method = environ['REQUEST_METHOD'].upper() path = environ['PATH_INFO'] try: handler, kwargs = self.router.lookup(path, method) state['kwargs'] = kwargs funcs = [handler, self.finalize_response] response = self.http_injector.run_all(funcs, state=state) except Exception as exc: state['exc'] = exc # type: ignore funcs = [self.exception_handler, self.finalize_response] response = self.http_injector.run_all(funcs, state=state) # Get the WSGI response information, given the Response instance. try: status_text = STATUS_TEXT[response.status] except KeyError: status_text = str(response.status) headers.update(response.headers) headers['content-type'] = response.content_type if isinstance(response.content, (bytes, str)): content = [response.content] else: content = response.content # Return the WSGI response. start_response(status_text, list(headers)) return content def exception_handler(self, exc: Exception) -> http.Response: if isinstance(exc, exceptions.Found): return http.Response('', exc.status_code, {'Location': exc.location}) if isinstance(exc, exceptions.HTTPException): if isinstance(exc.detail, str): content = {'message': exc.detail} else: content = exc.detail return http.Response(content, exc.status_code, {}) raise def finalize_response(self, ret: ReturnValue) -> http.Response: if isinstance(ret, http.Response): data, status, headers, content_type = ret if content_type is not None: return ret else: data, status, headers, content_type = ret, 200, {}, None if data is None: content = b'' content_type = 'text/plain' elif isinstance(data, str): content = data.encode('utf-8') content_type = 'text/html; charset=utf-8' elif isinstance(data, bytes): content = data content_type = 'text/html; charset=utf-8' else: content = json.dumps(data).encode('utf-8') content_type = 'application/json' if not content and status == 200: status = 204 content_type = 'text/plain' return http.Response(content, status, headers, content_type)
class ASyncIOApp(CliApp): INJECTOR_CLS = dependency.AsyncDependencyInjector BUILTIN_COMMANDS = [ Command('new', commands.new), Command('run', commands.run_asyncio), Command('schema', commands.schema), Command('test', commands.test) ] BUILTIN_COMPONENTS = [ Component(Schema, init=schema.CoreAPISchema), Component(Templates, init=templates.Jinja2Templates), Component(StaticFiles, init=statics.WhiteNoiseStaticFiles), Component(Router, init=router.WerkzeugRouter), Component(CommandLineClient, init=commandline.ArgParseCommandLineClient), Component(Console, init=console.PrintConsole), Component(SessionStore, init=sessions.LocalMemorySessionStore), ] HTTP_COMPONENTS = [ Component(http.Method, init=umi.get_method), Component(http.URL, init=umi.get_url), Component(http.Scheme, init=umi.get_scheme), Component(http.Host, init=umi.get_host), Component(http.Port, init=umi.get_port), Component(http.Path, init=umi.get_path), Component(http.Headers, init=umi.get_headers), Component(http.Header, init=umi.get_header), Component(http.QueryString, init=umi.get_querystring), Component(http.QueryParams, init=umi.get_queryparams), Component(http.QueryParam, init=umi.get_queryparam), Component(http.Body, init=umi.get_body), Component(http.Request, init=http.Request), Component(http.RequestStream, init=umi.get_stream), Component(http.RequestData, init=umi.get_request_data), Component(FileWrapper, init=umi.get_file_wrapper), Component(http.Session, init=sessions.get_session), Component(Auth, init=umi.get_auth) ] def __init__(self, **kwargs): super().__init__(**kwargs) # Setup everything that we need in order to run `self.__call__()` self.router = self.preloaded_state[Router] self.http_injector = self.create_http_injector() settings = kwargs.get('settings', None) if settings and 'BEFORE_REQUEST' in settings: self.before_request = settings['BEFORE_REQUEST'] else: self.before_request = [hooks.check_permissions_async] if settings and 'AFTER_REQUEST' in settings: self.after_request = settings['AFTER_REQUEST'] else: self.after_request = [hooks.render_response] def create_http_injector(self) -> Injector: """ Create the dependency injector for running handlers in response to incoming HTTP requests. """ http_components = { component.cls: component.init for component in self.HTTP_COMPONENTS } return self.INJECTOR_CLS(components={ **http_components, **self.components }, initial_state=self.preloaded_state, required_state={ UMIMessage: 'message', UMIChannels: 'channels', KeywordArgs: 'kwargs', Handler: 'handler', Exception: 'exc', http.ResponseHeaders: 'response_headers', http.ResponseData: 'response_data' }, resolvers=[dependency.HTTPResolver()]) async def __call__(self, message: typing.Dict[str, typing.Any], channels: typing.Dict[str, typing.Any]): headers = http.ResponseHeaders() state = { 'message': message, 'channels': channels, 'handler': None, 'kwargs': None, 'exc': None, 'response_headers': headers } method = message['method'].upper() path = message['path'] try: handler, kwargs = self.router.lookup(path, method) state['handler'], state['kwargs'] = handler, kwargs funcs = self.before_request + [handler] + self.after_request response = await self.http_injector.run_all_async(funcs, state=state) except Exception as exc: state['exc'] = exc # type: ignore funcs = [self.exception_handler] + self.after_request response = await self.http_injector.run_all_async(funcs, state=state) headers.update(response.headers) if response.content_type is not None: headers['content-type'] = response.content_type response_message = { 'status': response.status, 'headers': [[key.encode(), value.encode()] for key, value in headers], 'content': response.content } await channels['reply'].send(response_message) def exception_handler(self, exc: Exception) -> http.Response: if isinstance(exc, exceptions.Found): return http.Response('', exc.status_code, {'Location': exc.location}) if isinstance(exc, exceptions.HTTPException): if isinstance(exc.detail, str): content = {'message': exc.detail} else: content = exc.detail return http.Response(content, exc.status_code, {}) raise
class ASyncIOApp(CliApp): INJECTOR_CLS = dependency.AsyncDependencyInjector BUILTIN_COMMANDS = [ Command('new', commands.new), Command('run', commands.run_asyncio), Command('schema', commands.schema), Command('test', commands.test) ] BUILTIN_COMPONENTS = [ Component(Schema, init=schema.CoreAPISchema), Component(Templates, init=templates.Jinja2Templates), Component(StaticFiles, init=statics.WhiteNoiseStaticFiles), Component(Router, init=router.WerkzeugRouter), Component(CommandLineClient, init=commandline.ArgParseCommandLineClient), Component(Console, init=console.PrintConsole), Component(SessionStore, init=sessions.LocalMemorySessionStore), ] HTTP_COMPONENTS = [ Component(http.Method, init=umi.get_method), Component(http.URL, init=umi.get_url), Component(http.Scheme, init=umi.get_scheme), Component(http.Host, init=umi.get_host), Component(http.Port, init=umi.get_port), Component(http.Path, init=umi.get_path), Component(http.Headers, init=umi.get_headers), Component(http.Header, init=umi.get_header), Component(http.QueryString, init=umi.get_querystring), Component(http.QueryParams, init=umi.get_queryparams), Component(http.QueryParam, init=umi.get_queryparam), Component(http.Body, init=umi.get_body), Component(http.Request, init=http.Request), Component(http.RequestData, init=umi.get_request_data), Component(FileWrapper, init=umi.get_file_wrapper), Component(http.Session, init=sessions.get_session), ] def __init__(self, **kwargs): super().__init__(**kwargs) # Setup everything that we need in order to run `self.__call__()` self.router = self.preloaded_state[Router] self.http_injector = self.create_http_injector() def create_http_injector(self) -> Injector: """ Create the dependency injector for running handlers in response to incoming HTTP requests. """ http_components = { component.cls: component.init for component in self.HTTP_COMPONENTS } return self.INJECTOR_CLS(components={ **http_components, **self.components }, initial_state=self.preloaded_state, required_state={ UMIMessage: 'message', UMIChannels: 'channels', KeywordArgs: 'kwargs', Exception: 'exc', http.ResponseHeaders: 'response_headers' }, resolvers=[dependency.HTTPResolver()]) async def __call__(self, message: typing.Dict[str, typing.Any], channels: typing.Dict[str, typing.Any]): headers = http.ResponseHeaders() state = { 'message': message, 'channels': channels, 'kwargs': None, 'exc': None, 'response_headers': headers } method = message['method'].upper() path = message['path'] try: handler, kwargs = self.router.lookup(path, method) state['kwargs'] = kwargs response = await self.http_injector.run_async(handler, state=state) except Exception as exc: state['exc'] = exc # type: ignore response = await self.http_injector.run_async( self.exception_handler, state=state) if getattr(response, 'content_type', None) is None: response = self.finalize_response(response) headers.update(response.headers) headers['content-type'] = response.content_type response_message = { 'status': response.status, 'headers': [[key.encode(), value.encode()] for key, value in headers], 'content': response.content } await channels['reply'].send(response_message) def exception_handler(self, exc: Exception) -> http.Response: if isinstance(exc, exceptions.Found): return http.Response('', exc.status_code, {'Location': exc.location}) if isinstance(exc, exceptions.HTTPException): if isinstance(exc.detail, str): content = {'message': exc.detail} else: content = exc.detail return http.Response(content, exc.status_code, {}) raise def finalize_response(self, response: http.Response) -> http.Response: if isinstance(response, http.Response): data, status, headers, content_type = response else: data, status, headers, content_type = response, 200, {}, None if data is None: content = b'' content_type = 'text/plain' elif isinstance(data, str): content = data.encode('utf-8') content_type = 'text/html; charset=utf-8' elif isinstance(data, bytes): content = data content_type = 'text/html; charset=utf-8' else: content = json.dumps(data).encode('utf-8') content_type = 'application/json' if not content and status == 200: status = 204 content_type = 'text/plain' return http.Response(content, status, headers, content_type)