Ejemplo n.º 1
0
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
Ejemplo n.º 2
0
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)
Ejemplo n.º 3
0
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
Ejemplo n.º 4
0
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)