Package pyrobase :: Package webservice :: Module imgur
[hide private]
[frames] | no frames]

Source Code for Module pyrobase.webservice.imgur

  1  # -*- coding: utf-8 -*- 
  2  # pylint: disable=logging-not-lazy, bad-whitespace 
  3  """ imgur image hosting. 
  4   
  5      Copyright (c) 2012 The PyroScope Project <pyroscope.project@gmail.com> 
  6  """ 
  7  # This program is free software; you can redistribute it and/or modify 
  8  # it under the terms of the GNU General Public License as published by 
  9  # the Free Software Foundation; either version 2 of the License, or 
 10  # (at your option) any later version. 
 11  # 
 12  # This program is distributed in the hope that it will be useful, 
 13  # but WITHOUT ANY WARRANTY; without even the implied warranty of 
 14  # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the 
 15  # GNU General Public License for more details. 
 16  # 
 17  # You should have received a copy of the GNU General Public License along 
 18  # with this program; if not, write to the Free Software Foundation, Inc., 
 19  # 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 20  from __future__ import with_statement 
 21   
 22  import os 
 23  import sys 
 24  import socket 
 25  import hashlib 
 26  import httplib 
 27  import logging 
 28  from contextlib import closing 
 29   
 30  from pyrobase import parts, pyutil, logutil, fmt 
 31  from pyrobase.io import http 
 32   
 33  json = pyutil.require_json() 
 34  LOG = logging.getLogger(__name__) 
 35   
 36   
 37  UploadError = (socket.error, httplib.HTTPException) 
 38   
 39   
40 -class ImgurUploader(object): # pylint: disable=R0903
41 """ Upload an image to "imgur.com". 42 43 Sample code:: 44 imgur = ImgurUploader() 45 image = imgur.upload("favicon.jpg") 46 # OR: image = imgur.upload(open("favicon.jpg", "rb")) 47 # OR: image = imgur.upload(open("favicon.jpg", "rb").read()) 48 # OR: image = imgur.upload("http://i.imgur.com/5EuUx.jpg") 49 print image.links.original 50 """ 51 UPLOAD_URL = "http://api.imgur.com/2/upload.json" 52 53
54 - def __init__(self, api_key=None, mock_http=False):
55 """ Initialize upload parameters. 56 57 @param api_key: the API key (optionally taken from IMGUR_APIKEY environment variable). 58 """ 59 self.api_key = api_key or os.environ.get("IMGUR_APIKEY") 60 self.mock_http = mock_http
61 62
63 - def upload(self, image, name=None):
64 """ Upload the given image, which can be a http[s] URL, a path to an existing file, 65 binary image data, or an open file handle. 66 """ 67 assert self.api_key, "imgur API key is not set! Export the IMGUR_APIKEY environment variable..." 68 69 # Prepare image 70 try: 71 image_data = (image + '') 72 except (TypeError, ValueError): 73 assert hasattr(image, "read"), "Image is neither a string nor an open file handle" 74 image_type = "file" 75 image_data = image 76 image_repr = repr(image) 77 else: 78 if image.startswith("http:") or image.startswith("https:"): 79 image_type = "url" 80 image_data = image 81 image_repr = image 82 elif all(ord(i) >= 32 for i in image) and os.path.exists(image): 83 image_type = "file" 84 image_data = open(image, "rb") 85 image_repr = "file:" + image 86 else: 87 image_type = "base64" 88 image_data = image_data.encode(image_type) 89 image_repr = "<binary data>" 90 91 # See http://api.imgur.com/resources_anon#upload 92 fields = [ 93 ("key", self.api_key), 94 ("type", image_type), 95 ("image", image_data), 96 ("name", name or hashlib.md5(str(image)).hexdigest()), 97 ] 98 handle = http.HttpPost(self.UPLOAD_URL, fields, mock_http=self.mock_http) 99 100 response = handle.send() 101 if response.status >= 300: 102 LOG.warn("Image %s upload failed with result %d %s" % (image_repr, response.status, response.reason)) 103 else: 104 LOG.debug("Image %s uploaded with result %d %s" % (image_repr, response.status, response.reason)) 105 body = response.read() 106 LOG.debug("Response size: %d" % len(body)) 107 LOG.debug("Response headers:\n %s" % "\n ".join([ 108 "%s: %s" % item for item in response.getheaders() 109 ])) 110 111 try: 112 result = json.loads(body) 113 except (ValueError, TypeError), exc: 114 raise httplib.HTTPException("Bad JSON data from imgur upload%s [%s]: %s" % ( 115 ", looking like a CAPTCHA challenge" if "captcha" in body else "", 116 exc, logutil.shorten(body))) 117 118 if "error" in result: 119 raise httplib.HTTPException("Error response from imgur.com: %(message)s" % result["error"], result) 120 121 return parts.Bunch([(key, parts.Bunch(val)) 122 for key, val in result["upload"].items() 123 ])
124 125
126 -def fake_upload_from_url(url):
127 """ Return a 'fake' upload data record, so that upload errors 128 can be mitigated by using an original / alternative URL. 129 """ 130 return parts.Bunch( 131 image=parts.Bunch( 132 animated='false', bandwidth=0, caption=None, views=0, deletehash=None, hash=None, 133 name=(url.rsplit('/', 1) + [url])[1], title=None, type='image/*', width=0, height=0, size=0, 134 datetime=fmt.iso_datetime(), 135 ), 136 links=parts.Bunch( 137 delete_page=None, imgur_page=None, 138 original=url, large_thumbnail=url, small_square=url, 139 ))
140 141
142 -def cache_image_data(cache_dir, cache_key, uploader, *args, **kwargs):
143 """ Call uploader and cache its results. 144 """ 145 use_cache = True 146 if "use_cache" in kwargs: 147 use_cache = kwargs["use_cache"] 148 del kwargs["use_cache"] 149 150 json_path = None 151 if cache_dir: 152 json_path = os.path.join(cache_dir, "cached-img-%s.json" % cache_key) 153 if use_cache and os.path.exists(json_path): 154 LOG.info("Fetching %r from cache..." % (args,)) 155 try: 156 with closing(open(json_path, "r")) as handle: 157 img_data = json.load(handle) 158 159 return parts.Bunch([(key, parts.Bunch(val)) 160 for key, val in img_data.items() # BOGUS pylint: disable=E1103 161 ]) 162 except (EnvironmentError, TypeError, ValueError), exc: 163 LOG.warn("Problem reading cached data from '%s', ignoring cache... (%s)" % (json_path, exc)) 164 165 LOG.info("Copying %r..." % (args,)) 166 img_data = uploader(*args, **kwargs) 167 168 if json_path: 169 with closing(open(json_path, "w")) as handle: 170 json.dump(img_data, handle) 171 172 return img_data
173 174
175 -def copy_image_from_url(url, cache_dir=None, use_cache=True):
176 """ Copy image from given URL and return upload metadata. 177 """ 178 return cache_image_data(cache_dir, hashlib.sha1(url).hexdigest(), ImgurUploader().upload, url, use_cache=use_cache)
179 180
181 -def _main():
182 """ Command line interface for testing. 183 """ 184 import pprint 185 import tempfile 186 187 try: 188 image = sys.argv[1] 189 except IndexError: 190 print("Usage: python -m pyrobase.webservice.imgur <url>") 191 else: 192 try: 193 pprint.pprint(copy_image_from_url(image, cache_dir=tempfile.gettempdir())) 194 except UploadError, exc: 195 print("Upload error. %s" % exc)
196 197 198 # When called directly, e.g. via 199 # python -m pyrobase.webservice.imgur http://i.imgur.com/5EuUx.jpg 200 if __name__ == "__main__": 201 _main() 202