diff options
| author | kientzle <kientzle@5914aa95-5b3a-0410-a3b5-7b719e7fe9b2> | 2008-03-18 18:41:09 +0000 |
|---|---|---|
| committer | kientzle <kientzle@5914aa95-5b3a-0410-a3b5-7b719e7fe9b2> | 2008-03-18 18:41:09 +0000 |
| commit | e1c531970715bcb496299e2162c603cadcbc0a5f (patch) | |
| tree | 649cd4a86f9dfcecae6e7d5b510622ee3a377df0 | |
| parent | a9aa9e93ab46f067bfe912aae3612a17f2743720 (diff) | |
Copy David Flanagan's "metaweb.py" to a separate directory.
Include a top-level README documenting the two different
libraries here.
git-svn-id: http://freebase-python.googlecode.com/svn/trunk@36 5914aa95-5b3a-0410-a3b5-7b719e7fe9b2
| -rw-r--r-- | README | 21 | ||||
| -rw-r--r-- | metaweb-py/metaweb.py | 242 |
2 files changed, 263 insertions, 0 deletions
@@ -0,0 +1,21 @@ +These are two independent python libraries for use in +accessing Freebase services from Python: + +* metaweb-py is the older and simpler of the two. It's an improved + version of the metaweb.py module originally developed by David + Flanagan as part of the original Freebase API documentation. + + Pros: Single file, simpler. + +* freebase-api is a more ambitious library that is derived from + code developed by Nick Thompson for accessing freebase services. + + Pros: Does more. + +Both are available under a simple 2-clause BSD-style license that +should allow you to reuse this code in both open-source and +commercial projects. + +If you have suggestions, bug fixes, or feature requests for either +of these, please let one of the maintainers know or you can +discuss it on the Freebase developers list. diff --git a/metaweb-py/metaweb.py b/metaweb-py/metaweb.py new file mode 100644 index 0000000..65624e7 --- /dev/null +++ b/metaweb-py/metaweb.py @@ -0,0 +1,242 @@ +#======================================================================== +# Copyright (c) 2007, Metaweb Technologies, Inc. +# All rights reserved. +# +# Redistribution and use in source and binary forms, with or without +# modification, are permitted provided that the following conditions +# are met: +# * Redistributions of source code must retain the above copyright +# notice, this list of conditions and the following disclaimer. +# * Redistributions in binary form must reproduce the above +# copyright notice, this list of conditions and the following +# disclaimer in the documentation and/or other materials provided +# with the distribution. +# +# THIS SOFTWARE IS PROVIDED BY METAWEB TECHNOLOGIES ``AS IS'' AND ANY +# EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +# IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +# PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL METAWEB TECHNOLOGIES BE +# LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR +# CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF +# SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR +# BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, +# WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE +# OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN +# IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. +# ======================================================================== +# +# This is the full "metaweb.py" module from the Metaweb API documentation +# +# In the documentation, each function is presented as a separate +# example. This is the whole file. +# +# If you find any errors or have suggestions for improving this module, +# send them to the Freebase developers mailing list: developers@freebase.com +# You can subscribe to the mailing list at http://lists.freebase.com/ +# + +import urllib # URL encoding +import urllib2 # Higher-level URL content fetching +import simplejson # JSON serialization and parsing + +# +# When experimenting, use the sandbox.freebase.com service. +# Every Monday, sandbox.freebase.com is erased and it is updated +# with a fresh copy of data from www.freebase.com. This makes +# it an ideal place to experiment. +# +host = 'sandbox.freebase.com' # The Metaweb host +readservice = '/api/service/mqlread' # Path to mqlread service +loginservice = '/api/account/login' # Path to login service +writeservice = '/api/service/mqlwrite' # Path to mqlwrite service +uploadservice = '/api/service/upload' # Path to upload service + +# If anything goes wrong when talking to a Metaweb service, we raise MQLError. +class MQLError(Exception): + def __init__(self, value): # This is the exception constructor method + self.value = value + def __str__(self): # Convert error object to a string + return repr(self.value) + +# Submit the MQL query q and return the result as a Python object. +# If authentication credentials are supplied, use them in a cookie. +# Raises MQLError if the query was invalid. Raises urllib2.HTTPError if +# mqlread returns an HTTP status code other than 200 (which should not happen). +def read(q, credentials=None): + # Put the query in an envelope + env = {'qname':{'query':q}} + # JSON serialize and URL encode the envelope and the query parameter + args = urllib.urlencode({'queries':simplejson.dumps(env)}) + # Build the URL and create a Request object for it + url = 'http://%s%s?%s' % (host, readservice, args) + req = urllib2.Request(url) + + # Send our authentication credentials, if any, as a cookie. + # The need for mqlread authentication is a temporary restriction. + if credentials: + req.add_header('Cookie', credentials) + + # Now upen the URL and and parse its JSON content + f = urllib2.urlopen(req) # Open the URL + response = simplejson.load(f) # Parse JSON response to an object + inner = response['qname'] # Open outer envelope; get inner envelope + + # If anything was wrong with the invocation, mqlread will return an HTTP + # error, and the code above with raise urllib2.HTTPError. + # If anything was wrong with the query, we won't get an HTTP error, but + # will get an error status code in the response envelope. In this case + # we raise our own MQLError exception. + if not inner['code'].startswith('/api/status/ok'): + error = inner['messages'][0] + raise MQLError('%s: %s' % (error['code'], error['message'])) + + # If there was no error, then just return the result from the envelope + return inner['result']; + +# Submit the MQL query q and return the result as a Python object +# This function behaves like read() above, but uses cursors so that +# it works even for very large result sets +def readall(q, credentials=None): + # This is the start of the mqlread URL. + # We just need to append the envelope to it + urlprefix = 'http://%s%s?queries=' % (host, readservice) + + # The query and most of the envelope are constant. We just need to append + # the encoded cursor value and some closing braces to this prefix string + jsonq = simplejson.dumps(q); + envelopeprefix = urllib.quote_plus('{"q0":{"query":'+jsonq+',"cursor":') + + cursor = 'true' # This is the initial value of the cursor + results = [] # We accumulate results in this array + + # Loop until mqlread tells us there are no more results + while cursor: + # append the cursor and the closing braces to the envelope + envelope = envelopeprefix + urllib.quote_plus(cursor + '}}') + # append the envelope to the URL + url = urlprefix + envelope + + # Begin an HTTP request for the URL + req = urllib2.Request(url) + + # Send our authentication credentials, if any, as a cookie. + # The need for mqlread authentication is a temporary restriction. + if credentials: + req.add_header('Cookie', credentials) + + # Read and parse the URL contents + f = urllib2.urlopen(req) # Open URL + response = simplejson.load(f) # Parse JSON response + inner = response['q0'] # Get inner envelope from outer + + # Raise a MQLError if there were errors + if not inner['code'].startswith('/api/status/ok'): + error = inner['messages'][0] + raise MQLError('%s: %s' % (error['code'], error['message'])) + + # Append this batch of results to the main array of results. + results.extend(inner['result']); + + # Finally, get the new value of the cursor for the next iteration + cursor = inner['cursor'] + if cursor: # If it is not false, put it + cursor = '"' + cursor + '"' # in quotes as a JSON string + + # Now that we're done with the loop, return the results array + return results + +# Submit the specified username and password to the Metaweb login service. +# Return opaque authentication credentials on success. +# Raise MQLError on failure. +def login(username, password): + # Establish a connection to the server and make a request. + # Note that we use the low-level httplib library instead of urllib2. + # This allows us to manage cookies explicitly. + conn = httplib.HTTPConnection(host) + conn.request('POST', # POST the request + loginservice, # The URL path /api/account/login + # The body of the request: encoded username/password + urllib.urlencode({'username':username, 'password':password}), + # This header specifies how the body of the post is encoded. + {'Content-type': 'application/x-www-form-urlencoded'}) + + # Get the response from the server + response = conn.getresponse() + + if response.status == 200: # We get HTTP 200 OK even if login fails + # Parse response body and raise a MQLError if login failed + body = simplejson.loads(response.read()) + if not body['code'].startswith('/api/status/ok'): + error = body['messages'][0] + raise MQLError('%s: %s' % (error['code'], error['message'])) + + # Otherwise return cookies to serve as authentication credentials. + # The set-cookie header holds one or more cookie specifications, + # separated by commas. Each specification is a name, an equal + # sign, a value, and one or more trailing clauses that consist + # of a semicolon and some metadata. We don't care about the + # metadata. We just want to return a comma-separated list of + # name=value pairs. + cookies = response.getheader('set-cookie').split(',') + return ';'.join([c[0:c.index(';')] for c in cookies]) + else: # This should never happen + raise MQLError('HTTP Error: %d %s' % (response.status,response.reason)) + + +# Submit the MQL write q and return the result as a Python object. +# Authentication credentials are required, obtained from login() +# Raises MQLError if the query was invalid. Raises urllib2.HTTPError if +# mqlwrite returns an HTTP status code other than 200 +def write(query, credentials): + # We're requesting this URL + req = urllib2.Request('http://%s%s' % (host, writeservice)) + # Send our authentication credentials as a cookie + req.add_header('Cookie', credentials) + # This custom header is required and guards against XSS attacks + req.add_header('X-Metaweb-Request', 'True') + # The body of the POST request is encoded URL parameters + req.add_header('Content-type', 'application/x-www-form-urlencoded') + # Wrap the query object in a query envelope + envelope = {'qname': {'query': query}} + # JSON encode the envelope + encoded = simplejson.dumps(envelope) + print encoded + # Use the encoded envelope as the value of the q parameter in the body + # of the request. Specifying a body automatically makes this a POST. + req.add_data(urllib.urlencode({'queries':encoded})) + + # Now do the POST + f = urllib2.urlopen(req) + response = simplejson.load(f) # Parse HTTP response as JSON + print response + inner = response['qname'] # Open outer envelope; get inner envelope + + # If anything was wrong with the invocation, mqlwrite will return an HTTP + # error, and the code above with raise urllib2.HTTPError. + # If anything was wrong with the query, we will get an error status code + # in the response envelope. + # we raise our own MQLError exception. + if not inner['code'].startswith('/api/status/ok'): + error = inner['messages'][0] + raise MQLError('%s: %s' % (error['code'], error['message'])) + + # If there was no error, then just return the result from the envelope + return inner['result'] + +# Upload the specified content (and give it the specified type). +# Return the guid of the /type/content object that represents it. +# The returned guid can be used to retrieve the content with /api/trans/raw. +def upload(content, type, credentials): + # This is the URL we POST content to + url = 'http://%s%s'%(host,uploadservice) + # Build the HTTP request + req = urllib2.Request(url, content) # URL and content to POST + req.add_header('Content-Type', type) # Content type header + req.add_header('Cookie', credentials) # Authentication header + req.add_header('X-Metaweb-Request', 'True') # Guard against XSS attacks + f = urllib2.urlopen(req) # POST the request + response = simplejson.load(f) # Parse the response + if not response['code'].startswith('/api/status/ok'): + error = response['messages'][0] + raise MQLError('%s: %s' % (error['code'], error['message'])) + return response['result']['id'] # Extract and return content id |
