Uploading Images to imgur with Python

That time again. Code snippet time.

UPDATE: See the bottom of the post for the most up-to-date version of the code.

Another brief one. We are going to start working on a class to upload images to imgur.com. There are many ways we can go about doing this, but to start things off, we’ll use the simplest way which is to use the public/anonymous API. This means that anyone can view our images and that we do not need to worry about authentication just yet.

We will use pycurl for the url handling and parameter passing and to decipher our results we will utilize the XML minidom.

Let’s jump right in and see the code. You will need an API key, available here, that you will need to substitute for the key parameter string “YOUR_API_KEY_HERE”.

class UploadImage():
	"""
	Upload images to imgur
	"""

	# Not very verbose at the moment in terms of errors - will build on that later
	def __init__(self,image,upload_method="anon"):
		import pycurl
		from xml.dom import minidom
		import cStringIO

		# setup some initial items we will need
		self.c = pycurl.Curl()
		self.response = cStringIO.StringIO()
		self.minidom = minidom
		self.image = image
		self.imageURL = ""
		self.error = ""

		# provide some various methods for uploading
		# by default - at least for now in testing - we will use anonymous upload method
		if upload_method == "anon":
			self.anon_upload()
		# later or on-demand we can switch to oauth based uploads
		elif upload_method == "oauth":
			self.auth_upload()

	# anonymous upload method
	def anon_upload(self):
		"Upload anonymously to imgur"

		# setup the basic parameters
		params = [
				("key", "YOUR_API_KEY_HERE"),
				("image", (self.c.FORM_FILE, self.image))
			]

		# setup the url and pipe in our key and image
		self.c.setopt(self.c.URL, "http://api.imgur.com/2/upload.xml")
		self.c.setopt(self.c.HTTPPOST, params)

		# we want to capture the output so lets set the write output to go to our cStringIO so we can parse it
		self.c.setopt(self.c.WRITEFUNCTION, self.response.write)

		# run it
		self.c.perform()
		self.c.close()

		try:
			# parse the XML return string and get the URL of our image
			xml = self.minidom.parseString(self.response.getvalue())
			self.imageURL = xml.getElementsByTagName("original")[0].firstChild.data

		except:
			self.error = "Problem uploading anonymously."

		return self.imageURL,self.error

	# oauth-based upload method
	def oauth_upload(self):
		"Upload using oauth to imgur"

		### Not coded yet but we will use python-oauth2
		pass

To use our uploader, all we have to do is something like the following:

In [1]: from handlers import UploadImage as U

In [2]: a = U('roosevelt.jpg')

In [3]: a.imageURL
Out[3]: u'http://i.imgur.com/cUWzn.jpg'

In [4]: a.error
Out[4]: ''

As you can surmise, we could simply check that there was no error and then utilize the returned URL of our newly uploaded image. Pretty simple stuff and very handy for some quick public/disposable image uploads.

Next time, we will expand our uploader to include an oAuth method that we can utilize for private uploads. Additionally, we will explore our XML returns a little deeper for methods to delete our files or grab some basic view statistics.

Updated code: Here, we are using things in the context of Django, hence the settings call for pulling in the API key and so forth.

class UploadImage():
	"""
	Upload images to imgur
	  returns either .error or .imageURL

	TO DO:

	POSSIBLE TO DO:
		- add stats and image info functions
		- allow for full JSON responses instead of having to manually parse XML

	"""

	# Not very verbose at the moment in terms of errors - will build on that later
	def __init__(self,image="",dhash="",delete=False):

		# setup some initial items and placeholders we will need
		self.c = pycurl.Curl()
		self.response = cStringIO.StringIO()
		self.minidom = minidom
		self.image = image
		self.dhash = dhash.__str__()
		self.delete = delete
		self.message = ""
		self.imageURL = {}
		self.error = []

		if self.dhash and self.delete:
			# if we have a hash and a delete trigger, lets try to wipe the image
			self.wipe()

		else:
			# fire away an upload - we will return an imageURL dictionary with attributes or an error
			self.upload()


	def upload(self):
		"Upload anonymously to imgur"

		# setup the basic parameters
		params = [
				("key", settings.imgur["anon_key"]),
				("image", (self.c.FORM_FILE, self.image))
			]

		# setup the url and pipe in our key and image
		self.c.setopt(self.c.URL, "http://api.imgur.com/2/upload.xml")
		self.c.setopt(self.c.HTTPPOST, params)

		# we want to capture the output so lets set the write output to go to our cStringIO so we can parse it
		self.c.setopt(self.c.WRITEFUNCTION, self.response.write)

		try:
			# run it
			self.c.perform()
			self.c.close()

		except:
			self.error.append("Problem uploading image.")

		if not self.error:
			# parse the xml
			self.parseXML()


		return self.message,self.imageURL,self.error


	def wipe(self):
		"Wipe an anonymouse image from imgur"

		deleteURL = "http://api.imgur.com/2/delete/%s" % self.dhash

		self.c.setopt(self.c.URL, deleteURL)

		self.c.setopt(self.c.WRITEFUNCTION, self.response.write)

		try:
			self.c.perform()
			self.c.close()

		except:
			self.error.append("Problem deleting image.")

		if not self.error:
			self.parseXML(delete=True)


	def parseXML(self,delete=False):
		"Parse the XML ouput from IMGUR and write to the imageURL dictionary"

		try:
			# parse the XML string into the dom
			xml = self.minidom.parseString(self.response.getvalue())

			if delete:
				self.message = xml.getElementsByTagName("message")[0].firstChild.data

			else:
				# grab out some helpful/interesting data and setup in the imageURL dictionary
				self.imageURL['url'] = xml.getElementsByTagName("original")[0].firstChild.data
				self.imageURL['hash'] = xml.getElementsByTagName("hash")[0].firstChild.data
				self.imageURL['deletehash'] = xml.getElementsByTagName("deletehash")[0].firstChild.data
				self.imageURL['smallthumb'] = xml.getElementsByTagName("small_square")[0].firstChild.data
				self.imageURL['bigthumb'] = xml.getElementsByTagName("large_thumbnail")[0].firstChild.data

		except:
			self.error.append("Problem parsing XML output.")