#!/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__)