- default tip
authorCraig Tweedy <craig.tweedy@hedgehoglab.com>
Sun Nov 29 14:28:14 2009 +0000
changeset 0c5b753c9f7d4
-
fixx.py
httplib2/iri2uri.py
mimeTypes.py
restful_lib.py
talis.py
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/fixx.py	Sun Nov 29 14:28:14 2009 +0000
     1.3 @@ -0,0 +1,273 @@
     1.4 +import urllib, base64,httplib
     1.5 +from restful_lib import Connection
     1.6 +import simplejson as json
     1.7 +
     1.8 +####USAGE
     1.9 +# from fixx import Fixx
    1.10 +# variable = Fixx("fixx url","port","username","password")
    1.11 +# variable.getAllIssues()
    1.12 +# variable.getIssue("issuenumber")
    1.13 +# Data should be inputted in dictionaries on all cases except the filtering/search/pagination for getting issues, which should be a list.
    1.14 +# returns python objects
    1.15 +#
    1.16 +###
    1.17 +
    1.18 +
    1.19 +class Fixx:
    1.20 +    
    1.21 +    def __init__(self,url,port,name,password):
    1.22 +        self.url=url
    1.23 +        self.auth = base64.encodestring('%s:%s' % (name, password))[:-1]
    1.24 +        self.type = "application/json"
    1.25 +        self.port = port
    1.26 +    
    1.27 +        ####################
    1.28 +        #     ISSUES       #
    1.29 +        ####################
    1.30 +        
    1.31 +    def getAllIssues(self,filterOptions = None):
    1.32 +        if(filterOptions is not None):
    1.33 +            options = "&".join(filterOptions)
    1.34 +            url = "/api/issues?"+options
    1.35 +        else:
    1.36 +            url = "/api/issues"
    1.37 +        conn = Connection(self.url)
    1.38 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
    1.39 +        return json.loads(response['body']) 
    1.40 +    
    1.41 +        
    1.42 +    def getAllIssuesForProjects(self,projectId):
    1.43 +        url = "/api/projects/"+projectId+"/issues"
    1.44 +        conn = Connection(self.url)
    1.45 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
    1.46 +        return json.loads(response['body']) 
    1.47 +        
    1.48 +    def getIssue(self,issueId):
    1.49 +        url = "/api/issues/"+issueId
    1.50 +        conn = Connection(self.url)
    1.51 +        response = conn.request_get(url, headers={'Accept':self.type, 'Content-Type':self.type, "Authorization":"Basic %s" % self.auth})
    1.52 +        return json.loads(response['body']) 
    1.53 +    
    1.54 +    def createIssue(self,data):
    1.55 +        data = json.dumps(data)
    1.56 +        url = "/api/issues"
    1.57 +        conn = Connection(self.url)
    1.58 +        response = conn.request_post(url,body=data, headers={'Accept':self.type, 'Content-Type':self.type, "Authorization":"Basic %s" % self.auth})
    1.59 +        return json.loads(response['body']) 
    1.60 +        
    1.61 +    def updateIssue(self,issueId,data):
    1.62 +        data = json.dumps(data)
    1.63 +        url = "/api/issues/"+issueId
    1.64 +        conn = Connection(self.url)
    1.65 +        response = conn.request_put(url, body=data, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
    1.66 +        return json.loads(response['body']) 
    1.67 +
    1.68 +    def deleteIssue(self,issueId):
    1.69 +        url = "/api/issues/"+issueId
    1.70 +        conn = Connection(self.url)
    1.71 +        response = conn.request_delete(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
    1.72 +        return json.loads(response['body']) 
    1.73 +
    1.74 +
    1.75 +        ####################
    1.76 +        #     PROJECTS     #
    1.77 +        ####################
    1.78 +    def getAllProjects(self):
    1.79 +        url = "/api/projects"
    1.80 +        conn = Connection(self.url)
    1.81 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
    1.82 +        return json.loads(response['body']) 
    1.83 +
    1.84 +    def getProject(self,projectId):
    1.85 +        url = "/api/projects/"+projectId
    1.86 +        conn = Connection(self.url)
    1.87 +        response = conn.request_get(url, headers={'Accept':self.type, 'Content-Type':self.type, "Authorization":"Basic %s" % self.auth})
    1.88 +        return json.loads(response['body']) 
    1.89 +
    1.90 +    def createProject(self,data):
    1.91 +        data = json.dumps(data)
    1.92 +        url = "/api/projects"
    1.93 +        conn = Connection(self.url)
    1.94 +        response = conn.request_post(url,body=data, headers={'Accept':self.type, 'Content-Type':self.type, "Authorization":"Basic %s" % self.auth})
    1.95 +        return json.loads(response['body']) 
    1.96 +
    1.97 +    def updateProject(self,projectId,data):
    1.98 +        data = json.dumps(data)
    1.99 +        url = "/api/projects/"+projectId
   1.100 +        conn = Connection(self.url)
   1.101 +        response = conn.request_put(url, args=data, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.102 +        return json.loads(response['body']) 
   1.103 +        
   1.104 +        
   1.105 +        ####################
   1.106 +        #   RESOLUTIONS    #
   1.107 +        ####################
   1.108 +    def getResolutionsForProject(self,projectId):
   1.109 +        url = "/api/projects/"+projectId+"/resolutions"
   1.110 +        conn = Connection(self.url)
   1.111 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.112 +        return json.loads(response['body']) 
   1.113 +        
   1.114 +    def getIssueTypesForProject(self,projectId):
   1.115 +        url = "/api/projects/"+projectId+"/issue-types"
   1.116 +        conn = Connection(self.url)
   1.117 +        response = conn.request_get(url, args=data, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.118 +        return json.loads(response['body']) 
   1.119 +        
   1.120 +        ####################
   1.121 +        #      AREAS       #
   1.122 +        #################### 
   1.123 +        
   1.124 +    def getAllAreasForProject(self,projectId):
   1.125 +         url = "/api/projects/"+projectId+"/areas"
   1.126 +         conn = Connection(self.url)
   1.127 +         response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.128 +         return json.loads(response['body']) 
   1.129 +         
   1.130 +         
   1.131 +        ####################
   1.132 +        #      VERSIONS    #
   1.133 +        #################### 
   1.134 +
   1.135 +    def getAllVersionsForProject(self,projectId):
   1.136 +         url = "/api/projects/"+projectId+"/versions"
   1.137 +         conn = Connection(self.url)
   1.138 +         response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.139 +         return json.loads(response['body']) 
   1.140 +
   1.141 +    def getReleasedVersionsOnlyForProject(self,projectId):
   1.142 +         url = "/api/projects/"+projectId+"/versions?type=fixfor"
   1.143 +         conn = Connection(self.url)
   1.144 +         response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.145 +         return json.loads(response['body']) 
   1.146 +         
   1.147 +        ####################
   1.148 +        #      COMMENTS    #
   1.149 +        #################### 
   1.150 +
   1.151 +    def getAllCommentsForIssue(self,issueId):
   1.152 +         url = "/api/issues/"+issueId+"/comments"
   1.153 +         conn = Connection(self.url)
   1.154 +         response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.155 +         return json.loads(response['body']) 
   1.156 +         
   1.157 +    def addCommentToIssue(self,issueId,data):
   1.158 +        data = json.dumps(data)
   1.159 +        url = "/api/issues/"+issueId+"/comments"
   1.160 +        conn = Connection(self.url)
   1.161 +        response = conn.request_post(url, body=data,headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.162 +        return json.loads(response['body']) 
   1.163 +        
   1.164 +    def deleteComment(self,issueId,commentId):
   1.165 +        url = "/api/issues/"+issueId+"/comments/"+commentId
   1.166 +        conn = Connection(self.url)
   1.167 +        response = conn.request_delete(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.168 +        return json.loads(response['body']) 
   1.169 +        
   1.170 +        ####################
   1.171 +        #    TIME LOGS     #
   1.172 +        ####################
   1.173 +    def getAllTimelogsForIssue(self,issueId):
   1.174 +        url = "/api/issues/"+issueId+"/timelogs"
   1.175 +        conn = Connection(self.url)
   1.176 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.177 +        return json.loads(response['body']) 
   1.178 +        
   1.179 +    def addTimelogToIssue(self,issueId,data):
   1.180 +        data = json.dumps(data)
   1.181 +        url = "/api/issues/"+issueId+"/timelogs"
   1.182 +        conn = Connection(self.url)
   1.183 +        response = conn.request_post(url, body=data, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.184 +        return json.loads(response['body']) 
   1.185 +        
   1.186 +    def deleteTimelog(self,issueId,timelogId):
   1.187 +        url = "/api/issues/"+issueId+"/timelogs/"+timelogId
   1.188 +        conn = Connection(self.url)
   1.189 +        response = conn.request_delete(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.190 +        return json.loads(response['body']) 
   1.191 +    
   1.192 +        ####################
   1.193 +        #       TAGS       #
   1.194 +        ####################
   1.195 +    def getAllTagsForIssue(self,issueId):
   1.196 +        url = "/api/issues/"+issueId+"/tags"
   1.197 +        conn = Connection(self.url)
   1.198 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.199 +        return json.loads(response['body']) 
   1.200 +
   1.201 +    def addTagToIssue(self,issueId,data):
   1.202 +        data = json.dumps(data)
   1.203 +        url = "/api/issues/"+issueId+"/tags"
   1.204 +        conn = Connection(self.url)
   1.205 +        response = conn.request_post(url, body=data, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.206 +        return json.loads(response['body']) 
   1.207 +
   1.208 +    def deleteTag(self,issueId,tagId):
   1.209 +        url = "/api/issues/"+issueId+"/timelogs/"+timelogId
   1.210 +        conn = Connection(self.url)
   1.211 +        response = conn.request_delete(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.212 +        return json.loads(response['body']) 
   1.213 +        
   1.214 +        ####################
   1.215 +        #     CHANGELOGS   #
   1.216 +        ####################
   1.217 +    def getAllChangelogsForIssue(self,issueId):
   1.218 +        url = "/api/issues/"+issueId+"/changelogs"
   1.219 +        conn = Connection(self.url)
   1.220 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.221 +        return json.loads(response['body']) 
   1.222 +        
   1.223 +        ####################
   1.224 +        #       USERS      #
   1.225 +        ####################
   1.226 +    def getAllUsers(self):
   1.227 +        url = "/users"
   1.228 +        conn = Connection(self.url)
   1.229 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.230 +        return json.loads(response['body']) 
   1.231 +
   1.232 +    def getUser(self,user):
   1.233 +        url = "/users/"+user
   1.234 +        conn = Connection(self.url)
   1.235 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.236 +        return json.loads(response['body']) 
   1.237 +
   1.238 +    def getUsersForProject(self,projectId):
   1.239 +        url = "/api/projects/"+projectId+"/users"
   1.240 +        conn = Connection(self.url)
   1.241 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.242 +        return json.loads(response['body']) 
   1.243 +        
   1.244 +    def getClientsForProject(self,projectId):
   1.245 +        url = "/api/projects/"+projectId+"/clients"
   1.246 +        conn = Connection(self.url)
   1.247 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.248 +        return json.loads(response['body']) 
   1.249 +        
   1.250 +    def getWatchersForIssue(self,issueId):
   1.251 +        url = "/api/issues/"+issueId+"/watchers"
   1.252 +        conn = Connection(self.url)
   1.253 +        response = conn.request_get(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.254 +        return json.loads(response['body']) 
   1.255 +
   1.256 +    def addWatcherToIssue(self,issueId,userId):
   1.257 +        url = "/api/issues/"+issueId+"/watchers"+userId
   1.258 +        conn = Connection(self.url)
   1.259 +        response = conn.request_put(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.260 +        return json.loads(response['body']) 
   1.261 +
   1.262 +        ####################
   1.263 +        #    PRIORITIES    #
   1.264 +        ####################    
   1.265 +        
   1.266 +    def getAllPriorities(self):
   1.267 +        url = "/priorities"
   1.268 +        conn = Connection(self.url)
   1.269 +        response = conn.request_put(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.270 +        return json.loads(response['body']) 
   1.271 +        
   1.272 +    def getPriority(self,priorityId):
   1.273 +        url = "/priorities/"+priorityId
   1.274 +        conn = Connection(self.url)
   1.275 +        response = conn.request_put(url, headers={'Accept':self.type,'Content-Type':self.type,"Authorization":"Basic %s" % self.auth})
   1.276 +        return json.loads(response['body']) 
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/httplib2/iri2uri.py	Sun Nov 29 14:28:14 2009 +0000
     2.3 @@ -0,0 +1,110 @@
     2.4 +"""
     2.5 +iri2uri
     2.6 +
     2.7 +Converts an IRI to a URI.
     2.8 +
     2.9 +"""
    2.10 +__author__ = "Joe Gregorio (joe@bitworking.org)"
    2.11 +__copyright__ = "Copyright 2006, Joe Gregorio"
    2.12 +__contributors__ = []
    2.13 +__version__ = "1.0.0"
    2.14 +__license__ = "MIT"
    2.15 +__history__ = """
    2.16 +"""
    2.17 +
    2.18 +import urlparse
    2.19 +
    2.20 +
    2.21 +# Convert an IRI to a URI following the rules in RFC 3987
    2.22 +# 
    2.23 +# The characters we need to enocde and escape are defined in the spec:
    2.24 +#
    2.25 +# iprivate =  %xE000-F8FF / %xF0000-FFFFD / %x100000-10FFFD
    2.26 +# ucschar = %xA0-D7FF / %xF900-FDCF / %xFDF0-FFEF
    2.27 +#         / %x10000-1FFFD / %x20000-2FFFD / %x30000-3FFFD
    2.28 +#         / %x40000-4FFFD / %x50000-5FFFD / %x60000-6FFFD
    2.29 +#         / %x70000-7FFFD / %x80000-8FFFD / %x90000-9FFFD
    2.30 +#         / %xA0000-AFFFD / %xB0000-BFFFD / %xC0000-CFFFD
    2.31 +#         / %xD0000-DFFFD / %xE1000-EFFFD
    2.32 +
    2.33 +escape_range = [
    2.34 +   (0xA0, 0xD7FF ),
    2.35 +   (0xE000, 0xF8FF ),
    2.36 +   (0xF900, 0xFDCF ),
    2.37 +   (0xFDF0, 0xFFEF),
    2.38 +   (0x10000, 0x1FFFD ),
    2.39 +   (0x20000, 0x2FFFD ),
    2.40 +   (0x30000, 0x3FFFD),
    2.41 +   (0x40000, 0x4FFFD ),
    2.42 +   (0x50000, 0x5FFFD ),
    2.43 +   (0x60000, 0x6FFFD),
    2.44 +   (0x70000, 0x7FFFD ),
    2.45 +   (0x80000, 0x8FFFD ),
    2.46 +   (0x90000, 0x9FFFD),
    2.47 +   (0xA0000, 0xAFFFD ),
    2.48 +   (0xB0000, 0xBFFFD ),
    2.49 +   (0xC0000, 0xCFFFD),
    2.50 +   (0xD0000, 0xDFFFD ),
    2.51 +   (0xE1000, 0xEFFFD),
    2.52 +   (0xF0000, 0xFFFFD ),
    2.53 +   (0x100000, 0x10FFFD)
    2.54 +]
    2.55 + 
    2.56 +def encode(c):
    2.57 +    retval = c
    2.58 +    i = ord(c)
    2.59 +    for low, high in escape_range:
    2.60 +        if i < low:
    2.61 +            break
    2.62 +        if i >= low and i <= high:
    2.63 +            retval = "".join(["%%%2X" % ord(o) for o in c.encode('utf-8')])
    2.64 +            break
    2.65 +    return retval
    2.66 +
    2.67 +
    2.68 +def iri2uri(uri):
    2.69 +    """Convert an IRI to a URI. Note that IRIs must be 
    2.70 +    passed in a unicode strings. That is, do not utf-8 encode
    2.71 +    the IRI before passing it into the function.""" 
    2.72 +    if isinstance(uri ,unicode):
    2.73 +        (scheme, authority, path, query, fragment) = urlparse.urlsplit(uri)
    2.74 +        authority = authority.encode('idna')
    2.75 +        # For each character in 'ucschar' or 'iprivate'
    2.76 +        #  1. encode as utf-8
    2.77 +        #  2. then %-encode each octet of that utf-8 
    2.78 +        uri = urlparse.urlunsplit((scheme, authority, path, query, fragment))
    2.79 +        uri = "".join([encode(c) for c in uri])
    2.80 +    return uri
    2.81 +        
    2.82 +if __name__ == "__main__":
    2.83 +    import unittest
    2.84 +
    2.85 +    class Test(unittest.TestCase):
    2.86 +
    2.87 +        def test_uris(self):
    2.88 +            """Test that URIs are invariant under the transformation."""
    2.89 +            invariant = [ 
    2.90 +                u"ftp://ftp.is.co.za/rfc/rfc1808.txt",
    2.91 +                u"http://www.ietf.org/rfc/rfc2396.txt",
    2.92 +                u"ldap://[2001:db8::7]/c=GB?objectClass?one",
    2.93 +                u"mailto:John.Doe@example.com",
    2.94 +                u"news:comp.infosystems.www.servers.unix",
    2.95 +                u"tel:+1-816-555-1212",
    2.96 +                u"telnet://192.0.2.16:80/",
    2.97 +                u"urn:oasis:names:specification:docbook:dtd:xml:4.1.2" ]
    2.98 +            for uri in invariant:
    2.99 +                self.assertEqual(uri, iri2uri(uri))
   2.100 +            
   2.101 +        def test_iri(self):
   2.102 +            """ Test that the right type of escaping is done for each part of the URI."""
   2.103 +            self.assertEqual("http://xn--o3h.com/%E2%98%84", iri2uri(u"http://\N{COMET}.com/\N{COMET}"))
   2.104 +            self.assertEqual("http://bitworking.org/?fred=%E2%98%84", iri2uri(u"http://bitworking.org/?fred=\N{COMET}"))
   2.105 +            self.assertEqual("http://bitworking.org/#%E2%98%84", iri2uri(u"http://bitworking.org/#\N{COMET}"))
   2.106 +            self.assertEqual("#%E2%98%84", iri2uri(u"#\N{COMET}"))
   2.107 +            self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}"))
   2.108 +            self.assertEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}")))
   2.109 +            self.assertNotEqual("/fred?bar=%E2%98%9A#%E2%98%84", iri2uri(u"/fred?bar=\N{BLACK LEFT POINTING INDEX}#\N{COMET}".encode('utf-8')))
   2.110 +
   2.111 +    unittest.main()
   2.112 +
   2.113 +    
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/mimeTypes.py	Sun Nov 29 14:28:14 2009 +0000
     3.3 @@ -0,0 +1,57 @@
     3.4 +"""
     3.5 +    Copyright (C) 2008 Benjamin O'Steen
     3.6 +
     3.7 +    This file is part of python-fedoracommons.
     3.8 +
     3.9 +    python-fedoracommons is free software: you can redistribute it and/or modify
    3.10 +    it under the terms of the GNU General Public License as published by
    3.11 +    the Free Software Foundation, either version 3 of the License, or
    3.12 +    (at your option) any later version.
    3.13 +
    3.14 +    python-fedoracommons is distributed in the hope that it will be useful,
    3.15 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
    3.16 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    3.17 +    GNU General Public License for more details.
    3.18 +
    3.19 +    You should have received a copy of the GNU General Public License
    3.20 +    along with python-fedoracommons.  If not, see <http://www.gnu.org/licenses/>.
    3.21 +"""
    3.22 +
    3.23 +__license__ = 'GPL http://www.gnu.org/licenses/gpl.txt'
    3.24 +__author__ = "Benjamin O'Steen <bosteen@gmail.com>"
    3.25 +__version__ = '0.1'
    3.26 +
    3.27 +class mimeTypes(object):
    3.28 +    def getDictionary(self):
    3.29 +        mimetype_to_extension = {}
    3.30 +        extension_to_mimetype = {}
    3.31 +        mimetype_to_extension['text/plain'] = 'txt'
    3.32 +        mimetype_to_extension['text/xml'] = 'xml'
    3.33 +        mimetype_to_extension['text/css'] = 'css'
    3.34 +        mimetype_to_extension['text/javascript'] = 'js'
    3.35 +        mimetype_to_extension['text/rtf'] = 'rtf'
    3.36 +        mimetype_to_extension['text/calendar'] = 'ics'
    3.37 +        mimetype_to_extension['application/msword'] = 'doc'
    3.38 +        mimetype_to_extension['application/msexcel'] = 'xls'
    3.39 +        mimetype_to_extension['application/x-msword'] = 'doc'
    3.40 +        mimetype_to_extension['application/vnd.ms-excel'] = 'xls'
    3.41 +        mimetype_to_extension['application/vnd.ms-powerpoint'] = 'ppt'
    3.42 +        mimetype_to_extension['application/pdf'] = 'pdf'
    3.43 +        mimetype_to_extension['text/comma-separated-values'] = 'csv'
    3.44 +        
    3.45 +        
    3.46 +        mimetype_to_extension['image/jpeg'] = 'jpg'
    3.47 +        mimetype_to_extension['image/gif'] = 'gif'
    3.48 +        mimetype_to_extension['image/jpg'] = 'jpg'
    3.49 +        mimetype_to_extension['image/tiff'] = 'tiff'
    3.50 +        mimetype_to_extension['image/png'] = 'png'
    3.51 +        
    3.52 +        # And hacky reverse lookups
    3.53 +        for mimetype in mimetype_to_extension:
    3.54 +            extension_to_mimetype[mimetype_to_extension[mimetype]] = mimetype
    3.55 +        
    3.56 +        mimetype_extension_mapping = {}
    3.57 +        mimetype_extension_mapping.update(mimetype_to_extension)
    3.58 +        mimetype_extension_mapping.update(extension_to_mimetype)
    3.59 +        
    3.60 +        return mimetype_extension_mapping
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/restful_lib.py	Sun Nov 29 14:28:14 2009 +0000
     4.3 @@ -0,0 +1,129 @@
     4.4 +"""
     4.5 +    Copyright (C) 2008 Benjamin O'Steen
     4.6 +
     4.7 +    This file is part of python-fedoracommons.
     4.8 +
     4.9 +    python-fedoracommons is free software: you can redistribute it and/or modify
    4.10 +    it under the terms of the GNU General Public License as published by
    4.11 +    the Free Software Foundation, either version 3 of the License, or
    4.12 +    (at your option) any later version.
    4.13 +
    4.14 +    python-fedoracommons is distributed in the hope that it will be useful,
    4.15 +    but WITHOUT ANY WARRANTY; without even the implied warranty of
    4.16 +    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    4.17 +    GNU General Public License for more details.
    4.18 +
    4.19 +    You should have received a copy of the GNU General Public License
    4.20 +    along with python-fedoracommons.  If not, see <http://www.gnu.org/licenses/>.
    4.21 +"""
    4.22 +
    4.23 +__license__ = 'GPL http://www.gnu.org/licenses/gpl.txt'
    4.24 +__author__ = "Benjamin O'Steen <bosteen@gmail.com>"
    4.25 +__version__ = '0.1'
    4.26 +
    4.27 +import httplib2
    4.28 +import urlparse
    4.29 +import urllib
    4.30 +import base64
    4.31 +from base64 import encodestring
    4.32 +
    4.33 +from mimeTypes import *
    4.34 +
    4.35 +import mimetypes
    4.36 +
    4.37 +from cStringIO import StringIO
    4.38 +
    4.39 +class Connection:
    4.40 +    def __init__(self, base_url, username=None, password=None):
    4.41 +        self.base_url = base_url
    4.42 +        self.username = username
    4.43 +        m = mimeTypes()
    4.44 +        self.mimetypes = m.getDictionary()
    4.45 +        
    4.46 +        self.url = urlparse.urlparse(base_url)
    4.47 +        
    4.48 +        (scheme, netloc, path, query, fragment) = urlparse.urlsplit(base_url)
    4.49 +            
    4.50 +        self.scheme = scheme
    4.51 +        self.host = netloc
    4.52 +        self.path = path
    4.53 +        
    4.54 +        # Create Http class with support for Digest HTTP Authentication, if necessary
    4.55 +        self.h = httplib2.Http(".cache")
    4.56 +        self.h.follow_all_redirects = True
    4.57 +        if username and password:
    4.58 +            self.h.add_credentials(username, password)
    4.59 +    
    4.60 +    def request_get(self, resource, args = None, headers={}):
    4.61 +        return self.request(resource, "get", args, headers=headers)
    4.62 +        
    4.63 +    def request_delete(self, resource, args = None, headers={}):
    4.64 +        return self.request(resource, "delete", args, headers=headers)
    4.65 +        
    4.66 +    def request_head(self, resource, args = None, headers={}):
    4.67 +        return self.request(resource, "head", args, headers=headers)
    4.68 +        
    4.69 +    def request_post(self, resource, args = None, body = None, filename=None, headers={}):
    4.70 +        return self.request(resource, "post", args , body = body, filename=filename, headers=headers)
    4.71 +        
    4.72 +    def request_put(self, resource, args = None, body = None, filename=None, headers={}):
    4.73 +        return self.request(resource, "put", args , body = body, filename=filename, headers=headers)
    4.74 +        
    4.75 +    def get_content_type(self, filename):
    4.76 +        extension = filename.split('.')[-1]
    4.77 +        guessed_mimetype = self.mimetypes.get(extension, mimetypes.guess_type(filename)[0])
    4.78 +        return guessed_mimetype or 'application/octet-stream'
    4.79 +        
    4.80 +    def request(self, resource, method = "get", args = None, body = None, filename=None, headers={}):
    4.81 +        params = None
    4.82 +        path = resource
    4.83 +        headers['User-Agent'] = 'Basic Agent'
    4.84 +        
    4.85 +        BOUNDARY = u'00hoYUXOnLD5RQ8SKGYVgLLt64jejnMwtO7q8XE1'
    4.86 +        CRLF = u'\r\n'
    4.87 +        
    4.88 +        if filename and body:
    4.89 +            #fn = open(filename ,'r')
    4.90 +            #chunks = fn.read()
    4.91 +            #fn.close()
    4.92 +            
    4.93 +            # Attempt to find the Mimetype
    4.94 +            content_type = self.get_content_type(filename)
    4.95 +            headers['Content-Type']='multipart/form-data; boundary='+BOUNDARY
    4.96 +            encode_string = StringIO()
    4.97 +            encode_string.write(CRLF)
    4.98 +            encode_string.write(u'--' + BOUNDARY + CRLF)
    4.99 +            encode_string.write(u'Content-Disposition: form-data; name="file"; filename="%s"' % filename)
   4.100 +            encode_string.write(CRLF)
   4.101 +            encode_string.write(u'Content-Type: %s' % content_type + CRLF)
   4.102 +            encode_string.write(CRLF)
   4.103 +            encode_string.write(body)
   4.104 +            encode_string.write(CRLF)
   4.105 +            encode_string.write(u'--' + BOUNDARY + u'--' + CRLF)
   4.106 +            
   4.107 +            body = encode_string.getvalue()
   4.108 +            headers['Content-Length'] = str(len(body))
   4.109 +        elif body:
   4.110 +            if not headers.get('Content-Type', None):
   4.111 +                headers['Content-Type']='text/xml'
   4.112 +            headers['Content-Length'] = str(len(body))        
   4.113 +        else: 
   4.114 +            headers['Content-Type']='text/xml'
   4.115 +            
   4.116 +        if args:
   4.117 +            path += u"?" + urllib.urlencode(args)
   4.118 +            
   4.119 +        request_path = []
   4.120 +        if self.path != "/":
   4.121 +            if self.path.endswith('/'):
   4.122 +                request_path.append(self.path[:-1])
   4.123 +            else:
   4.124 +                request_path.append(self.path)
   4.125 +            if path.startswith('/'):
   4.126 +                request_path.append(path[1:])
   4.127 +            else:
   4.128 +                request_path.append(path)
   4.129 +        
   4.130 +        resp, content = self.h.request(u"%s://%s%s" % (self.scheme, self.host, u'/'.join(request_path)), method.upper(), body=body, headers=headers )
   4.131 +        
   4.132 +        return {u'headers':resp, u'body':content.decode('UTF-8')}
     5.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     5.2 +++ b/talis.py	Sun Nov 29 14:28:14 2009 +0000
     5.3 @@ -0,0 +1,262 @@
     5.4 +import urllib2
     5.5 +
     5.6 +from urllib2 import urlparse
     5.7 +
     5.8 +from restful_lib import Connection
     5.9 +
    5.10 +from gae_restful_lib import GAE_Connection
    5.11 +
    5.12 +from datetime import datetime
    5.13 +
    5.14 +from StringIO import StringIO
    5.15 +
    5.16 +from xml.etree import ElementTree as ET
    5.17 +
    5.18 +SPARQL_ENDPOINT = "/services/sparql"
    5.19 +META_ENDPOINT = "/meta"
    5.20 +CONTENT_ENDPOINT = "/items"
    5.21 +JOB_REQUESTS = "/jobs"
    5.22 +SNAPSHOTS = "/snapshots"
    5.23 +SNAPSHOT_TEMPLATE = "/snapshots/%s"
    5.24 +
    5.25 +RESET_STORE_TEMPLATE = u"""<rdf:RDF
    5.26 +    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    5.27 +    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    5.28 +    xmlns:bf="http://schemas.talis.com/2006/bigfoot/configuration#" > 
    5.29 +  <bf:JobRequest>
    5.30 +    <rdfs:label>%s</rdfs:label>
    5.31 +    <bf:jobType rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#ResetDataJob"/>
    5.32 +    <bf:startTime>%sZ</bf:startTime>
    5.33 +  </bf:JobRequest>
    5.34 + </rdf:RDF>"""
    5.35 +
    5.36 +SNAPSHOT_STORE_TEMPLATE = """<rdf:RDF
    5.37 +    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    5.38 +    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    5.39 +    xmlns:bf="http://schemas.talis.com/2006/bigfoot/configuration#" > 
    5.40 +  <bf:JobRequest>
    5.41 +    <rdfs:label>%s</rdfs:label>
    5.42 +    <bf:jobType rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#SnapshotJob"/>
    5.43 +    <bf:startTime>%sZ</bf:startTime>
    5.44 +  </bf:JobRequest>
    5.45 + </rdf:RDF>"""
    5.46 +
    5.47 +SNAPSHOT_RESTORE_TEMPLATE = """<rdf:RDF
    5.48 +    xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
    5.49 +    xmlns:rdfs="http://www.w3.org/2000/01/rdf-schema#"
    5.50 +    xmlns:bf="http://schemas.talis.com/2006/bigfoot/configuration#" > 
    5.51 +  <bf:JobRequest>
    5.52 +    <rdfs:label>%s</rdfs:label>
    5.53 +    <bf:jobType rdf:resource="http://schemas.talis.com/2006/bigfoot/configuration#RestoreJob"/>
    5.54 +    <bf:snapshotUri rdf:resource="%s" />
    5.55 +    <bf:startTime>%sZ</bf:startTime>
    5.56 +  </bf:JobRequest>
    5.57 + </rdf:RDF>"""
    5.58 +
    5.59 +class RDFFormatException(Exception):
    5.60 +    def __init__(self, value):
    5.61 +        self.value = value
    5.62 +    def __str__(self):
    5.63 +        return repr(self.value)
    5.64 +
    5.65 +class Store():
    5.66 +    def __init__(self, base_store_url, username=None, password=None):
    5.67 +        """ Base URL for the store should be pretty self-explanatory. E.g. something like
    5.68 +            "http://api.talis.com/stores/store_name"
    5.69 +            Only needs to enter the username/password if this class is going to tinker
    5.70 +            with things."""
    5.71 +        if base_store_url.endswith('/'):
    5.72 +            base_store_url = base_store_url[:-1]
    5.73 +
    5.74 +        self.base_store_url = base_store_url
    5.75 +        # Split the given URL
    5.76 +        if base_store_url:
    5.77 +            self.conn = Connection(base_store_url, username=username, password=password)
    5.78 +
    5.79 +    def does_snapshot_exist(self, snapshot_filename):
    5.80 +        # Test to see if snapshot exists:
    5.81 +        snapshot_path = SNAPSHOT_TEMPLATE % snapshot_filename
    5.82 +        
    5.83 +        response = self.conn.request(snapshot_path, method = "HEAD")
    5.84 +        
    5.85 +        if response.get('headers') and response.get('headers').get('status'):
    5.86 +            status = response.get('headers').get('status')
    5.87 +        
    5.88 +            if status in ['200', '204']:
    5.89 +                return True
    5.90 +            elif status.startswith('4'):
    5.91 +                return False
    5.92 +            # else: raise Error?
    5.93 +
    5.94 +        return False
    5.95 +
    5.96 +    def schedule_reset_data(self, label, at_time=None):
    5.97 +        """Will request that the store is emptied, and label the request. 
    5.98 +           If a time is given as an ISO8601 formatted string, this will be 
    5.99 +           the scheduled time for the snapshot. Otherwise, it will use the current time."""
   5.100 +        if not at_time:
   5.101 +            at_time=datetime.utcnow().isoformat().split('.')[0]
   5.102 +        
   5.103 +        snapshot_request = RESET_STORE_TEMPLATE % (label, at_time)
   5.104 +        
   5.105 +        return self.conn.request_post(JOB_REQUESTS, body = snapshot_request, headers={'Content-Type':'application/rdf+xml'})
   5.106 +
   5.107 +    def schedule_snapshot_data(self, label, at_time=None):
   5.108 +        """Will request a snapshot be made of the store. 
   5.109 +           If a time is given as an ISO8601 formatted string, this will be 
   5.110 +           the scheduled time for the snapshot. Otherwise, it will use the current time."""
   5.111 +        if not at_time:
   5.112 +            at_time=datetime.utcnow().isoformat().split('.')[0]
   5.113 +        
   5.114 +        snapshot_request = SNAPSHOT_STORE_TEMPLATE % (label, at_time)
   5.115 +        
   5.116 +        return self.conn.request_post(JOB_REQUESTS, body = snapshot_request, headers={'Content-Type':'application/rdf+xml'})
   5.117 +        
   5.118 +    def schedule_snapshot_restore(self, label, snapshot_filename, at_time=None):
   5.119 +        """Will request that the store is restored from a snapshot. If a time is given as
   5.120 +           an ISO8601 formatted string, this will be the scheduled time for
   5.121 +           the recovery. Otherwise, it will use the current time."""
   5.122 +        if not at_time:
   5.123 +            at_time=datetime.utcnow().isoformat().split('.')[0]
   5.124 +        
   5.125 +        # Test to see if snapshot exists:
   5.126 +        snapshot_path = SNAPSHOT_TEMPLATE % snapshot_filename
   5.127 +        
   5.128 +        if self.does_snapshot_exist(snapshot_filename):
   5.129 +            snapshot_uri = "%s%s" % (self.base_store_url, snapshot_path)
   5.130 +            snapshot_request = SNAPSHOT_RESTORE_TEMPLATE % (label, snapshot_uri, at_time)    
   5.131 +            return self.conn.request_post(JOB_REQUESTS, body = snapshot_request, headers={'Content-Type':'application/rdf+xml'})
   5.132 +            
   5.133 +    def submit_rdfxml(self, rdf_text):
   5.134 +        """Puts the given RDF/XML into the Talis Store"""
   5.135 +        return self._put_rdf(rdf_text, mimetype="application/rdf+xml")
   5.136 +        
   5.137 +    def _put_rdf(self, rdf_text, mimetype="application/rdf+xml"):
   5.138 +        """Placeholder for allowing other serialisation types to be put into a
   5.139 +           Talis store, whether the conversion takes place here, or if the Talis
   5.140 +           store starts to accept other formats."""
   5.141 +        if rdf_text:
   5.142 +            request_headers = {}
   5.143 +            if mimetype not in ['application/rdf+xml']:
   5.144 +                raise RDFFormatException("%s is not an allowed RDF serialisation format" % mimetype)
   5.145 +            request_headers['Content-Type'] = mimetype
   5.146 +            return self.conn.request_post(META_ENDPOINT, body=rdf_text, headers=request_headers)        
   5.147 +                 
   5.148 +    def _query_sparql_service(self, query, args={}):
   5.149 +        """Low-level SPARQL query - returns the message and response headers from the server.
   5.150 +           You may be looking for Store.sparql instead of this."""
   5.151 +        passed_args = {'query':query}
   5.152 +        passed_args.update(args)
   5.153 +        return self.conn.request_get(SPARQL_ENDPOINT, args=passed_args, headers={'Content-type':'application/x-www-form-urlencoded'})
   5.154 +        
   5.155 +    def _query_search_service(self, query, args={}):
   5.156 +        """Low-level content box query - returns the message and response headers from the server.
   5.157 +           You may be looking for Store.search instead of this."""
   5.158 +           
   5.159 +        passed_args = {'query':query}
   5.160 +        passed_args.update(args)
   5.161 +        
   5.162 +        return self.conn.request_get(CONTENT_ENDPOINT, args=passed_args, headers={'Content-type':'application/x-www-form-urlencoded'} )
   5.163 +        
   5.164 +    def _list_snapshots(self, passed_args={}):
   5.165 +        return self.conn.request_get(SNAPSHOTS, args=passed_args, headers={}) 
   5.166 +        
   5.167 +##############################################################################
   5.168 +# Convenience Functions
   5.169 +##############################################################################
   5.170 +
   5.171 +    def submit_rdfxml_from_url(self, url_to_file, headers={"Accept":"application/rdf+xml"}):
   5.172 +        """Convenience method - downloads the file from a given url, and then pushes that
   5.173 +           into the meta store. Currently, it doesn't put it through a parse-> reserialise
   5.174 +           step, so that it could handle more than rdf/xml on the way it but it is a
   5.175 +           future possibility."""
   5.176 +        import_rdf_connection = Connection(url_to_file)
   5.177 +        response = import_rdf_connection.request_get("", headers=headers)
   5.178 +        
   5.179 +        if response.get('headers') and response.get('headers').get('status') in ['200', '204']:
   5.180 +            request_headers = {}
   5.181 +            
   5.182 +            # Lowercase all response header fields, to make matching easier. 
   5.183 +            # According to HTTP spec, they should be case-insensitive
   5.184 +            response_headers = response['headers']
   5.185 +            for header in response_headers:
   5.186 +                response_headers[header.lower()] = response_headers[header]
   5.187 +                
   5.188 +            # Set the body content
   5.189 +            body = response.get('body').encode('UTF-8')
   5.190 +            
   5.191 +            # Get the response mimetype
   5.192 +            rdf_type = response_headers.get('content-type', None)
   5.193 +            
   5.194 +            return self._put_rdf(body, mimetype=rdf_type)
   5.195 +            
   5.196 +    def sparql(self, query, args={}):
   5.197 +        """Performs a SPARQL query and simply returns the body of the response if successful
   5.198 +           - if there is an issue, such as a code 404 or 500, this method will return False. 
   5.199 +           
   5.200 +           Use the _query_sparql_service method to get hold of
   5.201 +           the complete response in this case."""
   5.202 +        response = self._query_sparql_service(query, args)
   5.203 +        headers = response.get('headers')
   5.204 +        
   5.205 +        status = headers.get('status', headers.get('Status'))
   5.206 +        
   5.207 +        if status in ['200', 200, '204', 204]:
   5.208 +            return response.get('body').encode('UTF-8')
   5.209 +        else:
   5.210 +            return False
   5.211 +
   5.212 +    def search(self, query, args={}):
   5.213 +        """Performs a search query and simply returns the body of the response if successful
   5.214 +           - if there is an issue, such as a code 404 or 500, this method will return False. 
   5.215 +           
   5.216 +           Use the _query_search_service method to get hold of
   5.217 +           the complete response in this case."""
   5.218 +        response = self._query_search_service(query, args)
   5.219 +        headers = response.get('headers')
   5.220 +        
   5.221 +        status = headers.get('status', headers.get('Status'))
   5.222 +        
   5.223 +        if status in ['200', 200, '204', 204]:
   5.224 +            parsed_atom = Atom_Search_Results(response.get('body').encode('UTF-8'))
   5.225 +            return parsed_atom.get_item_list()
   5.226 +        else:
   5.227 +            return False
   5.228 +            
   5.229 +class Item():
   5.230 +    def __init__(self):
   5.231 +        self.title = None
   5.232 +        self.link = None
   5.233 +
   5.234 +class Atom_Search_Results():
   5.235 +    def __init__(self, atom_text):
   5.236 +        self.load_atom_search(atom_text)
   5.237 +    
   5.238 +    def load_atom_search(self, atom_text):
   5.239 +        self.atom = ET.fromstring(atom_text)
   5.240 +    
   5.241 +    def get_item_list(self):
   5.242 +        if self.atom:
   5.243 +            items = []
   5.244 +            for item in self.atom.findall('{http://purl.org/rss/1.0/}item'):
   5.245 +                item_fields = Item()
   5.246 +                item_fields.title = item.find('{http://purl.org/rss/1.0/}title').text
   5.247 +                item_fields.link = item.find('{http://purl.org/rss/1.0/}link').text
   5.248 +                items.append(item_fields)
   5.249 +                
   5.250 +            return items
   5.251 +            
   5.252 +class GAE_Store(Store):
   5.253 +    def __init__(self, base_store_url, username=None, password=None):
   5.254 +        """ Base URL for the store should be pretty self-explanatory. E.g. something like
   5.255 +            "http://api.talis.com/stores/store_name"
   5.256 +            The username and password will not do anything, until the Google app engine's
   5.257 +            fetch library handles authentication, if ever."""
   5.258 +        if base_store_url.endswith('/'):
   5.259 +            base_store_url = base_store_url[:-1]
   5.260 +
   5.261 +        self.base_store_url = base_store_url
   5.262 +        # Split the given URL
   5.263 +        if base_store_url:
   5.264 +            self.conn = GAE_Connection(base_store_url, username, password)
   5.265 +