Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
tree: 5fd06dc112
Fetching contributors…

Octocat-spinner-32-eaf2f5

Cannot retrieve contributors at this time

file 147 lines (125 sloc) 6.768 kb
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146
"""
bind -- a framework for creating web service API bindings in Python.

bind -- the core of the bind framework

Copyright (c) 2011 Rafe Kettler

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
"""

import urlparse
import httplib2
import re
import base64

class URLPattern(object):
    """A URL pattern. Patterns are of the form:

/path/:param/otherpath/day:param2/

A section of a path can begin with a ':' to indicate a parameter in
the URL. A parameter consumes characters until the next '/'. Patterns
cannot be absolute paths, only relative."""
    param_pattern = re.compile(r':(\w+)')

    def __init__(self, pattern):
        self.pattern = pattern
        self.parse()

    def parse(self):
        """Parse the provided URL pattern into static and dynamic
components. The resulting attribute, self.parameters, is a list of
tuples. Each tuple contains two elements: the value of that part of
the URL pattern, and a dictionary containing a key "dynamic" mapped
to a boolean value telling the URLPattern whether that particular
part of the URL must be replaced. Dynamic components also have a
key "param" which contains the name of the parameter (e.g. "foo" if
the parameter were ":foo"."""
        self.parameters = []
        for parameter in self.pattern.split('/'):
            match = self.param_pattern.match(parameter)
            if match is None:
                self.parameters.append((parameter, {"dynamic":False}))
            else:
                self.parameters.append((parameter, {"dynamic":True,
                                                     "param":match.group(1)}
                                                     ))

    def make_url(self, **kw):
        """Make a URL based on the pattern. Takes keyword arguments based
on the parameters of the URL pattern."""
        tojoin = []
        for parameter, info in self.parameters:
            if info['dynamic']:
                tojoin.append(kw.pop(info['param']))
            else:
                tojoin.append(parameter)
        return '/'.join(tojoin)

class Request(object):
    """Represents a request to an API."""
    def __init__(self, pattern, method="GET", base_url=None,
                 requires_auth=False, request_callback=lambda x, y: (x, y),
                 response_callback=lambda x, y: (x, y)):
        self.requires_auth = requires_auth
        if self.requires_auth:
            self.username, self.password = None, None
        self.base_url = base_url
        self.pattern = URLPattern(pattern)
        self.http = httplib2.Http()
        self.method = "GET"
        # Callback attributes need to be staticmethods so that they don't
        # get passed self when called by an instance
        self.request_callback = staticmethod(request_callback)
        self.response_callback = staticmethod(response_callback)

    def authenticate(self, username, password):
        """Set up to authenticate using HTTP basic authentication. Note
that this just sets up to authenticate, the method does not
perform any requests."""
        self.username, self.password = username, password
        authstring = base64.encodestring(self.username + ":" +
                                         self.password)
        self.auth_header = "Basic " + authstring

    def set_base_url(self, url):
        """Set the base URL for requests."""
        # If the base URL was explicitly set in the constructor, don't
        # change it
        if self.base_url is None:
            self.base_url = url

    def request(self, body=None, headers={}, **params):
        """Make an HTTP request. Takes keyword arguments based on the
instance's URL pattern. Returns a tuple (response, content)."""
        relative_url = self.pattern.make_url(**params)
        absolute_url = self.base_url + relative_url
        print absolute_url
        if self.requires_auth:
            # Add HTTP basic auth headers
            headers['Authorization'] = self.auth_header
        headers, body = self.request_callback(headers, body)
        response, content = self.http.request(absolute_url, self.method,
                                              body, headers)
        return self.response_callback(response, content)

    def __call__(self, *args, **kw):
        """Alias for self.request. Allows convenient API call methods:
api = MyAPI()
# Instead of api.do_some_request.request(...)
api.do_some_request(param1=value)
"""
        return self.request(*args, **kw)

class API(object):
    """Represents an API."""
    POSSIBLE_GLOBAL_ATTRS = ["BASE_URL", "REQUEST_CALLBACK",
                             "RESPONSE_CALLBACK"]

    def __init__(self):
        # Set any globally defined attributes for the Request class
        # attributes
        global_attrs = []
        for attr in self.POSSIBLE_GLOBAL_ATTRS:
            if getattr(self, attr, False):
                global_attrs.append(attr)
        for req in self.__class__.__dict__.values():
            if isinstance(req, Request):
                if "BASE_URL" in global_attrs:
                    req.set_base_url(self.BASE_URL)
                if "REQUEST_CALLBACK" in global_attrs:
                    req.request_callback = self.REQUEST_CALLBACK
                if "RESPONSE_CALLBACK" in global_attrs:
                    req.response_callback = self.RESPONSE_CALLBACK

    def authenticate(self, username, password):
        """Authentic using basic HTTP authentication."""
        for req in self.__class__.__dict__.values():
            if isinstance(req, Request):
                if req.requires_auth:
                    req.authenticate(username, password)
Something went wrong with that request. Please try again.