196 lines
6.0 KiB
Python
196 lines
6.0 KiB
Python
"""Do dispatcher stuff over SOAP. POC code."""
|
|
|
|
from __future__ import unicode_literals
|
|
|
|
import logging
|
|
|
|
from pysimplesoap.server import SoapDispatcher
|
|
from django.conf import settings
|
|
from django.contrib.sites.models import Site
|
|
from django.core.urlresolvers import reverse_lazy
|
|
from django.http import HttpResponse
|
|
from django.views.decorators.csrf import csrf_exempt
|
|
|
|
from facts.models import Fact, FactCategory
|
|
|
|
|
|
log = logging.getLogger(__name__)
|
|
|
|
|
|
class ComplexType(object):
|
|
|
|
_type_info = None
|
|
|
|
def __init__(self, data=None):
|
|
"""Bind data to this instance, if provided.
|
|
|
|
:param data: data to bind, if desired
|
|
:type data: _type_info
|
|
"""
|
|
|
|
self.data = None
|
|
if data:
|
|
ComplexType._checker(data, self._type_info)
|
|
# exception would have been raised if there was a problem
|
|
self.data = data
|
|
|
|
@staticmethod
|
|
def _checker(data, type_info, label=None):
|
|
label = '\'{0:s}\''.format(label) if label else 'data'
|
|
|
|
if isinstance(type_info, dict):
|
|
if not isinstance(data, dict):
|
|
raise Exception("{0:s} is supposed to be a dict but is {1:s}".format(label, type(data)))
|
|
|
|
for key in type_info.keys():
|
|
try:
|
|
sub_data = data[key]
|
|
except KeyError:
|
|
raise Exception("key '{0:s}' not in data {1:s}".format(key, data))
|
|
|
|
ComplexType._checker(sub_data, type_info[key], label=key)
|
|
elif isinstance(type_info, list):
|
|
if not isinstance(data, list):
|
|
raise Exception("{0:s} is supposed to be a list but is {1:s}".format(label, type(data)))
|
|
|
|
for item in data:
|
|
ComplexType._checker(item, type_info[0], label='item in list {0:s}'.format(label))
|
|
else:
|
|
if type_info != type(data):
|
|
raise Exception("{0:s} is supposed to be {1:s} but is {2:s}".format(label, type_info, type(data)))
|
|
|
|
|
|
class WsFact(ComplexType):
|
|
_type_info = {
|
|
'fact': {
|
|
'key': unicode,
|
|
'fact': unicode,
|
|
'nickmask': unicode,
|
|
}
|
|
}
|
|
|
|
|
|
class WsFactList(ComplexType):
|
|
_type_info = {
|
|
'facts': [WsFact._type_info]
|
|
}
|
|
|
|
|
|
def get_fact_by_key(key, regex=None):
|
|
"""Get a fact of the matching key and regex.
|
|
|
|
If there are multiple matching facts, you get a random one.
|
|
|
|
:param key: fact category to search for
|
|
:type key: unicode
|
|
:param regex: regex to filter results by (optional)
|
|
:type regex: unicode
|
|
"""
|
|
|
|
fact = Fact.objects.random_fact(key, regex)
|
|
|
|
ret = {'key': fact.category.name, 'fact': fact.fact, 'nickmask': fact.nickmask}
|
|
return WsFact({'fact': ret}).data
|
|
|
|
|
|
def get_facts_by_key(key, regex=None):
|
|
"""Get all facts of the matching key and regex."""
|
|
|
|
try:
|
|
fact_category = FactCategory.objects.get(name=key)
|
|
except FactCategory.DoesNotExist:
|
|
return None
|
|
|
|
facts = Fact.objects.filter(category=fact_category)
|
|
ret_facts = []
|
|
for fact in facts:
|
|
ret_fact = {'key': fact.category.name, 'fact': fact.fact, 'nickmask': fact.nickmask}
|
|
ret_facts.append({'fact': ret_fact})
|
|
|
|
return WsFactList({'facts': ret_facts}).data
|
|
|
|
|
|
class LazyAbsoluteUrl(object):
|
|
|
|
"""Use (abuse?) duck typing to wrap reverse_lazy in a manner that lets us
|
|
append domain stuff but still be lazy, and appear as a string.
|
|
|
|
We need to do this lazily because SoapRequest objects are probably created at the module
|
|
level, which means they'll probably first get hit during urls.py scanning, and we can't
|
|
reference reverse() then (as urls haven't finished importing). Since we need a domain and
|
|
stuff, we need to do that lazily too, and can't simply use reverse_lazy(). Hence this
|
|
class that tries to look like a string
|
|
"""
|
|
|
|
def __init__(self, url_name):
|
|
"""Set up for generating an absolute lazy URL, note what name to reverse_lazy()
|
|
eventually.
|
|
|
|
:param url_name: the name (as registered from urls.py) to look up with reverse_lazy
|
|
:type url_name: unicode
|
|
"""
|
|
|
|
self.url_name = url_name
|
|
|
|
def __add__(self, other):
|
|
"""SoapDispatcher tries to add self.action (this) to a string, so we need
|
|
to support that.
|
|
|
|
:param other: string to concatenate
|
|
:type other: str
|
|
:returns: concatenated string
|
|
:rtype: str
|
|
"""
|
|
|
|
return '{0:s}{1:s}'.format(self, other)
|
|
|
|
def __str__(self):
|
|
"""Lazily generate a reverse absolute URL, via Site + url_name lookup.
|
|
|
|
:returns: absolute URL
|
|
:rtype: str
|
|
"""
|
|
|
|
return 'http://{0:s}{1:s}'.format(Site.objects.get_current().domain,
|
|
reverse_lazy(self.url_name))
|
|
|
|
def replace(self, *args, **kwargs):
|
|
"""We're kind of tricking things to think we're a string in SoapDispatcher,
|
|
so we need to look like a string.
|
|
"""
|
|
|
|
return '{0:s}'.format(self).replace(*args, **kwargs)
|
|
|
|
|
|
# create the SOAP dispatcher
|
|
# note - setting debug lets exception details include the traceback
|
|
lazy_soap_url = LazyAbsoluteUrl('facts_soap_dispatcher')
|
|
soap = SoapDispatcher(
|
|
name='facts',
|
|
location=lazy_soap_url,
|
|
action=lazy_soap_url,
|
|
namespace=lazy_soap_url, prefix='drb',
|
|
documentation="dr.botzo Facts API",
|
|
debug=settings.DEBUG,
|
|
)
|
|
|
|
|
|
# register functions with the SOAP dispatcher
|
|
soap.register_function('getFactByKey', get_fact_by_key, args={'key': unicode, 'regex': unicode},
|
|
returns=WsFact._type_info)
|
|
soap.register_function('getFactsByKey', get_facts_by_key, args={'key': unicode, 'regex': unicode},
|
|
returns=WsFactList._type_info)
|
|
|
|
|
|
# django URL handler, point urls.py at this
|
|
@csrf_exempt
|
|
def dispatcher_handler(request):
|
|
if request.method == "POST":
|
|
response = HttpResponse(content_type="application/xml")
|
|
response.write(soap.dispatch(request.body))
|
|
else:
|
|
response = HttpResponse(content_type="application/xml")
|
|
response.write(soap.wsdl())
|
|
response['Content-length'] = str(len(response.content))
|
|
return response
|