# -*- coding: utf-8 -*-
from __future__ import unicode_literals
import logging
from flask import Blueprint, request
from flask_cache import Cache
from flask_compress import Compress
from flask.ext.script import Manager
from capitains_nautilus.cache import BaseCache
from capitains_nautilus.mycapytain import Text, NautilusRetriever
[docs]class WerkzeugCacheWrapper(BaseCache):
""" Werkzeug Cache Wrapper for Nautilus Base Cache object
:param instance: Werkzeug Cache instance
"""
def __init__(self, instance=None, *args, **kwargs):
super(WerkzeugCacheWrapper, self).__init__(*args, **kwargs)
if not instance:
instance = BaseCache()
self.__instance__ = instance
[docs] def get(self, key):
return self.__instance__.get(key)
[docs] def set(self, key, value, timeout=None):
return self.__instance__.set(key, value, timeout)
[docs] def add(self, key, value, timeout=None):
return self.__instance__.add(key, value, timeout)
[docs] def clear(self):
return self.__instance__.clear()
[docs] def delete(self, key):
return self.__instance__.delete(key)
[docs]class FlaskNautilus(object):
""" Initiate the class
:param prefix: Prefix on which to install the extension
:param app: Application on which to register
:param name: Name to use for the blueprint
:param resources: List of directory to feed the inventory
:type resources: list(str)
:param logger: Logging handler.
:type logger: logging
:param parser_cache: Cache object
:type parser_cache: BaseCache
:param http_cache: HTTP Cache should be a FlaskCache object
:param auto_parse: Parses on first execution the resources given to build inventory. Not recommended for production
:cvar ROUTES: List of triple length tuples
:cvar Access_Control_Allow_Methods: Dictionary with route name and allowed methods over CORS
:cvar Access_Control_Allow_Origin: Dictionary with route name and allowed host over CORS or "*"
:cvar LoggingHandler: Logging handler to be set for the blueprint
:ivar logger: Logging handler
:type logger: logging
:ivar retriever: CapiTainS Retriever
"""
ROUTES = [
('/', "r_dispatcher", ["GET"])
]
Access_Control_Allow_Methods = {
"r_dispatcher": "OPTIONS, GET"
}
Access_Control_Allow_Origin = "*"
LoggingHandler = logging.StreamHandler
def __init__(self,
prefix="", app=None, name=None,
resources=None, parser_cache=None,
compresser=True,
http_cache=None, pagination=False,
access_Control_Allow_Origin=None, access_Control_Allow_Methods=None,
logger=None,
auto_parse=True
):
self.logger = None
self.retriever = None
self.setLogger(logger)
if not resources:
resources = list()
# Set up endpoints with cache system
if parser_cache:
Text.CACHE_CLASS = parser_cache
self.retriever = NautilusRetriever(resources, pagination=pagination, cache=parser_cache, logger=self.logger, auto_parse=auto_parse)
else:
self.retriever = NautilusRetriever(resources, pagination=pagination, auto_parse=auto_parse)
self.retriever.resolver.TEXT_CLASS = Text
self.app = app
self.name = name
self.prefix = prefix
self.blueprint = None
self.Access_Control_Allow_Methods = access_Control_Allow_Methods
if not self.Access_Control_Allow_Methods:
self.Access_Control_Allow_Methods = FlaskNautilus.Access_Control_Allow_Methods
self.Access_Control_Allow_Origin = access_Control_Allow_Origin
if not self.Access_Control_Allow_Origin:
self.Access_Control_Allow_Origin = FlaskNautilus.Access_Control_Allow_Origin
self.__http_cache = http_cache
self.__compresser = False
if self.name is None:
self.name = __name__
if self.app:
self.init_app(app=app, compresser=compresser)
[docs] def setLogger(self, logger):
""" Set up the Logger for the application
:param logger: logging.Logger object
:return: Logger instance
"""
self.logger = logger
if not logger:
self.logger = logging.getLogger("capitains_nautilus")
else:
self.logger = self.logger.getLogger("capitains_nautilus")
formatter = logging.Formatter("[%(asctime)s] {%(pathname)s:%(lineno)d} %(levelname)s - %(message)s")
stream = FlaskNautilus.LoggingHandler()
stream.setLevel(logging.INFO)
stream.setFormatter(formatter)
self.logger.addHandler(stream)
if self.retriever:
self.retriever.logger = self.logger
self.retriever.resolver.logger = self.logger
return self.logger
[docs] def init_app(self, app, compresser=False):
""" Initiate the extension on the application
:param app: Flask Application
:return: Blueprint for Flask Nautilus registered in app
:rtype: Blueprint
"""
if not self.app:
self.app = app
if not self.__http_cache:
self.__http_cache = Cache(config={'CACHE_TYPE': 'simple'})
self.init_blueprint()
if compresser:
self.__compresser = Compress(app)
return self.blueprint
[docs] def init_blueprint(self):
""" Properly generates the blueprint, registering routes and filters and connecting the app and the blueprint
:return: Blueprint of the extension
:rtype: Blueprint
"""
self.blueprint = Blueprint(
self.name,
self.name,
url_prefix=self.prefix
)
# Register routes
for url, name, methods in FlaskNautilus.ROUTES:
self.blueprint.add_url_rule(
url,
view_func=self.view(name),
endpoint=name[2:],
methods=methods
)
self.app.register_blueprint(self.blueprint)
return self.blueprint
[docs] def view(self, function_name):
""" Builds response according to a function name
:param function_name: Route name / function name
:return: Function
"""
if isinstance(self.Access_Control_Allow_Origin, dict):
d = {
"Access-Control-Allow-Origin": self.Access_Control_Allow_Origin[function_name],
"Access-Control-Allow-Methods": self.Access_Control_Allow_Methods[function_name]
}
else:
d = {
"Access-Control-Allow-Origin": self.Access_Control_Allow_Origin,
"Access-Control-Allow-Methods": self.Access_Control_Allow_Methods[function_name]
}
def r(*x, **y):
val = list(getattr(self, function_name)(*x, **y))
val[2].update(d)
return tuple(val)
return r
[docs] def r_dispatcher(self):
""" Actual main route of CTS APIs. Transfer typical requests through the ?request=REQUESTNAME route
:return: Response
"""
_request = request.args.get("request", None)
if not _request:
return "This request does not exist", 404, dict() # Should maybe return documentation on top of 404 ?
elif _request.lower() == "getcapabilities":
return self._r_GetCapabilities(
urn=request.args.get("urn", None),
inv=request.args.get("inv", None)
)
elif _request.lower() == "getpassage":
return self._r_GetPassage(
urn=request.args.get("urn", None),
inv=request.args.get("inv", None)
)
elif _request.lower() == "getpassageplus":
return self._r_GetPassagePlus(
urn=request.args.get("urn", None),
inv=request.args.get("inv", None)
)
elif _request.lower() == "getlabel":
return self._r_GetLabel(
urn=request.args.get("urn", None),
inv=request.args.get("inv", None)
)
elif _request.lower() == "getfirsturn":
return self._r_GetFirstUrn(
urn=request.args.get("urn", None),
inv=request.args.get("inv", None)
)
elif _request.lower() == "getprevnexturn":
return self._r_GetPrevNext(
urn=request.args.get("urn", None),
inv=request.args.get("inv", None)
)
elif _request.lower() == "getvalidreff":
return self._r_GetValidReff(
urn=request.args.get("urn", None),
inv=request.args.get("inv", None),
level=request.args.get("level", 1, type=int)
)
return "This request does not exist", 404, "" # Should maybe return documentation on top of 404 ?
def _r_GetCapabilities(self, urn=None, inv=None):
""" Provisional route for GetCapabilities request
:param urn: URN to filter the resource
:param inv: Inventory Identifier
:return: GetCapabilities response
"""
return self.retriever.getCapabilities(inventory=inv, urn=urn).strip(), 200, {"content-type": "application/xml"}
def _r_GetPassage(self, urn, inv):
""" Provisional route for GetPassage request
:param urn: URN to filter the resource
:param inv: Inventory Identifier
:return: GetPassage response
"""
return self.retriever.getPassage(inventory=inv, urn=urn).strip(), 200, {"content-type": "application/xml"}
def _r_GetPassagePlus(self, urn, inv):
""" Provisional route for GetPassagePlus request
:param urn: URN to filter the resource
:param inv: Inventory Identifier
:return: GetPassagePlus response
"""
return self.retriever.getPassagePlus(inventory=inv, urn=urn).strip(), 200, {"content-type": "application/xml"}
def _r_GetValidReff(self, urn, inv, level):
""" Provisional route for GetValidReff request
:param urn: URN to filter the resource
:param inv: Inventory Identifier
:return: GetValidReff response
"""
return self.retriever.getValidReff(inventory=inv, urn=urn, level=level).strip(), 200, {"content-type": "application/xml"}
def _r_GetPrevNext(self, urn, inv):
""" Provisional route for GetPrevNext request
:param urn: URN to filter the resource
:param inv: Inventory Identifier
:return: GetPrevNext response
"""
return self.retriever.getPrevNextUrn(inventory=inv, urn=urn).strip(), 200, {"content-type": "application/xml"}
def _r_GetFirstUrn(self, urn, inv):
""" Provisional route for GetFirstUrn request
:param urn: URN to filter the resource
:param inv: Inventory Identifier
:return: GetFirstUrn response
"""
return self.retriever.getFirstUrn(inventory=inv, urn=urn).strip(), 200, {"content-type": "application/xml"}
def _r_GetLabel(self, urn, inv):
""" Provisional route for GetLabel request
:param urn: URN to filter the resource
:param inv: Inventory Identifier
:return: GetLabel response
"""
return self.retriever.getLabel(inventory=inv, urn=urn).strip(), 200, {"content-type": "application/xml"}
[docs]def FlaskNautilusManager(nautilus, app=None):
""" Provides a manager for flask scripts to perform specific maintenance operations
:param nautilus: Nautilus Extension Instance
:param app: Flask Application
:return: Sub-Manager
:rtype: Manager
Import with
.. code-block:: python
:lineno:
manager = Manager(app) # Where app is the name of your app
from capitains_nautilus.flask_ext import manager as nautilus_manager
manager.add_command("nautilus", FlaskNautilusManager(nautilus, app)) # Where nautilus is an instance of FlaskNautilus
"""
_manager = Manager(usage="Perform maintenance operations")
@_manager.command
def flush():
""" Flush the cache system [Right now flushes only the inventory] """
nautilus.retriever.resolver.flush()
@_manager.command
def preprocess():
""" Preprocess the inventory and cache it """
nautilus.retriever.resolver.logger.setLevel(logging.INFO)
nautilus.retriever.resolver.parse(resource=nautilus.retriever.resolver.source, cache=True)
@_manager.command
def inventory():
""" Clean then preprocess the inventory """
nautilus.retriever.resolver.logger.setLevel(logging.INFO)
nautilus.retriever.resolver.flush()
nautilus.retriever.resolver.parse(resource=nautilus.retriever.resolver.source, cache=True)
return _manager