"""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