facts: proof of concept grade SOAP WS

mostly just to prove to myself that i can use pysimplesoap and be kinda
pleased with the results
This commit is contained in:
Brian S. Stephan 2015-08-14 16:54:31 -05:00
parent 68cf0f8816
commit 425bf64baa
3 changed files with 204 additions and 0 deletions

View File

@ -10,6 +10,7 @@ urlpatterns = patterns('',
url(r'^$', 'dr_botzo.views.home', name='home'),
url(r'^dispatch/', include('dispatch.urls')),
url(r'^facts/', include('facts.urls')),
url(r'^markov/', include('markov.urls')),
url(r'^races/', include('races.urls')),

195
dr_botzo/facts/soap.py Normal file
View File

@ -0,0 +1,195 @@
"""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

8
dr_botzo/facts/urls.py Normal file
View File

@ -0,0 +1,8 @@
"""URL patterns for the facts API."""
from django.conf.urls import patterns, url
urlpatterns = patterns('',
url(r'^soap/', 'facts.soap.dispatcher_handler', name='facts_soap_dispatcher'),
)