Pastie now auto-senses if line-wrap is a bad or good idea. Feedback?
## mark a section (Learn more)
#!/usr/bin/python # # Download each Flickr photo's information # to <PHOTOID>.XML in the current working directory. # Files already created will be skipped on subsequent runs. # # # The files you get will contain data like: # # <?xml version="1.0" encoding="utf-8" ?> # <rsp stat="ok"> # <photo id="xxxxxxxxxx" secret="xxxxxxxxxx" server="xxxx" farm="x" dateuploaded="1186256580" isfavorite="0" license="1" rotation="0" originalsecret="989af4e697" originalformat="jpg" views="2"> # <owner nsid="xxxxxxxx@N00" username="your username" realname="your realname" location="somewhere" /> # <title>Photo Title</title> # <description>Photo Description</description> # <visibility ispublic="0" isfriend="1" isfamily="1" /> # <dates posted="1186256580" taken="2007-08-04 15:41:26" takengranularity="0" lastupdate="1186256660" /> # <permissions permcomment="3" permaddmeta="1" /> # <editability cancomment="1" canaddmeta="1" /> # <comments>0</comments> # <notes /> # <tags> # <tag id="xxxxxxx-xxxxxxxxx-xxxx" author="xxxxxxxx2@N00" raw="yourtag" machine_tag="0">yourtag</tag> # <tag id="xxxxxxx-xxxxxxxxx-xxxx" author="xxxxxxxx2@N00" raw="yourothertag" machine_tag="0">yourothertag</tag> # </tags> # <urls> # <url type="photopage">http://www.flickr.com/photos/yourusername/xxxxxxxxxx/</url> # </urls> # </photo> # </rsp> # # Main loop copyright Dave Findlay 2007. Do with it what you wish. # See "Flickr API Implementation" section below for its copyright and licensing info. # def main(argv): # flickr auth information: flickrUsername = "YOUR_USERNAME" # you need to apply for a Flickr API key at: # http://www.flickr.com/services/api/keys/apply/ # Once approved you'll get your API key and shared secret and # can insert them below. flickrAPIKey = "YOUR_API_KEY" flickrSecret = "YOUR_SECRET" # we need a browser the first time so that the user can # authorize our access to their photos on Flickr browserToUse = "/Applications/Firefox.app/Contents/MacOS/firefox" print "Connecting to Flickr..." fapi = FlickrAPI(flickrAPIKey, flickrSecret) # get token, including asking user's permission if necessary token = fapi.getToken(browser=browserToUse, perms="read") rsp = fapi.people_findByUsername(api_key=flickrAPIKey, username=flickrUsername) fapi.testFailure(rsp) userid = rsp.user[0]["id"] print "Connected." # # Now walk the photo ids in chronological date-taken order, # retrieving the photos in smallish chunks so as not to # annoy the server too much # cpage = 1 while 1: rsp = fapi.photos_search(api_key=flickrAPIKey, auth_token=token, user_id=userid, per_page="100", page=str(cpage), sort="date-taken-asc") fapi.testFailure(rsp) npages = int(rsp.photos[0]["pages"]) for i in range(len(rsp.photos[0].photo)): photo_id = rsp.photos[0].photo[i]["id"] + ".xml" if os.path.isfile(photo_id): print "Already got info for",photo_id, " \r", else: print "Getting data for", photo_id," " photorsp = fapi.photos_getInfo(api_key=flickrAPIKey, auth_token=token, photo_id=str(photo_id)) fapi.testFailure(photorsp) # write the photo's XML data to a file file = open(photo_id, "w") file.write(photorsp.xml) file.close cpage += 1 if cpage > npages: break print "Done." return 0 # # Flickr API implementation # # Inspired largely by Michele Campeotto's flickrclient and Aaron Swartz' # xmltramp... but I wanted to get a better idea of how python worked in # those regards, so I mostly worked those components out for myself. # # http://micampe.it/things/flickrclient # http://www.aaronsw.com/2002/xmltramp/ # # Release 1: initial release # Release 2: added upload functionality # Release 3: code cleanup, convert to doc strings # Release 4: better permission support # Release 5: converted into fuller-featured "flickrapi" # Release 6: fix upload sig bug (thanks Deepak Jois), encode test output # Release 7: fix path construction, Manish Rai Jain's improvements, exceptions # # Work by (or inspired by) Manish Rai Jain <manishrjain@gmail.com>: # # improved error reporting, proper multipart MIME boundary creation, # use of urllib2 to allow uploads through a proxy, upload accepts # raw data as well as a filename # # Copyright 2005 Brian "Beej Jorgensen" Hall <beej@beej.us> # # This work is licensed under the Creative Commons # Attribution License. To view a copy of this license, # visit http://creativecommons.org/licenses/by/2.5/ or send # a letter to Creative Commons, 543 Howard Street, 5th # Floor, San Francisco, California, 94105, USA. # # This license says that I must be credited for any derivative works. # You do not need to credit me to simply use the FlickrAPI classes in # your Python scripts--you only need to credit me if you're taking this # FlickrAPI class and modifying it or redistributing it. # # Previous versions of this API were granted to the public domain. # You're free to use those as you please. # # Beej Jorgensen, Maintainer, November 2005 # beej@beej.us # import sys import md5 import string import urllib import urllib2 import mimetools import httplib import os.path import xml.dom.minidom import re ######################################################################## # Exceptions ######################################################################## class UploadException(Exception): pass ######################################################################## # XML functionality ######################################################################## #----------------------------------------------------------------------- class XMLNode: """XMLNode -- generic class for holding an XML node xmlStr = \"\"\"<xml foo="32"> <name bar="10">Name0</name> <name bar="11" baz="12">Name1</name> </xml>\"\"\" f = XMLNode.parseXML(xmlStr) print f.elementName # xml print f['foo'] # 32 print f.name # [<name XMLNode>, <name XMLNode>] print f.name[0].elementName # name print f.name[0]["bar"] # 10 print f.name[0].elementText # Name0 print f.name[1].elementName # name print f.name[1]["bar"] # 11 print f.name[1]["baz"] # 12 """ def __init__(self): """Construct an empty XML node.""" self.elementName="" self.elementText="" self.attrib={} self.xml="" def __setitem__(self, key, item): """Store a node's attribute in the attrib hash.""" self.attrib[key] = item def __getitem__(self, key): """Retrieve a node's attribute from the attrib hash.""" return self.attrib[key] #----------------------------------------------------------------------- @classmethod def parseXML(cls, xmlStr, storeXML=True): """Convert an XML string into a nice instance tree of XMLNodes. xmlStr -- the XML to parse storeXML -- if True, stores the XML string in the root XMLNode.xml """ def __parseXMLElement(element, thisNode): """Recursive call to process this XMLNode.""" thisNode.elementName = element.nodeName #print element.nodeName # add element attributes as attributes to this node for i in range(element.attributes.length): an = element.attributes.item(i) thisNode[an.name] = an.nodeValue for a in element.childNodes: if a.nodeType == xml.dom.Node.ELEMENT_NODE: child = XMLNode() try: list = getattr(thisNode, a.nodeName) except AttributeError: setattr(thisNode, a.nodeName, []) # add the child node as an attrib to this node list = getattr(thisNode, a.nodeName); #print "appending child: %s to %s" % (a.nodeName, thisNode.elementName) list.append(child); __parseXMLElement(a, child) elif a.nodeType == xml.dom.Node.TEXT_NODE: thisNode.elementText += a.nodeValue return thisNode dom = xml.dom.minidom.parseString(xmlStr) # get the root rootNode = XMLNode() if storeXML: rootNode.xml = xmlStr return __parseXMLElement(dom.firstChild, rootNode) ######################################################################## # Flickr functionality ######################################################################## #----------------------------------------------------------------------- class FlickrAPI: """Encapsulated flickr functionality. Example usage: flickr = FlickrAPI(flickrAPIKey, flickrSecret) rsp = flickr.auth_checkToken(api_key=flickrAPIKey, auth_token=token) """ flickrHost = "flickr.com" flickrRESTForm = "/services/rest/" flickrAuthForm = "/services/auth/" flickrUploadForm = "/services/upload/" #------------------------------------------------------------------- def __init__(self, apiKey, secret): """Construct a new FlickrAPI instance for a given API key and secret.""" self.apiKey = apiKey self.secret = secret self.__handlerCache={} #------------------------------------------------------------------- def __sign(self, data): """Calculate the flickr signature for a set of params. data -- a hash of all the params and values to be hashed, e.g. {"api_key":"AAAA", "auth_token":"TTTT"} """ dataName = self.secret keys = data.keys() keys.sort() for a in keys: dataName += (a + data[a]) #print dataName hash = md5.new() hash.update(dataName) return hash.hexdigest() #------------------------------------------------------------------- def __getattr__(self, method, **arg): """Handle all the flickr API calls. This is Michele Campeotto's cleverness, wherein he writes a general handler for methods not defined, and assumes they are flickr methods. He then converts them to a form to be passed as the method= parameter, and goes from there. http://micampe.it/things/flickrclient My variant is the same basic thing, except it tracks if it has already created a handler for a specific call or not. example usage: flickr.auth_getFrob(api_key="AAAAAA") rsp = flickr.favorites_getList(api_key=flickrAPIKey, \\ auth_token=token) """ if not self.__handlerCache.has_key(method): def handler(_self = self, _method = method, **arg): _method = "flickr." + _method.replace("_", ".") url = "http://" + FlickrAPI.flickrHost + \ FlickrAPI.flickrRESTForm arg["method"] = _method postData = urllib.urlencode(arg) + "&api_sig=" + \ _self.__sign(arg) #print "--url---------------------------------------------" #print url #print "--postData----------------------------------------" #print postData f = urllib.urlopen(url, postData) data = f.read() #print "--response----------------------------------------" #print data f.close() return XMLNode.parseXML(data, True) self.__handlerCache[method] = handler; return self.__handlerCache[method] #------------------------------------------------------------------- def __getAuthURL(self, perms, frob): """Return the authorization URL to get a token. This is the URL the app will launch a browser toward if it needs a new token. perms -- "read", "write", or "delete" frob -- picked up from an earlier call to FlickrAPI.auth_getFrob() """ data = {"api_key": self.apiKey, "frob": frob, "perms": perms} data["api_sig"] = self.__sign(data) url = "http://%s%s?%s" % (FlickrAPI.flickrHost, \ FlickrAPI.flickrAuthForm, urllib.urlencode(data)) return url #------------------------------------------------------------------- def upload(self, filename=None, jpegData=None, **arg): """Upload a file to flickr. Be extra careful you spell the parameters correctly, or you will get a rather cryptic "Invalid Signature" error on the upload! Supported parameters: One of filename or jpegData must be specified by name when calling this method: filename -- name of a file to upload jpegData -- array of jpeg data to upload api_key auth_token title description tags -- space-delimited list of tags, "tag1 tag2 tag3" is_public -- "1" or "0" is_friend -- "1" or "0" is_family -- "1" or "0" """ if filename == None and jpegData == None or \ filename != None and jpegData != None: raise UploadException("filename OR jpegData must be specified") # verify key names for a in arg.keys(): if a != "api_key" and a != "auth_token" and a != "title" and \ a != "description" and a != "tags" and a != "is_public" and \ a != "is_friend" and a != "is_family": sys.stderr.write("FlickrAPI: warning: unknown parameter " \ "\"%s\" sent to FlickrAPI.upload\n" % (a)) arg["api_sig"] = self.__sign(arg) url = "http://" + FlickrAPI.flickrHost + FlickrAPI.flickrUploadForm # construct POST data boundary = mimetools.choose_boundary() body = "" # required params for a in ('api_key', 'auth_token', 'api_sig'): body += "--%s\r\n" % (boundary) body += "Content-Disposition: form-data; name=\""+a+"\"\r\n\r\n" body += "%s\r\n" % (arg[a]) # optional params for a in ('title', 'description', 'tags', 'is_public', \ 'is_friend', 'is_family'): if arg.has_key(a): body += "--%s\r\n" % (boundary) body += "Content-Disposition: form-data; name=\""+a+"\"\r\n\r\n" body += "%s\r\n" % (arg[a]) body += "--%s\r\n" % (boundary) body += "Content-Disposition: form-data; name=\"photo\";" body += " filename=\"%s\"\r\n" % filename body += "Content-Type: image/jpeg\r\n\r\n" #print body if filename != None: fp = file(filename, "rb") data = fp.read() fp.close() else: data = jpegData postData = body.encode("utf_8") + data + \ ("--%s--" % (boundary)).encode("utf_8") request = urllib2.Request(url) request.add_data(postData) request.add_header("Content-Type", \ "multipart/form-data; boundary=%s" % boundary) response = urllib2.urlopen(request) rspXML = response.read() return XMLNode.parseXML(rspXML) #----------------------------------------------------------------------- @classmethod def testFailure(cls, rsp, exit=True): """Exit app if the rsp XMLNode indicates failure.""" if not rsp: sys.sysstderr.write("\n\nnull response\n") if exit: sys.exit(1) if rsp['stat'] == "fail": sys.stderr.write("\n\n%s\n" % (cls.getPrintableError(rsp))) if exit: sys.exit(1) #----------------------------------------------------------------------- @classmethod def getPrintableError(cls, rsp): """Return a printed error message string.""" return "%s: error %s: %s" % (rsp.elementName, \ cls.getRspErrorCode(rsp), cls.getRspErrorMsg(rsp)) #----------------------------------------------------------------------- @classmethod def getRspErrorCode(cls, rsp): """Return the error code of a response, or 0 if no error.""" if rsp['stat'] == "fail": return rsp.err[0]['code'] return 0 #----------------------------------------------------------------------- @classmethod def getRspErrorMsg(cls, rsp): """Return the error message of a response, or "Success" if no error.""" if rsp['stat'] == "fail": return rsp.err[0]['msg'] return "Success" #----------------------------------------------------------------------- def __getCachedTokenPath(self): """Return the directory holding the app data.""" return os.path.expanduser(os.path.sep.join(["~", ".flickr", \ self.apiKey])) #----------------------------------------------------------------------- def __getCachedTokenFilename(self): """Return the full pathname of the cached token file.""" return os.path.sep.join([self.__getCachedTokenPath(), "auth.xml"]) #----------------------------------------------------------------------- def __getCachedToken(self): """Read and return a cached token, or None if not found. The token is read from the cached token file, which is basically the entire RSP response containing the auth element. """ try: f = file(self.__getCachedTokenFilename(), "r") data = f.read() f.close() rsp = XMLNode.parseXML(data) return rsp.auth[0].token[0].elementText except IOError: return None #----------------------------------------------------------------------- def __setCachedToken(self, xml): """Cache a token for later use. The cached tag is stored by simply saving the entire RSP response containing the auth element. """ path = self.__getCachedTokenPath() if not os.path.exists(path): os.makedirs(path) f = file(self.__getCachedTokenFilename(), "w") f.write(xml) f.close() #----------------------------------------------------------------------- def getToken(self, perms="read", browser="lynx"): """Get a token either from the cache, or make a new one from the frob. This first attempts to find a token in the user's token cache on disk. If that fails (or if the token is no longer valid based on flickr.auth.checkToken) a new frob is acquired. The frob is validated by having the user log into flickr (with lynx), and subsequently a valid token is retrieved. The newly minted token is then cached locally for the next run. perms--"read", "write", or "delete" browser--whatever browser should be used in the system() call """ # see if we have a saved token token = self.__getCachedToken() # see if it's valid if token != None: rsp = self.auth_checkToken(api_key=self.apiKey, auth_token=token) if rsp['stat'] != "ok": token = None else: # see if we have enough permissions tokenPerms = rsp.auth[0].perms[0].elementText if tokenPerms == "read" and perms != "read": token = None elif tokenPerms == "write" and perms == "delete": token = None # get a new token if we need one if token == None: # get the frob rsp = self.auth_getFrob(api_key=self.apiKey) self.testFailure(rsp) frob = rsp.frob[0].elementText # validate online url = self.__getAuthURL(perms, frob) cmd = "%s \"%s\"" % (browser, url) os.system(cmd) # get a token rsp = self.auth_getToken(api_key=self.apiKey, frob=frob) self.testFailure(rsp) token = rsp.auth[0].token[0].elementText # store the auth info for next time self.__setCachedToken(rsp.xml) return token # run the main if we're not being imported: if __name__ == "__main__": sys.exit(main(sys.argv))
This paste will be private.
From the Design Piracy series on my blog: