Source code for yatel.server

#!/usr/bin/env python
# -*- coding: utf-8 -*-

# "THE WISKEY-WARE LICENSE":
# <utn_kdd@googlegroups.com> wrote this file. As long as you retain this notice
# you can do whatever you want with this stuff. If we meet some day, and you
# think this stuff is worth it, you can buy us a WISKEY in return.


#===============================================================================
# DOCS
#===============================================================================

"""Http server for querying, using QBJ or YQL (Yatel Query Languaje).

"""

#===============================================================================
# IMPORTS
#===============================================================================

import json, string, os

import jsonschema

import flask

from yatel import db, qbj


#===============================================================================
# CONSTANTS
#===============================================================================

#: JSON schema to validate configuration
CONF_SCHEMA = {
    "schema": "yatel server configuration schema",
    "type": "object",
    "properties": {
        "CONFIG": {
            "type": "object",
            "properties": {
                "DEBUG": {"type": "boolean"}
            },
            "additionalProperties": True
        },
        "NETWORKS": {
            "type": "object",
            "patternProperties": {
                "^[a-zA-Z0-9_-]$": {
                    "type": "object",
                    "properties":{
                        "uri": {"type": "string"},
                        "qbj": {"type": "boolean"}
                    },
                    "additionalProperties": False
                }
            }
        }
    }
}

CONF_BASE = {
    "CONFIG": {
        "DEBUG": True
    },
    "NETWORKS": {
        "network-name": {
            "uri": "uri",
            "qbj": True,
        }
    }
}

#: Template for WSGI configuration
WSGI_BASE_TPL = string.Template("""
#!/usr/bin/env python
# -*- coding: utf-8 -*-

import sys, os, json

# Changes working directory so relative paths (and template lookup) work again
sys.path.append(os.path.dirname(__file__))
os.chdir(os.path.dirname(__file__))

# Error output redirect to Apache2 logs
sys.stdout =  sys.stderr

with open("${confpath}") as fp:
    conf = json.load(fp)

from yatel import server
application = server.from_dict(conf)

""")



#===============================================================================
# BASE CLASS
#===============================================================================

[docs]class YatelHttpServer(flask.Flask): """Yatel server class. """ def __init__(self, **config): super(YatelHttpServer, self).__init__(__name__) self.config.from_object(config) self._nws = {} self.route("/", methods=["POST", "GET"])(self.its_works) self.route("/qbj/<nwname>", methods=["POST", "GET"])(self.qbj)
[docs] def add_nw(self, nwname, nw, enable_qbj): """Adds the given ``nw`` to the server. """ if not isinstance(nw, db.YatelNetwork): raise TypeError("nw must be db.YatelNetwork subclass") self._nws[nwname] = {"nwname": nwname, "nw": nw} if enable_qbj: self._nws[nwname]["qbj"] = qbj.QBJEngine(nw)
def its_works(self): return flask.jsonify({type(self).__name__: "Works"})
[docs] def qbj(self, nwname): """It handles the server query calls. """ qbj_nw = self._nws[nwname]["qbj"] response = qbj_nw.execute( flask.request.json, stacktrace=self.config["DEBUG"] ) return flask.jsonify(response)
[docs] def nw(self, name): """Returns the context network. """ return self._nws[name]["nw"] #============================================================================== # FUNCTIONS #==============================================================================
[docs]def validate_conf(confdata): """Validates that the configuration structure given as JSON is correct. """ return jsonschema.validate(confdata, CONF_SCHEMA)
[docs]def from_dict(data): """Returns a server created with a dictionary as configuration. ``data`` keys: - Path to the configuration file. - ``IP`` and ``port`` where the service will be listening separated by a ``:`` """ validate_conf(data) config = data["CONFIG"] server = YatelHttpServer(**config) for nwname, nwdata in data["NETWORKS"].items(): nw = db.YatelNetwork(**db.parse_uri(nwdata["uri"])) qbj = nwdata.get("qbj", False) server.add_nw(nwname, nw, qbj) return server
[docs]def get_conf_template(): """Returns a JSON configuration template as a String. """ return json.dumps(CONF_BASE, indent=2)
[docs]def get_wsgi_template(confpath): """Returns WSGI configuration template as a String. Parameters ---------- confpath: string Path to configuration file. """ if not os.path.isfile(confpath): raise ValueError("confpath '{}' not exists".format(confpath)) return WSGI_BASE_TPL.substitute(confpath=confpath).strip() #=============================================================================== # MAIN #===============================================================================
if __name__ == "__main__": print(__doc__)