Skip to content

Commit

Permalink
Added Support for Cloudinary
Browse files Browse the repository at this point in the history
  • Loading branch information
jakewaldron committed Mar 11, 2015
1 parent 56ff1ff commit e99b5ef
Show file tree
Hide file tree
Showing 24 changed files with 1,977 additions and 0 deletions.
1 change: 1 addition & 0 deletions scripts/cloudinary/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
build/
125 changes: 125 additions & 0 deletions scripts/cloudinary/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,125 @@
from __future__ import absolute_import

import os
import sys

CF_SHARED_CDN = "d3jpl91pxevbkh.cloudfront.net"
OLD_AKAMAI_SHARED_CDN = "cloudinary-a.akamaihd.net"
AKAMAI_SHARED_CDN = "res.cloudinary.com"
SHARED_CDN = AKAMAI_SHARED_CDN
CL_BLANK = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAAALAAAAAABAAEAAAIBRAA7"

VERSION = "1.0.21"
USER_AGENT = "cld-python-" + VERSION

from cloudinary import utils
from cloudinary.compat import urlparse, parse_qs

def import_django_settings():
try:
import django.conf
from django.core.exceptions import ImproperlyConfigured
try:
if 'CLOUDINARY' in dir(django.conf.settings):
return django.conf.settings.CLOUDINARY
else:
return None
except ImproperlyConfigured:
return None
except ImportError:
return None

class Config(object):
def __init__(self):
django_settings = import_django_settings()
if django_settings:
self.update(**django_settings)
elif os.environ.get("CLOUDINARY_CLOUD_NAME"):
self.update(
cloud_name = os.environ.get("CLOUDINARY_CLOUD_NAME"),
api_key = os.environ.get("CLOUDINARY_API_KEY"),
api_secret = os.environ.get("CLOUDINARY_API_SECRET"),
secure_distribution = os.environ.get("CLOUDINARY_SECURE_DISTRIBUTION"),
private_cdn = os.environ.get("CLOUDINARY_PRIVATE_CDN") == 'true'
)
elif os.environ.get("CLOUDINARY_URL"):
uri = urlparse(os.environ.get("CLOUDINARY_URL").replace("cloudinary://", "http://"))
for k, v in parse_qs(uri.query).items():
self.__dict__[k] = v[0]
self.update(
cloud_name = uri.hostname,
api_key = uri.username,
api_secret = uri.password,
private_cdn = uri.path != ''
)
if uri.path != '':
self.update(secure_distribution = uri.path[1:])
def __getattr__(self, i):
if i in self.__dict__:
return self.__dict__[i]
else:
return None

def update(self, **keywords):
for k, v in keywords.items():
self.__dict__[k] = v

_config = Config()

def config(**keywords):
global _config
_config.update(**keywords)
return _config

def reset_config():
global _config
_config = Config()

class CloudinaryImage(object):
def __init__(self, public_id = None, format = None, version = None,
signature = None, url_options = {}, metadata = None, type = None):

self.metadata = metadata
metadata = metadata or {}
self.public_id = public_id or metadata.get('public_id')
self.format = format or metadata.get('format')
self.version = version or metadata.get('version')
self.signature = signature or metadata.get('signature')
self.type = type or metadata.get('type') or "upload"
self.url_options = url_options

def __unicode__(self):
return self.public_id

def validate(self):
expected = utils.api_sign_request({"public_id": self.public_id, "version": self.version}, config().api_secret)
return self.signature == expected

@property
def url(self):
return self.build_url(**self.url_options)

def __build_url(self, **options):
combined_options = dict(format = self.format, version = self.version, type = self.type)
combined_options.update(options)
return utils.cloudinary_url(self.public_id, **combined_options)

def build_url(self, **options):
return self.__build_url(**options)[0]

def image(self, **options):
src, attrs = self.__build_url(**options)
responsive = attrs.pop("responsive", False)
hidpi = attrs.pop("hidpi", False)
if responsive or hidpi:
attrs["data-src"] = src
classes = "cld-responsive" if responsive else "cld-hidpi"
if "class" in attrs: classes += " " + attrs["class"]
attrs["class"] = classes
src = attrs.pop("responsive_placeholder", config().responsive_placeholder)
if src == "blank": src = CL_BLANK

attrs = sorted(attrs.items())
if src: attrs.insert(0, ("src", src))

return u"<img {0}/>".format(' '.join([u"{0}='{1}'".format(key, value) for key, value in attrs if value]))
Binary file added scripts/cloudinary/__init__.pyc
Binary file not shown.
243 changes: 243 additions & 0 deletions scripts/cloudinary/api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,243 @@
# Copyright Cloudinary
import json
import base64
import sys
import email.utils
import socket
import cloudinary
from cloudinary.compat import urllib2, urlencode, to_string, to_bytes, PY3, HTTPError
from cloudinary import utils

class Error(Exception): pass
class NotFound(Error): pass
class NotAllowed(Error): pass
class AlreadyExists(Error): pass
class RateLimited(Error): pass
class BadRequest(Error): pass
class GeneralError(Error): pass
class AuthorizationRequired(Error): pass

EXCEPTION_CODES = {
400: BadRequest,
401: AuthorizationRequired,
403: NotAllowed,
404: NotFound,
409: AlreadyExists,
420: RateLimited
}

class Response(dict):
def __init__(self, result, response):
self.update(result)
self.rate_limit_allowed = int(response.headers["x-featureratelimit-limit"])
self.rate_limit_reset_at = email.utils.parsedate(response.headers["x-featureratelimit-reset"])
self.rate_limit_remaining = int(response.headers["x-featureratelimit-remaining"])

def ping(**options):
return call_api("get", ["ping"], {}, **options)

def usage(**options):
return call_api("get", ["usage"], {}, **options)

def resource_types(**options):
return call_api("get", ["resources"], {}, **options)

def resources(**options):
resource_type = options.pop("resource_type", "image")
type = options.pop("type", None)
uri = ["resources", resource_type]
if type: uri.append(type)
return call_api("get", uri, only(options, "next_cursor", "max_results", "prefix", "tags", "context", "moderations", "direction", "start_at"), **options)

def resources_by_tag(tag, **options):
resource_type = options.pop("resource_type", "image")
uri = ["resources", resource_type, "tags", tag]
return call_api("get", uri, only(options, "next_cursor", "max_results", "tags", "context", "moderations", "direction"), **options)

def resources_by_moderation(kind, status, **options):
resource_type = options.pop("resource_type", "image")
uri = ["resources", resource_type, "moderations", kind, status]
return call_api("get", uri, only(options, "next_cursor", "max_results", "tags", "context", "moderations", "direction"), **options)

def resources_by_ids(public_ids, **options):
resource_type = options.pop("resource_type", "image")
type = options.pop("type", "upload")
uri = ["resources", resource_type, type]
params = [("public_ids[]", public_id) for public_id in public_ids]
optional = list(only(options, "tags", "moderations", "context").items())
return call_api("get", uri, params + optional, **options)

def resource(public_id, **options):
resource_type = options.pop("resource_type", "image")
type = options.pop("type", "upload")
uri = ["resources", resource_type, type, public_id]
return call_api("get", uri, only(options, "exif", "faces", "colors", "image_metadata", "pages", "phash", "coordinates", "max_results"), **options)

def update(public_id, **options):
resource_type = options.pop("resource_type", "image")
type = options.pop("type", "upload")
uri = ["resources", resource_type, type, public_id]
upload_options = only(options, "moderation_status", "raw_convert", "ocr", "categorization", "detection", "similarity_search", "background_removal")
if "tags" in options: upload_options["tags"] = ",".join(utils.build_array(options["tags"]))
if "face_coordinates" in options: upload_options["face_coordinates"] = utils.encode_double_array(options.get("face_coordinates"))
if "custom_coordinates" in options: upload_options["custom_coordinates"] = utils.encode_double_array(options.get("custom_coordinates"))
if "context" in options: upload_options["context"] = utils.encode_dict(options.get("context"))
if "auto_tagging" in options: upload_options["auto_tagging"] = float(options.get("auto_tagging"))
return call_api("post", uri, upload_options, **options)

def delete_resources(public_ids, **options):
resource_type = options.pop("resource_type", "image")
type = options.pop("type", "upload")
uri = ["resources", resource_type, type]
params = [("public_ids[]", public_id) for public_id in public_ids]
optional = list(only(options, "keep_original", "next_cursor", "invalidate").items())
return call_api("delete", uri, params + optional, **options)

def delete_resources_by_prefix(prefix, **options):
resource_type = options.pop("resource_type", "image")
type = options.pop("type", "upload")
uri = ["resources", resource_type, type]
return call_api("delete", uri, dict(only(options, "keep_original", "next_cursor", "invalidate"), prefix=prefix), **options)

def delete_all_resources(**options):
resource_type = options.pop("resource_type", "image")
type = options.pop("type", "upload")
uri = ["resources", resource_type, type]
optional = list(only(options, "keep_original", "next_cursor").items())
return call_api("delete", uri, dict(only(options, "keep_original", "next_cursor", "invalidate"), all=True), **options)

def delete_resources_by_tag(tag, **options):
resource_type = options.pop("resource_type", "image")
uri = ["resources", resource_type, "tags", tag]
return call_api("delete", uri, only(options, "keep_original", "next_cursor", "invalidate"), **options)

def delete_derived_resources(derived_resource_ids, **options):
uri = ["derived_resources"]
params = [("derived_resource_ids[]", derived_resource_id) for derived_resource_id in derived_resource_ids]

return call_api("delete", uri, params, **options)

def tags(**options):
resource_type = options.pop("resource_type", "image")
uri = ["tags", resource_type]
return call_api("get", uri, only(options, "next_cursor", "max_results", "prefix"), **options)

def transformations(**options):
uri = ["transformations"]
return call_api("get", uri, only(options, "next_cursor", "max_results"), **options)

def transformation(transformation, **options):
uri = ["transformations", transformation_string(transformation)]
return call_api("get", uri, only(options, "max_results"), **options)

def delete_transformation(transformation, **options):
uri = ["transformations", transformation_string(transformation)]
return call_api("delete", uri, {}, **options)

# updates - currently only supported update is the "allowed_for_strict" boolean flag and unsafe_update
def update_transformation(transformation, **options):
uri = ["transformations", transformation_string(transformation)]
updates = only(options, "allowed_for_strict")
if "unsafe_update" in options:
updates["unsafe_update"] = transformation_string(options.get("unsafe_update"))
if len(updates) == 0: raise Exception("No updates given")

return call_api("put", uri, updates, **options)

def create_transformation(name, definition, **options):
uri = ["transformations", name]
return call_api("post", uri, {"transformation": transformation_string(definition)}, **options)

def upload_presets(**options):
uri = ["upload_presets"]
return call_api("get", uri, only(options, "next_cursor", "max_results"), **options)

def upload_preset(name, **options):
uri = ["upload_presets", name]
return call_api("get", uri, only(options, "max_results"), **options)

def delete_upload_preset(name, **options):
uri = ["upload_presets", name]
return call_api("delete", uri, {}, **options)

def update_upload_preset(name, **options):
uri = ["upload_presets", name]
params = utils.build_upload_params(**options)
params = utils.cleanup_params(params)
params.update(only(options, "unsigned", "disallow_public_id"))
return call_api("put", uri, params, **options)

def create_upload_preset(**options):
uri = ["upload_presets"]
params = utils.build_upload_params(**options)
params = utils.cleanup_params(params)
params.update(only(options, "unsigned", "disallow_public_id", "name"))
return call_api("post", uri, params, **options)

def root_folders(**options):
return call_api("get", ["folders"], {}, **options)

def subfolders(of_folder_path, **options):
return call_api("get", ["folders", of_folder_path], {}, **options)

def call_api(method, uri, params, **options):
prefix = options.pop("upload_prefix", cloudinary.config().upload_prefix) or "https://api.cloudinary.com"
cloud_name = options.pop("cloud_name", cloudinary.config().cloud_name)
if not cloud_name: raise Exception("Must supply cloud_name")
api_key = options.pop("api_key", cloudinary.config().api_key)
if not api_key: raise Exception("Must supply api_key")
api_secret = options.pop("api_secret", cloudinary.config().api_secret)
if not cloud_name: raise Exception("Must supply api_secret")

data = to_bytes(urlencode(params))
api_url = "/".join([prefix, "v1_1", cloud_name] + uri)
request = urllib2.Request(api_url, data)
# Add authentication
byte_value = to_bytes('%s:%s' % (api_key, api_secret))
encoded_value = base64.encodebytes(byte_value) if PY3 else base64.encodestring(byte_value)
base64string = to_string(encoded_value).replace('\n', '')
request.add_header("Authorization", "Basic %s" % base64string)
request.add_header("User-Agent", cloudinary.USER_AGENT)
request.get_method = lambda: method.upper()

kw = {}
if 'timeout' in options:
kw['timeout'] = options['timeout']
try:
response = urllib2.urlopen(request, **kw)
body = response.read()
except HTTPError:
e = sys.exc_info()[1]
exception_class = EXCEPTION_CODES.get(e.code)
if exception_class:
response = e
body = response.read()
else:
raise GeneralError("Server returned unexpected status code - %d - %s" % (e.code, e.read()))
except socket.error:
e = sys.exc_info()[1]
raise GeneralError("Socket Error: %s" % (str(e)))

try:
body = to_string(body)
result = json.loads(body)
except Exception:
# Error is parsing json
e = sys.exc_info()[1]
raise GeneralError("Error parsing server response (%d) - %s. Got - %s" % (response.code, body, e))

if "error" in result:
exception_class = exception_class or Exception
raise exception_class(result["error"]["message"])

return Response(result, response)

def only(hash, *keys):
result = {}
for key in keys:
if key in hash: result[key] = hash[key]
return result

def transformation_string(transformation):
return transformation if isinstance(transformation, str) else cloudinary.utils.generate_transformation_string(**transformation)[0]

Binary file added scripts/cloudinary/api.pyc
Binary file not shown.
Loading

0 comments on commit e99b5ef

Please sign in to comment.