"""
The latest version of this package is available at:
<http://github.com/jantman/RPyMostat>
##################################################################################
Copyright 2016 Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
This file is part of RPyMostat, also known as RPyMostat.
RPyMostat is free software: you can redistribute it and/or modify
it under the terms of the GNU Affero General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
RPyMostat is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Affero General Public License for more details.
You should have received a copy of the GNU Affero General Public License
along with RPyMostat. If not, see <http://www.gnu.org/licenses/>.
The Copyright and Authors attributions contained herein may not be removed or
otherwise altered, except to add the Author attribution of a contributor to
this work. (Additional Terms pursuant to Section 7b of the AGPL v3)
##################################################################################
While not legally required, I sincerely request that anyone who finds
bugs please submit them at <https://github.com/jantman/RPyMostat> or
to me via email, and that you send any contributions or improvements
either as a pull request on GitHub, or to me via email.
##################################################################################
AUTHORS:
Jason Antman <jason@jasonantman.com> <http://www.jasonantman.com>
##################################################################################
"""
import abc
import logging
from klein.app import Klein
from copy import deepcopy
from rpymostat.exceptions import RequestParsingException
import json
logger = logging.getLogger(__name__)
[docs]class SiteHierarchy(object):
"""
Helper class to implement hierarchical sites in Klein. All engine
classes that provide routes must implement this.
"""
__metaclass__ = abc.ABCMeta
prefix_part = 'base'
def __init__(self, apiserver, app, dbconn, parent_prefix):
"""
Initialize a site hierarchy component. Takes the parent's prefix and
sets up all routes under its own prefix. After classes implementing
this interface are instantiated, their ``setup_routes`` method must
be called.
:param apiserver: The :py:class:`~.APIServer` instance at the root of
the project/application
:type apiserver: rpymostat.engine.apiserver.APIServer
:param app: the Klein app to add routes in
:type app: instance of :py:class:`klein.app.Klein`
:param dbconn: MongoDB ConnectionPool
:type dbconn: txmongo.connection.ConnectionPool
:param parent_prefix: The parent hierarchy's prefix
:type parent_prefix: list of str
"""
assert isinstance(app, Klein)
assert isinstance(parent_prefix, type([]))
assert self.prefix_part != 'base'
self.prefix = self.make_prefix(parent_prefix, self.prefix_part)
self.prefix_str = self.prefix_list_to_str(self.prefix)
logger.debug('initializing prefix: %s', self.prefix_str)
self.app = app
self.apiserver = apiserver
self.dbconn = dbconn
[docs] def make_prefix(self, parent_list, prefix_str):
"""
Given a list of the parent's prefix and our prefix string, construct
a new list with our prefix.
:param parent_list: parent's prefix
:type parent_list: list
:param prefix_str: our prefix string
:type prefix_str: str
:return: our prefix list
:rtype: list
"""
l = deepcopy(parent_list)
l.append(prefix_str)
return l
[docs] def prefix_list_to_str(self, prefix_list):
"""
Convert a prefix list to a string path prefix.
:param prefix_list: the list to convert
:type prefix_list: list
:return: prefix string
:rtype: str
"""
return '/' + '/'.join(prefix_list)
[docs] def add_route(self, func, path=None, methods=['GET']):
"""
Add a route to app, mapping ``func`` (a callable method in this class)
to ``path`` (under ``self.prefix``). If ``path`` is none, it will be
mapped directly to ``self.prefix``.
:param func: callable in this class to map to path
:param path: path to map method to
:type path: str
:param methods: methods allowed for this route
:type methods: list
"""
p = self.prefix_str
if path is not None:
p += '/' + path
logger.debug('Adding route for %s to %s', p, func)
self.app.route(p, methods=methods)(func)
@abc.abstractmethod
[docs] def setup_routes(self):
"""
Setup all routes for this class. Must be implemented by subclasses.
"""
raise NotImplementedError("setup_routes must be implemented in this "
"class")
[docs] def _parse_json_request(self, request):
"""
Parse a JSON request; return the deserialized request or raise
an exception.
:param request: the request
:type request: instance of :class:`twisted.web.server.Request`
:return: deserialized request JSON
:rtype: str
"""
try:
raw = request.content.getvalue()
except:
raise RequestParsingException('Could not read request content.')
# handle python3
if not isinstance(raw, type('')):
raw = raw.decode('utf-8')
if len(raw.strip()) < 1:
raise RequestParsingException('Empty Request.')
try:
data = json.loads(raw)
except:
raise RequestParsingException('Invalid JSON.')
return data