from __future__ import nested_scopes from Ft.Lib.pDomlette import PyExpatReader from xml import xpath import urlparse, urllib import httplib import copy __doc__ = "This code is very rough! It's just demonstration code!" xpeval = xpath.Evaluate # short form _reader = PyExpatReader() def singleChild(path, node, default = None): "convenience function for accessing a singleton child" nodes = xpeval(path, node) if len(nodes)==0: return default else: assert len(nodes) == 1 return nodes[0] ### These elements are translations of stuff in the DTD class Appinfo: def __init__(self, node): pass class Documentation: def __init__(self, node): pass class ReferenceType: def __init__(self, node, wrdl): self.name = getAttribute(node, "name") self.match = getAttribute(node, "match") self.key = getAttribute(node, "key") self._type = getAttribute(node, "type") self._restypes = wrdl.restypes assert self.name!=None assert self.match!=None def type(self): if self._type: return self._restypes[self._type] class Schema: def __init__(self, node): self.type = getAttribute(node, "type", None) self.href = getAttribute(node, "href", None) class NamedInputBase: def __init__(self, node): self.name = str(getAttribute(node, "name", None)) self.apiName = getAttribute(node, "apiName", None) or self.name self.type = getAttribute(node, "type", None) self.default = getAttribute(node, "default", None) self.use = getAttribute(node, "use", None) def set_default(self, method_inputs): if self.default: method_inputs.setarg(self.apiName, self.default) def validate(self, value): "TODO!" class Query(NamedInputBase): def addme(self, method_inputs, value): self.validate(value) # do type coercions here! method_inputs.setparam(self.name, value) class Header(NamedInputBase): def addme(self, method_inputs, value): self.validate(value) # do type coercions here! method_inputs.setheader(self.name, value) class UrlEncodedQuery(NamedInputBase): def addme(self, method_inputs, value): if not method_inputs.body: method_inputs.body = "" self.validate(value) # do type coercions here! method_inputs.setbodyarg(self.name, value) class Input: def __init__(self, node, wrdl, resource): queries = xpeval("query", node) self.queries = [Query(q) for q in queries] headers = xpeval("header", node) self.headers = [Header(q) for q in headers] self.arguments = {} for obj in self.queries + self.headers: self.arguments[obj.apiName] = obj # representation inheritance should be implemented! representations = getAttribute(node, "representations", "") if representations: self.representations = [wrdl.reptypes[name] for name in representations.split()] else: self.representations = resource.representations # todo: an Input should have the same arguments # across all representations for rep in self.representations: for name, arg in rep.arguments.items(): self.arguments[name] = arg ## fixme: needs work class Output: def __init__(self, node, wrdl, resource): headers = xpeval("header", node) self.headers = [Header(q) for q in headers] representations = getAttribute(node, "representations", "") if representations: self.representations = [wrdl.reptypes[name] for name in representations.split()] else: self.representations = resource.representations class MethodDecl: def __init__(self, node, wrdl, resource): if node.nodeName in ("GET", "PUT", "POST", "DELETE"): self.name = str(node.tagName) else: assert node.tagName == "otherMethod" self.name = str(getAttribute(node, "name")) self.createdResourceType = getAttribute(node, "createdResourceType", None) self.input = Input(singleChild("input", node, None), wrdl, resource) self.output = Output(singleChild("output", node, None), wrdl, resource) class _generic_method: def __init__(self, name, resource): self.name = name self.input = _genericInput(resource) self.output = _genericOutput(resource) class _genericInput: def __init__(self, resource): self.queries = [] self.representations = resource.representations self.arguments = {} class _genericOutput: def __init__(self, resource): self.headers = [] self.representations = resource.representations class _genericRepresentationType: def __init__(self): self.name = "" self.mediaType = "*/*" self.references = {} self.documentation = [] self.appinfo = [] self.arguments = {} def serialize(self, args): return None class _genericResourceType: def __init__(self): self.name = "" self.representations = [_genericRepresentationType()] self.GET_method = _generic_method("GET", self) self.POST_method = _generic_method("POST", self) self.PUT_method = _generic_method("PUT", self) self.DELETE_method = _generic_method("DELETE", self) generic_resource = _genericResourceType() def _findmeth(meth, node, wrdl, resource): meth = singleChild(meth, node, None) if meth: return MethodDecl(meth, wrdl, resource) else: return None class ResourceType: def __init__(self, node, wrdl): self.name = getAttribute(node, "name", None) representations = getAttribute(node, "representations", "") self.representations = [wrdl.reptypes[name] for name in representations.split()] self.GET_method = _findmeth("GET", node, wrdl, self) self.POST_method = _findmeth("POST", node, wrdl, self) self.DELETE_method = _findmeth("DELETE", node, wrdl, self) self.PUT_method = _findmeth("PUT", node, wrdl, self) class RepresentationType: def __init__(self, node, wrdl): self.name = getAttribute(node, "name" ) self.namespace = getAttribute(node, "namespace") self.mediaType = getAttribute(node, "mediaType") self.schemas = [Schema(x) for x in xpeval('schema', node)] self.references = {} for refnode in xpeval('referenceType', node): reftype = ReferenceType(refnode, wrdl) self.references[reftype.name] = reftype self.documentation = [Documentation(x) for x in xpeval("documentation", node)] self.appinfo = [Appinfo(x) for x in xpeval("appinfo", node)] self.arguments = {} class UrlEncodedRepresentationType: def __init__(self, node, wrdl): self.name = getAttribute(node, "name" ) self.documentation = [Documentation(x) for x in xpeval("documentation", node)] self.appinfo = [Appinfo(x) for x in xpeval("appinfo", node)] queries = xpeval("query", node) self.queries = [UrlEncodedQuery(q) for q in queries] self.arguments = {} for query in self.queries: self.arguments[query.apiName] = query self.mediaType = "application/x-www-form-urlencoded" def serialize(self, args): return urllib.urlencode(args) class MethodInputs: def __init__(self, input_spec): self.params = {} self.headers = {} self.bodyargs = {} self.body = None self.input_spec = input_spec for arg in input_spec.arguments.values(): arg.set_default(self) def setarg(self, name, value): argobj = self.input_spec.arguments[name] argobj.addme(self, value) def setparam(self, name, value): self.params[name]=value def setheader(self, name, value): self.headers[name]=value def setbodyarg(self, name, value): self.bodyargs[name] = value def setbody(self, body): self.body = body def copy(self): rc = MethodInputs(self.input_spec) rc.params = self.params.copy() rc.headers = self.headers.copy() rc.bodyargs = self.bodyargs.copy() rc.body = self.body return rc class MethodResponse: def __init__(self, httpresponse, reptype): self.httpresponse = httpresponse self.getheader = httpresponse.getheader self.status = httpresponse.status self.reason = httpresponse.reason self.version = httpresponse.version self.reptype = reptype self._data = None self._dom = None def references(self, name): if not self.reptype: raise AssertionError("Need a representation type \ to have references") dom = self.dom() reftype = self.reptype.references[name] if reftype.match.startswith("/"): nodes = xpeval(reftype.match, dom) else: nodes = xpeval("//"+reftype.match, dom) if reftype.key: resources ={} for node in nodes: uri = xpeval("string(.)", node) key = xpeval(dom, reftype.key) value = xpeval(dom, reftype.match) resources[key] = Resource(reftype.type(), uri) else: resources = [] for node in nodes: uri = xpeval("string(.)", node) resources.append(Resource(reftype.type(), uri)) return resources def body(self): mediatype = self.httpresponse.getheader("Content-type") if (self.reptype.mediaType != "*/*" and mediatype != self.reptype.mediaType and not mediatype.startswith(self.reptype.mediaType)): #TODO: should parse media types properly! error = ("Content-type should be " + self.reptype.mediaType + " not " + mediatype) assert 0, error if not self._data: self._data = self.httpresponse.read() return self._data def dom(self): if not self._dom: self._dom = _reader.fromString(self.body()) return self._dom class RuntimeMethod: def __init__(self, uri, method, inRepType, outRepType): assert inRepType and outRepType and method and uri self.method = method assert self.method self.inRepType = inRepType self.outRepType = outRepType uri = str(uri) # TODO: think about 18n of URIs later self.scheme, self.location, self.path, self.params, \ self.query, self.fragid = urlparse.urlparse(uri) def newMethodInputs(self): return MethodInputs(self.method.input) def __call__(self, body=None, method_inputs=None, **args): import copy if method_inputs: method_inputs = method_inputs.copy() else: method_inputs = self.newMethodInputs() method_inputs.setbody(body) for key, value in args.items(): method_inputs.setarg(key, value) headers = method_inputs.headers.copy() # TODO: this should come from the output, not the repType headers["Accept"] = self.outRepType.mediaType headers["Content-type"] = self.inRepType.mediaType # TODO: handle form-data if method_inputs.body: body = method_inputs.body else: body = self.inRepType.serialize(method_inputs.bodyargs) # TODO: mix in defaulted headers and query params! if method_inputs.params: path = self.path+"?"+urllib.urlencode(method_inputs.params) else: path = self.path conn = httplib.HTTPConnection(self.location) conn.request(self.method.name, path, body, headers) response = conn.getresponse() return MethodResponse(response, self.outRepType) class Resource: def __init__(self, resourceType, uri): self.resourceType = resourceType self.uri = uri if not resourceType: resourceType = generic_resource if resourceType.GET_method: for rep in resourceType.GET_method.output.representations: meth = RuntimeMethod(uri, resourceType.GET_method, _genericRepresentationType(), rep) repname = rep.name if repname: setattr(self, "GET_"+repname, meth) # arbitrarily reuse the last one as the unadorned GET self.GET = meth if resourceType.POST_method: for inrep in resourceType.POST_method.input.representations: inrepname = inrep.name for outrep in resourceType.POST_method.output.representations: outrepname = outrep.name meth = RuntimeMethod(uri, resourceType.POST_method, inrep, outrep) if inrepname and outrepname: methname = "POST_"+inrepname+"_RETURNING_"+outrepname setattr(self, methname, meth) # arbitrarily reuse the last one as the unadorned GET self.POST = meth # TODO: handle other methods #if resourceType.PUT_method: #self.PUT = RuntimeMethod(uri, resourceType.PUT_method, #_genericRepresentationType()) if resourceType.DELETE_method: self.DELETE = RuntimeMethod(uri, resourceType.DELETE_method, _genericRepresentationType(), _genericRepresentationType()) def __repr__(self): return "" def getAttribute(el, attr, default=None): try: # TODO: take a more intelligent approach to i18n! return str(xpeval("@"+attr, el)[0].nodeValue) except IndexError: return default class Wrdl: def __init__(self, url): doc = _reader.fromStream(urllib.urlopen(url)) self.restypes = {} # list of resource types self.reptypes = {} # list of representation types reptypes = xpeval('//representationType', doc) for reptype in reptypes: newtype = RepresentationType(reptype, self) self.reptypes[newtype.name]=newtype reptypes = xpeval('//urlEncodedRepresentation', doc) for reptype in reptypes: newtype = UrlEncodedRepresentationType(reptype, self) self.reptypes[newtype.name]=newtype # not finished #formdatatypes = xpeval('//formdataRepresentation', doc) #for formdata in formdatatypes: # newtype = FormDataRepresentationType(formdata, self) # self.reptypes[newtype.name]=newtype resourceTypes = xpeval('//resourceType', doc) for restype in resourceTypes: newtype = ResourceType(restype, self) self.restypes[newtype.name]=newtype def newResource(self, name, uri): return Resource(self.restypes[name], uri) def main(url): wrdl = Wrdl("meerkat.wrdl") # bind a resource type to a URI r = wrdl.newResource("meerkat", "http://www.oreillynet.com/meerkat/") # now GET that resource...any representation res = r.GET( channel=555, timePeriod="7DAY", search="Prescod") # alternately, GET a particular representation # (in this case "meerkat_xml_flavour") res = r.GET_meerkat_xml_flavour( channel=555, timePeriod="7DAY", search="Prescod") text = res.body() # get resource body assert text.find("Prescod")>0 # vanity assertion # get a list of references (XML or HTML hyperlinks) links = res.references("link") assert len(links)>1 # sanity check # download representation of first link newres = links[0].GET() # get the resource body for that link text = newres.body() assert text.find("Prescod")>0 #vanity assertion wrdl = Wrdl("babelfish.wrdl") s = wrdl.newResource("babelfish", "http://babelfish.altavista.com/tr") # do a POST res = s.POST(text="Hello world", languages="en_es") assert res.body().find("Hola")>0 # more specific way of POSTing: res = s.POST_babelFormData_RETURNING_html( text="Hello world", languages="en_es") if __name__=="__main__": url = "meerkat.wrdl" main(url) # todo: # default params # param checks