summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorkientzle <kientzle@5914aa95-5b3a-0410-a3b5-7b719e7fe9b2>2008-03-18 18:41:09 +0000
committerkientzle <kientzle@5914aa95-5b3a-0410-a3b5-7b719e7fe9b2>2008-03-18 18:41:09 +0000
commite1c531970715bcb496299e2162c603cadcbc0a5f (patch)
tree649cd4a86f9dfcecae6e7d5b510622ee3a377df0
parenta9aa9e93ab46f067bfe912aae3612a17f2743720 (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--README21
-rw-r--r--metaweb-py/metaweb.py242
2 files changed, 263 insertions, 0 deletions
diff --git a/README b/README
new file mode 100644
index 0000000..ca08382
--- /dev/null
+++ b/README
@@ -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