Esempio n. 1
0
def test_events_on_decorator():
    e = Events('foo')
    m = mock.MagicMock()

    @e.on("foo")
    def doit():
        m()
    yield from e.emit("foo")
    assert m.called
Esempio n. 2
0
def test_events_on_decorator():
    e = Events('foo')
    m = mock.MagicMock()

    @e.on("foo")
    def doit():
        m()

    yield from e.emit("foo")
    assert m.called
Esempio n. 3
0
def test_events_on_async():
    e = Events('foo')
    m = mock.MagicMock()

    @asyncio.coroutine
    def foo():
        m()

    e.on('foo', foo())
    yield from e.emit('foo')
    assert m.called
Esempio n. 4
0
def test_events_on_async():
    e = Events('foo')
    m = mock.MagicMock()

    @asyncio.coroutine
    def foo():
        m()

    e.on('foo', foo())
    yield from e.emit('foo')
    assert m.called
Esempio n. 5
0
def test_events_on_typecheck():
    e = Events('foo')
    with pytest.raises(ValueError):
        e.on('anything', 10)
Esempio n. 6
0
def test_events_constructor_nonempty():
    e = Events('foo')
    with pytest.raises(KeyError):
        e.on('anything', lambda: print())
Esempio n. 7
0
def test_events_constructor_empty():
    e = Events()
    e.on('anything', lambda: print())
Esempio n. 8
0
def test_events_on():
    e = Events('foo')
    m = mock.MagicMock()
    e.on('foo', m)
    yield from e.emit('foo')
    assert m.called
Esempio n. 9
0
    def __init__(self, protocol, EOL="\r\n"):
        self.protocol = protocol
        self.EOL = EOL

        self.headers = Headers()
        self.events = Events()
Esempio n. 10
0
class HTTPResponse:
    """

    Response class which handles HTTP formatting and sending a response to the
    client. If the response has sent a message (the `has_ended` property
    evaluates to True) the middleware chain will stop.

    There are many convenience functions such as `send_json` and `send_html`
    which will handle formatting data, setting headers and sending objects and
    strings for you.

    A typical use is the modification of the response object by the standard
    Renderer middleware, which adds a `render` method to the response object.
    Any middleware after this one (i.e. your routes) can then call
    res.render("template_name", data) to automatically render a web view and
    send it to.

    Parameters
    ----------
    protocol : GrowlerHTTPProtocol
        Protocol object creating the response
    EOL : str
        The string with which to end lines
    """
    SERVER_INFO = 'Growler/{growler_version} Python/{py_version}'.format(
        py_version=".".join(map(str, sys.version_info[:2])),
        growler_version=growler.__version__,
    )

    protocol = None
    has_sent_headers = False
    has_ended = False
    status_code = 200
    headers = None
    message = ''
    EOL = ''
    phrase = None

    def __init__(self, protocol, EOL="\r\n"):
        self.protocol = protocol
        self.EOL = EOL

        self.headers = Headers()
        self.events = Events()

    def _set_default_headers(self):
        """
        Create some default headers that should be sent along with every HTTP
        response
        """
        self.headers.setdefault('Date', self.get_current_time)
        self.headers.setdefault('Server', self.SERVER_INFO)
        self.headers.setdefault('Content-Length', "%d" % len(self.message))
        if self.app.enabled('x-powered-by'):
            self.headers.setdefault('X-Powered-By', 'Growler')

    def send_headers(self):
        """
        Sends the headers to the client
        """
        self.events.sync_emit('headers')
        self._set_default_headers()
        header_str = self.status_line + self.EOL + str(self.headers)
        self.protocol.transport.write(header_str.encode())
        self.events.sync_emit('after_headers')

    def write(self, msg=None):
        msg = self.message if msg is None else msg
        msg = msg.encode() if isinstance(msg, str) else msg
        self.protocol.transport.write(msg)

    def write_eof(self):
        self.stream.write_eof()
        self.has_ended = True
        self.events.sync_emit('after_send')

    @property
    def status_line(self):
        """
        Returns the first line of response, including http version, status
        and a phrase (OK).
        """
        if not self.phrase:
            self.phrase = Status.Phrase(self.status_code)
        return "{} {} {}".format("HTTP/1.1", self.status_code, self.phrase)

    def end(self):
        """
        Ends the response. Useful for quickly ending connection with no data
        sent
        """
        self.send_headers()
        self.write()
        self.write_eof()
        self.has_ended = True

    def redirect(self, url, status=None):
        """
        Redirect to the specified url, optional status code defaults to 302.
        """
        self.status_code = 302 if status is None else status
        self.headers = Headers([('location', url)])
        self.message = ''
        self.end()

    def set(self, header, value=None):
        """Set header to the value"""
        if value is None:
            for k, v in header.items():
                self.headers[k] = v
        else:
            self.headers[header] = value

    def header(self, header, value=None):
        """Alias for 'set()'"""
        self.set(header, value)

    def set_type(self, res_type):
        self.set('Content-Type', res_type)

    def get(self, field):
        """Get a header"""
        return self.headers[field]

    # def cookie(self, name, value, options={}):
    #     """Set cookie name to value"""
    #     self.cookies[name] = value
    #
    # def clear_cookie(self, name, options={}):
    #     """Removes a cookie"""
    #     options.setdefault("path", "/")
    #     del self.cookies[name]

    def location(self, location):
        """Set the location header"""
        self.headers['location'] = location

    def links(self, links):
        """Sets the Link """
        s = ['<{}>; rel="{}"'.format(link, rel)
             for link, rel in links.items()]
        self.headers['Link'] = ','.join(s)

    def json(self, body, status=200):
        """Alias of send_json"""
        return self.send_json(body, status)

    def send_json(self, obj, status=200):
        """
        Sends a stringified JSON response to client. Automatically sets the
        content-type header to application/json.

        Parameters
        ----------
        obj : mixed
            Any object which will be serialized by the json.dumps module
            function
        status : int, optional
            The HTTP status code, defaults to 200 (OK)
        """
        self.headers['Content-Type'] = 'application/json'
        self.status_code = status
        message = json.dumps(obj)
        self.send_text(message)

    def send_html(self, html, status=200):
        """
        Sends html response to client. Automatically sets the content-type
        header to text/html.

        Parameters
        ----------
        html : str
            The raw html string to be sent back to the client
        status : int, optional
            The HTTP status code, defaults to 200 (OK)
        """
        self.headers.setdefault('Content-Type', 'text/html')
        self.message = html
        self.status_code = status
        self.send_headers()
        self.write()
        self.write_eof()

    def send_text(self, txt, status=200):
        """
        Sends plaintext response to client. Automatically sets the content-type
        header to text/plain. If txt is not a string, it will be formatted as
        one.

        Parameters
        ----------
        txt : str
            The plaintext string to be sent back to the client
        status : int, optional
            The HTTP status code, defaults to 200 (OK)
        """
        self.headers.setdefault('content-type', 'text/plain')
        if not isinstance(txt, bytes):
            txt = str(txt).encode()
        self.message = txt
        self.status_code = status
        self.end()

    def send_file(self, filename, status=200):
        """
        Reads in the file 'filename' and sends bytes to client

        Parameters
        ----------
        filename : str
            Filename of the file to read
        status : int, optional
            The HTTP status code, defaults to 200 (OK)
        """
        if isinstance(filename, Path) and sys.version_info >= (3, 5):
            self.message = filename.read_bytes()
        else:
            with io.FileIO(str(filename)) as f:
                self.message = f.read()
        self.status_code = status
        self.send_headers()
        self.write()
        self.write_eof()

    def send(self, *args, **kwargs):
        raise NotImplementedError
        # return self.write(*args, **kwargs)

    @property
    def info(self):
        return self.SERVER_INFO

    @property
    def stream(self):
        return self.protocol.transport

    @property
    def app(self):
        return self.protocol.http_application

    @staticmethod
    def get_current_time():
        # return time.strftime("%a, %d %b %Y %H:%M:%S GMT", time.gmtime())
        return format_RFC_1123(time.mktime(datetime.now().timetuple()))
Esempio n. 11
0
    def __init__(self,
                 name=__name__,
                 loop=None,
                 debug=True,
                 request_class=HTTPRequest,
                 response_class=HTTPResponse,
                 protocol_factory=GrowlerHTTPProtocol.get_factory,
                 middleware_chain_factory=MiddlewareChain,
                 **kw):
        """
        Creates an application object.

        Args:
            name (str): Does nothing right now except identify object
            loop (asyncio.AbstractEventLoop): The event loop to run on
            debug (bool): (de)Activates the loop's debug setting

            request_class (type or callable): The factory of request objects, the default of
                which is growler.HTTPRequest. This should only be set in special cases, like
                debugging or if the dev doesn't want to modify default request objects via
                middleware.

            response_class (type or callable): The factory of response objects, the default of
                which is growler.HTTPResponse. This should only be set in special cases, like
                debugging or if the dev doesn't want to modify default response objects via
                middleware.

            protocol_factory (callable): Factory function this application uses to construct
                the asyncio protocol object which responds to client connections. The default
                is the GrowlerHTTPProtocol.get_factory method, which simply returns a lambda
                returning new GrowlerHTTPProtocol objects.

            middleware_chain_factory (type or callable): Called upon with no arguments to
                create the middleware chain used by the application. This is accessible via
                the attribute 'middleware'.

        Keyword Args:
            Any other custom variables for the application. This dict is stored as the
            attribute 'config' in the application. These variables are accessible by the
            application's dict-access, as in:

            .. code:: python

                app = app(..., val='VALUE')
                app['val'] #=> VALUE
        """
        self.name = name

        self.config = kw

        self.loop = asyncio.get_event_loop() if (loop is None) else loop
        self.loop.set_debug(debug)

        self.middleware = middleware_chain_factory()

        self.enable('x-powered-by')
        self['env'] = os.getenv('GROWLER_ENV', 'development')

        self.events = Events()
        self.strict_router_check = False

        self._request_class = request_class
        self._response_class = response_class
        self._protocol_factory = protocol_factory

        self.handle_404 = self.default_404_handler
Esempio n. 12
0
def test_events_on_typecheck():
    e = Events('foo')
    with pytest.raises(ValueError):
        e.on('anything', 10)
Esempio n. 13
0
def test_events_constructor_nonempty():
    e = Events('foo')
    with pytest.raises(KeyError):
        e.on('anything', lambda: print())
Esempio n. 14
0
def test_events_constructor_empty():
    e = Events()
    e.on('anything', lambda: print())
Esempio n. 15
0
def test_events_on():
    e = Events('foo')
    m = mock.MagicMock()
    e.on('foo', m)
    yield from e.emit('foo')
    assert m.called