[Python-modules-commits] [libthumbor] 01/05: New upstream version 1.3.2
Marcelo Jorge Vieira
metal at moszumanska.debian.org
Fri Sep 8 01:42:54 UTC 2017
This is an automated email from the git hooks/post-receive script.
metal pushed a commit to branch master
in repository libthumbor.
commit 119fd1002306adf754139e4cde0b9fccec929a18
Author: Marcelo Jorge Vieira <metal at alucinados.com>
Date: Thu Sep 7 22:21:36 2017 -0300
New upstream version 1.3.2
---
.coveragerc | 17 +
.gitignore | 7 +
.travis.yml | 45 ++
CONTRIBUTING | 33 ++
LICENSE | 20 +
MANIFEST.in | 2 +
Makefile | 19 +
README.mkd | 41 ++
flake8 | 4 +
libthumbor/__init__.py | 25 +
libthumbor/crypto.py | 58 ++
libthumbor/django/__init__.py | 0
libthumbor/django/urls.py | 9 +
libthumbor/django/views.py | 80 +++
libthumbor/url.py | 309 +++++++++++
libthumbor/url_signers/__init__.py | 25 +
libthumbor/url_signers/base64_hmac_sha1.py | 21 +
setup.py | 61 +++
test_requirements.txt | 1 +
tests/test_cryptourl.py | 64 +++
tests/test_generic_views.py | 159 ++++++
tests/test_libthumbor.py | 73 +++
tests/test_url.py | 146 +++++
tests/test_url_composer.py | 637 ++++++++++++++++++++++
tests/testproj/__init__.py | 0
tests/testproj/manage.py | 11 +
tests/testproj/settings.py | 94 ++++
tests/testproj/urls.py | 8 +
tests/url_signers/__init__.py | 0
tests/url_signers/test_base64_hmac_sha1_signer.py | 39 ++
tests/url_signers/test_base_url_signer.py | 45 ++
31 files changed, 2053 insertions(+)
diff --git a/.coveragerc b/.coveragerc
new file mode 100644
index 0000000..6b99390
--- /dev/null
+++ b/.coveragerc
@@ -0,0 +1,17 @@
+[run]
+omit =
+ test_*.py
+branch = True
+source =
+ libthumbor
+
+[report]
+exclude_lines =
+ pragma: no cover
+ def __repr__
+ raise NotImplementedError
+ if __name__ == .__main__.:
+ from urllib.parse import parse_qs
+ except ImportError:
+ from nose_focus import focus
+ @focus
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..c39af17
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,7 @@
+*.swp
+*.pyc
+.DS_Store
+dist
+libthumbor.egg-info
+*.db
+.coverage
diff --git a/.travis.yml b/.travis.yml
new file mode 100644
index 0000000..f2cbd0d
--- /dev/null
+++ b/.travis.yml
@@ -0,0 +1,45 @@
+language: python
+python:
+ - '2.7'
+ - '3.2'
+ - '3.3'
+ - '3.4'
+ - '3.5'
+ - pypy
+cache:
+ directories:
+ - $HOME/.cache/pip
+sudo: false
+matrix:
+ fast_finish: true
+
+addons:
+ apt:
+ packages:
+ - libcurl4-openssl-dev
+ - python-numpy
+ - python-opencv
+ - libopencv-dev
+ - libjpeg-dev
+ - libpng-dev
+ - libx264-dev
+ - libass-dev
+ - libvpx1
+ - libvpx-dev
+ - libwebp-dev
+ - webp
+ - gifsicle
+ - python-numpy
+ - python-scipy
+ - cython
+install:
+ - make setup
+ # Coveralls 4.0 doesn't support Python 3.2
+ - if [ "$TRAVIS_PYTHON_VERSION" == "3.2" ]; then travis_retry pip install coverage==3.7.1; fi
+ - pip install coveralls
+
+script: make ci_test
+
+after_success:
+ coveralls
+
diff --git a/CONTRIBUTING b/CONTRIBUTING
new file mode 100644
index 0000000..50c8a9c
--- /dev/null
+++ b/CONTRIBUTING
@@ -0,0 +1,33 @@
+So you want to contribute with libthumbor? Welcome onboard!
+
+There are a few things you'll need in order to properly start hacking on it.
+
+First step is to fork it at http://help.github.com/fork-a-repo/ and create your own clone of libthumbor.
+
+## Dependencies
+
+We seriously advise you to use virtualenv (http://pypi.python.org/pypi/virtualenv) since it will keep your environment clean of libthumbor's dependencies and you can choose when to "turn them on".
+
+You'll also need python >= 2.6 and < 3.0.
+
+After that, just issue a `make setup` command and you'll be ready to start hacking.
+
+## Running the Tests
+
+Running the tests is as easy as:
+
+ make test
+
+## Pull Requests
+
+After hacking and testing your contribution, it is time to make a pull request. Make sure that your code is already integrated with the master branch of libthumbor before asking for a pull request.
+
+To add thumbor's remote as a valid remote for your repository:
+
+ git remote add libthumbor git://github.com/thumbor/libthumbor.git
+
+To merge thumbor's master with your fork:
+
+ git pull libthumbor master
+
+If there was anything to merge, just run your tests again. If they pass, send a pull request (http://help.github.com/send-pull-requests/).
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..efd94d5
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,20 @@
+The MIT License (MIT)
+
+Copyright (c) 2014 Globo.com
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of
+this software and associated documentation files (the "Software"), to deal in
+the Software without restriction, including without limitation the rights to
+use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
+the Software, and to permit persons to whom the Software is furnished to do so,
+subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all
+copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
+FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
+COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
+IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
+CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
diff --git a/MANIFEST.in b/MANIFEST.in
new file mode 100644
index 0000000..0d6e54c
--- /dev/null
+++ b/MANIFEST.in
@@ -0,0 +1,2 @@
+prune dist
+prune build
diff --git a/Makefile b/Makefile
new file mode 100644
index 0000000..bb21a57
--- /dev/null
+++ b/Makefile
@@ -0,0 +1,19 @@
+COVERAGE = $(or $(shell which coverage), $(shell which python-coverage), coverage)
+
+test ci_test:
+ @$(COVERAGE) run --branch `which nosetests` -v --with-yanc -s tests/
+ @$(MAKE) coverage
+ @$(MAKE) static
+
+coverage:
+
+ @$(COVERAGE) report -m --fail-under=75
+
+publish:
+ python setup.py sdist upload
+
+setup:
+ @pip install -U -e .\[tests\]
+
+static:
+ @flake8 --config=./flake8 .
diff --git a/README.mkd b/README.mkd
new file mode 100644
index 0000000..6ca259c
--- /dev/null
+++ b/README.mkd
@@ -0,0 +1,41 @@
+[](http://travis-ci.org/thumbor/libthumbor) [](https://coveralls.io/github/thumbor/libthumbor?branch=master)
+
+libthumbor allows easy usage of
+[thumbor](http://github.com/thumbor/thumbor) in Python. Check the docs for django integration.
+
+This version is compliant with the new URL generation schema (thumbor 3.0.0 and up).
+
+### Warning
+
+2.x version won't support old url's
+
+## Using it
+
+ from libthumbor import CryptoURL
+
+ crypto = CryptoURL(key='my-security-key')
+
+ encrypted_url = crypto.generate(
+ width=300,
+ height=200,
+ smart=True,
+ image_url='/path/to/my/image.jpg'
+ )
+
+## Docs
+
+Check the wiki for more information on using libthumbor.
+
+## Contributions
+
+### Bernardo Heynemann
+
+* Generic URL encryption
+
+### Rafael Caricio
+
+* Django Generic View and URL
+
+### Fábio Costa
+
+* Django Generic View and URL
diff --git a/flake8 b/flake8
new file mode 100644
index 0000000..e2eb4e5
--- /dev/null
+++ b/flake8
@@ -0,0 +1,4 @@
+[flake8]
+exclude = ./.tox/*,./build/*,./tests/testproj/*
+max-line-length = 151
+max-complexity = 22
diff --git a/libthumbor/__init__.py b/libthumbor/__init__.py
new file mode 100644
index 0000000..4a9308b
--- /dev/null
+++ b/libthumbor/__init__.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# libthumbor - python extension to thumbor
+# http://github.com/heynemann/libthumbor
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license
+# Copyright (c) 2011 Bernardo Heynemann heynemann at gmail.com
+
+'''libthumbor is the library used to access thumbor's images in python'''
+
+from pkg_resources import get_distribution, DistributionNotFound
+
+__project__ = 'libthumbor'
+
+try:
+ __version__ = get_distribution(__project__).version
+except DistributionNotFound:
+ # Returns a local version. For tests.
+ __version__ = '{}-local'.format(__project__)
+
+from libthumbor.crypto import CryptoURL # NOQA
+from libthumbor.url import Url # NOQA
+from libthumbor.url_signers.base64_hmac_sha1 import UrlSigner as Signer # NOQA
diff --git a/libthumbor/crypto.py b/libthumbor/crypto.py
new file mode 100644
index 0000000..fa9d4d6
--- /dev/null
+++ b/libthumbor/crypto.py
@@ -0,0 +1,58 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# libthumbor - python extension to thumbor
+# http://github.com/heynemann/libthumbor
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license
+# Copyright (c) 2011 Bernardo Heynemann heynemann at gmail.com
+
+'''Encrypted URLs for thumbor encryption.'''
+
+from __future__ import absolute_import
+
+import base64
+import hmac
+import hashlib
+
+from six import text_type, b, PY3
+
+from libthumbor.url import unsafe_url, plain_image_url
+
+
+class CryptoURL(object):
+ '''Class responsible for generating encrypted URLs for thumbor'''
+
+ def __init__(self, key):
+ '''
+ Initializes the encryptor with the proper key
+ :param key: secret key to use for hashing.
+ :param thread_safe: if True (default) CryptoURL will not reuse the hmac instance on every generate call,
+ instead a copy of the hmac object will be created. Consider setting this parameter to False when only one
+ thread has access to the CryptoURL object at a time.
+ '''
+
+ if isinstance(key, text_type):
+ key = str(key)
+ self.key = key
+ self.computed_key = (key * 16)[:16]
+ self.hmac = hmac.new(b(key), digestmod=hashlib.sha1)
+
+ def generate_new(self, options):
+ url = plain_image_url(**options)
+ _hmac = self.hmac.copy()
+ _hmac.update(text_type(url).encode('utf-8'))
+ signature = base64.urlsafe_b64encode(_hmac.digest())
+
+ if PY3:
+ signature = signature.decode('ascii')
+ return '/%s/%s' % (signature, url)
+
+ def generate(self, **options):
+ '''Generates an encrypted URL with the specified options'''
+
+ if options.get('unsafe', False):
+ return unsafe_url(**options)
+ else:
+ return self.generate_new(options)
diff --git a/libthumbor/django/__init__.py b/libthumbor/django/__init__.py
new file mode 100644
index 0000000..e69de29
diff --git a/libthumbor/django/urls.py b/libthumbor/django/urls.py
new file mode 100644
index 0000000..9b5f952
--- /dev/null
+++ b/libthumbor/django/urls.py
@@ -0,0 +1,9 @@
+try:
+ from django.conf.urls.defaults import patterns, url
+except ImportError:
+ from django.conf.urls import patterns, url
+
+urlpatterns = patterns(
+ 'libthumbor.django.views',
+ url("^$", 'generate_url', name="generate_thumbor_url"),
+)
diff --git a/libthumbor/django/views.py b/libthumbor/django/views.py
new file mode 100644
index 0000000..ed2dcd6
--- /dev/null
+++ b/libthumbor/django/views.py
@@ -0,0 +1,80 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# libthumbor - python extension to thumbor
+# http://github.com/heynemann/libthumbor
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license
+# Copyright (c) 2011 Bernardo Heynemann heynemann at gmail.com
+
+'''Generic view for create thumbor encrypted urls.'''
+import logging
+
+from django.http import HttpResponseBadRequest, HttpResponseNotAllowed, HttpResponse
+from django.conf import settings
+
+from libthumbor.crypto import CryptoURL
+
+THUMBOR_SECURITY_KEY = getattr(
+ settings,
+ 'THUMBOR_SECURITY_KEY',
+ 'my-security-key'
+)
+
+THUMBOR_SERVER = getattr(settings, 'THUMBOR_SERVER', 'http://localhost:8888/')
+
+
+def generate_url(request):
+ if request.method != 'GET':
+ return HttpResponseNotAllowed(['GET'])
+
+ crypto = CryptoURL(THUMBOR_SECURITY_KEY)
+
+ args = request.GET
+ args = dict(zip(map(str, args.keys()), args.values()))
+ error_message = None
+
+ try:
+ if 'width' in args:
+ args['width'] = int(args['width'])
+ except ValueError as e:
+ error_message = "The width value '%s' is not an integer." % \
+ args['width']
+
+ try:
+ if 'height' in args:
+ args['height'] = int(args['height'])
+ except ValueError as e:
+ error_message = "The height value '%s' is not an integer." % \
+ args['height']
+
+ try:
+ if 'crop_top' in args or 'crop_left' in args or 'crop_right' in args or 'crop_bottom' in args:
+ args['crop'] = (
+ (int(args['crop_left']), int(args['crop_top'])),
+ (int(args['crop_right']), int(args['crop_bottom']))
+ )
+ except KeyError as e:
+ error_message = '''
+ Missing values for cropping.
+ Expected all 'crop_left', 'crop_top',
+ 'crop_right', 'crop_bottom' values.
+ '''
+ except ValueError as e:
+ error_message = '''
+ Invalid values for cropping.
+ Expected all 'crop_left', 'crop_top',
+ 'crop_right', 'crop_bottom' to be integers.
+ '''
+
+ if error_message is not None:
+ logging.warning(error_message)
+ return HttpResponseBadRequest(error_message)
+
+ try:
+ return HttpResponse(THUMBOR_SERVER + crypto.generate(**args).strip("/"), content_type="text/plain")
+ except (ValueError, KeyError) as e:
+ error_message = str(e)
+ logging.warning(error_message)
+ return HttpResponseBadRequest(error_message)
diff --git a/libthumbor/url.py b/libthumbor/url.py
new file mode 100644
index 0000000..977462d
--- /dev/null
+++ b/libthumbor/url.py
@@ -0,0 +1,309 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# libthumbor - python extension to thumbor
+# http://github.com/heynemann/libthumbor
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license
+# Copyright (c) 2011 Bernardo Heynemann heynemann at gmail.com
+
+'''URL composer to create options-based URLs for thumbor encryption.'''
+
+import hashlib
+import re
+
+from six import b
+
+AVAILABLE_HALIGN = ['left', 'center', 'right']
+AVAILABLE_VALIGN = ['top', 'middle', 'bottom']
+
+
+def calculate_width_and_height(url_parts, options):
+ '''Appends width and height information to url'''
+ width = options.get('width', 0)
+ has_width = width
+ height = options.get('height', 0)
+ has_height = height
+
+ flip = options.get('flip', False)
+ flop = options.get('flop', False)
+
+ if flip:
+ width = width * -1
+ if flop:
+ height = height * -1
+
+ if not has_width and not has_height:
+ if flip:
+ width = "-0"
+ if flop:
+ height = "-0"
+
+ if width or height:
+ url_parts.append('%sx%s' % (width, height))
+
+
+def url_for(**options):
+ '''Returns the url for the specified options'''
+
+ url_parts = get_url_parts(**options)
+ image_hash = hashlib.md5(b(options['image_url'])).hexdigest()
+ url_parts.append(image_hash)
+
+ return "/".join(url_parts)
+
+
+def unsafe_url(**options):
+ '''Returns the unsafe url with the specified options'''
+
+ return 'unsafe/%s' % plain_image_url(**options)
+
+
+def plain_image_url(**options):
+ url_parts = get_url_parts(**options)
+ url_parts.append(options['image_url'])
+
+ return '/'.join(url_parts)
+
+
+def get_url_parts(**options):
+ if 'image_url' not in options:
+ raise ValueError('The image_url argument is mandatory.')
+
+ url_parts = []
+
+ if options.get('meta', False):
+ url_parts.append('meta')
+
+ trim = options.get('trim', None)
+ if trim:
+ bits = ['trim']
+ if not isinstance(trim, bool):
+ bits.append(trim[0] if trim[0] else '')
+ if trim[1]:
+ bits.append(str(trim[1]))
+ url_parts.append(':'.join(bits))
+
+ crop = options.get('crop', None)
+ if crop:
+ crop_left = crop[0][0]
+ crop_top = crop[0][1]
+ crop_right = crop[1][0]
+ crop_bottom = crop[1][1]
+
+ if crop_left > 0 or crop_top > 0 or crop_bottom > 0 or crop_right > 0:
+ url_parts.append("%sx%s:%sx%s" % (
+ crop_left,
+ crop_top,
+ crop_right,
+ crop_bottom
+ ))
+
+ fit_in = False
+ full_fit_in = False
+
+ if options.get('fit_in', None):
+ fit_in = True
+ url_parts.append('fit-in')
+
+ if options.get('full_fit_in', None):
+ full_fit_in = True
+ url_parts.append('full-fit-in')
+
+ if options.get('adaptive_fit_in', None):
+ fit_in = True
+ url_parts.append('adaptive-fit-in')
+
+ if options.get('adaptive_full_fit_in', None):
+ full_fit_in = True
+ url_parts.append('adaptive-full-fit-in')
+
+ if (fit_in or full_fit_in) and not (options.get('width', None) or options.get('height', None)):
+ raise ValueError('When using fit-in or full-fit-in, you must specify width and/or height.')
+
+ calculate_width_and_height(url_parts, options)
+
+ halign = options.get('halign', 'center')
+ valign = options.get('valign', 'middle')
+
+ if halign not in AVAILABLE_HALIGN:
+ raise ValueError('Only "left", "center" and "right" are' +
+ ' valid values for horizontal alignment.')
+ if valign not in AVAILABLE_VALIGN:
+ raise ValueError('Only "top", "middle" and "bottom" are' +
+ ' valid values for vertical alignment.')
+
+ if halign != 'center':
+ url_parts.append(halign)
+ if valign != 'middle':
+ url_parts.append(valign)
+
+ if options.get('smart', False):
+ url_parts.append('smart')
+
+ if options.get('filters', False):
+ filters_string = ['filters']
+ for filter_value in options['filters']:
+ filters_string.append(filter_value)
+ url_parts.append(':'.join(filters_string))
+
+ return url_parts
+
+
+class Url(object):
+
+ unsafe_or_hash = r'(?:(?:(?P<unsafe>unsafe)|(?P<hash>.+?))/)?'
+ debug = '(?:(?P<debug>debug)/)?'
+ meta = '(?:(?P<meta>meta)/)?'
+ trim = '(?:(?P<trim>trim(?::(?:top-left|bottom-right))?(?::\d+)?)/)?'
+ crop = '(?:(?P<crop_left>\d+)x(?P<crop_top>\d+):(?P<crop_right>\d+)x(?P<crop_bottom>\d+)/)?'
+ fit_in = '(?:(?P<adaptive>adaptive-)?(?P<full>full-)?(?P<fit_in>fit-in)/)?'
+ dimensions = '(?:(?P<horizontal_flip>-)?(?P<width>(?:\d+|orig))?x(?P<vertical_flip>-)?(?P<height>(?:\d+|orig))?/)?'
+ halign = r'(?:(?P<halign>left|right|center)/)?'
+ valign = r'(?:(?P<valign>top|bottom|middle)/)?'
+ smart = r'(?:(?P<smart>smart)/)?'
+ filters = r'(?:filters:(?P<filters>.+?\))/)?'
+ image = r'(?P<image>.+)'
+
+ compiled_regex = None
+
+ @classmethod
+ def regex(cls, has_unsafe_or_hash=True):
+ reg = ['/?']
+
+ if has_unsafe_or_hash:
+ reg.append(cls.unsafe_or_hash)
+ reg.append(cls.debug)
+ reg.append(cls.meta)
+ reg.append(cls.trim)
+ reg.append(cls.crop)
+ reg.append(cls.fit_in)
+ reg.append(cls.dimensions)
+ reg.append(cls.halign)
+ reg.append(cls.valign)
+ reg.append(cls.smart)
+ reg.append(cls.filters)
+ reg.append(cls.image)
+
+ return ''.join(reg)
+
+ @classmethod
+ def parse_decrypted(cls, url):
+ if cls.compiled_regex:
+ reg = cls.compiled_regex
+ else:
+ reg = cls.compiled_regex = re.compile(cls.regex(has_unsafe_or_hash=False))
+
+ result = reg.match(url)
+
+ if not result:
+ return None
+
+ result = result.groupdict()
+
+ def int_or_0(value):
+ return 0 if value is None else int(value)
+
+ adaptive = (result.get('adaptive', '') or '').startswith('adaptive')
+ full = (result.get('full', '') or '').startswith('full')
+
+ values = {
+ 'debug': result['debug'] == 'debug',
+ 'meta': result['meta'] == 'meta',
+ 'trim': result['trim'],
+ 'crop': {
+ 'left': int_or_0(result['crop_left']),
+ 'top': int_or_0(result['crop_top']),
+ 'right': int_or_0(result['crop_right']),
+ 'bottom': int_or_0(result['crop_bottom'])
+ },
+ 'adaptive': adaptive,
+ 'full': full,
+ 'fit_in': result['fit_in'] == 'fit-in',
+ 'width': result['width'] == 'orig' and 'orig' or int_or_0(result['width']),
+ 'height': result['height'] == 'orig' and 'orig' or int_or_0(result['height']),
+ 'horizontal_flip': result['horizontal_flip'] == '-',
+ 'vertical_flip': result['vertical_flip'] == '-',
+ 'halign': result['halign'] or 'center',
+ 'valign': result['valign'] or 'middle',
+ 'smart': result['smart'] == 'smart',
+ 'filters': result['filters'] or '',
+ 'image': 'image' in result and result['image'] or None
+ }
+
+ return values
+
+ @classmethod # NOQA
+ def generate_options(cls,
+ debug=False,
+ width=0,
+ height=0,
+ smart=False,
+ meta=False,
+ trim=None,
+ adaptive=False,
+ full=False,
+ fit_in=False,
+ horizontal_flip=False,
+ vertical_flip=False,
+ halign='center',
+ valign='middle',
+ crop_left=None,
+ crop_top=None,
+ crop_right=None,
+ crop_bottom=None,
+ filters=None):
+
+ url = []
+
+ if debug:
+ url.append('debug')
+
+ if meta:
+ url.append('meta')
+
+ if trim:
+ if isinstance(trim, bool):
+ url.append('trim')
+ else:
+ url.append('trim:%s' % trim)
+
+ crop = crop_left or crop_top or crop_right or crop_bottom
+ if crop:
+ url.append('%sx%s:%sx%s' % (
+ crop_left,
+ crop_top,
+ crop_right,
+ crop_bottom
+ ))
+
+ if fit_in:
+ fit_ops = []
+ if adaptive:
+ fit_ops.append('adaptive')
+ if full:
+ fit_ops.append('full')
+ fit_ops.append('fit-in')
+ url.append('-'.join(fit_ops))
+
+ if horizontal_flip:
+ width = '-%s' % width
+ if vertical_flip:
+ height = '-%s' % height
+
+ if width or height:
+ url.append('%sx%s' % (width, height))
+
+ if halign != 'center':
+ url.append(halign)
+ if valign != 'middle':
+ url.append(valign)
+
+ if smart:
+ url.append('smart')
+
+ if filters:
+ url.append('filters:%s' % filters)
+
+ return '/'.join(url)
diff --git a/libthumbor/url_signers/__init__.py b/libthumbor/url_signers/__init__.py
new file mode 100644
index 0000000..7aa8b8d
--- /dev/null
+++ b/libthumbor/url_signers/__init__.py
@@ -0,0 +1,25 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# thumbor imaging service
+# https://github.com/thumbor/thumbor/wiki
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license
+# Copyright (c) 2011 globo.com timehome at corp.globo.com
+
+from six import text_type
+
+
+class BaseUrlSigner(object):
+ def __init__(self, security_key):
+ if isinstance(security_key, text_type):
+ security_key = security_key.encode('utf-8')
+ self.security_key = security_key
+
+ def validate(self, actual_signature, url):
+ url_signature = self.signature(url)
+ return url_signature == actual_signature
+
+ def signature(self, url):
+ raise NotImplementedError()
diff --git a/libthumbor/url_signers/base64_hmac_sha1.py b/libthumbor/url_signers/base64_hmac_sha1.py
new file mode 100644
index 0000000..5bedfa4
--- /dev/null
+++ b/libthumbor/url_signers/base64_hmac_sha1.py
@@ -0,0 +1,21 @@
+# -*- coding: utf-8 -*-
+
+import base64
+import hashlib
+import hmac
+
+from six import text_type
+
+from libthumbor.url_signers import BaseUrlSigner
+
+
+class UrlSigner(BaseUrlSigner):
+ """Validate urls and sign them using base64 hmac-sha1
+ """
+
+ def signature(self, url):
+ return base64.urlsafe_b64encode(
+ hmac.new(
+ self.security_key, text_type(url).encode('utf-8'), hashlib.sha1
+ ).digest()
+ )
diff --git a/setup.py b/setup.py
new file mode 100644
index 0000000..0898cc7
--- /dev/null
+++ b/setup.py
@@ -0,0 +1,61 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# libthumbor - python extension to thumbor
+# http://github.com/heynemann/libthumbor
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license
+# Copyright (c) 2011 Bernardo Heynemann heynemann at gmail.com
+
+'''Module that configures setuptools to package libthumbor'''
+
+from setuptools import setup, find_packages
+
+tests_require = [
+ 'mock',
+ 'nose',
+ 'coverage',
+ 'yanc',
+ 'preggy',
+ 'flake8',
+]
+
+setup(
+ name='libthumbor',
+ version="1.3.2",
+ description="libthumbor is the python extension to thumbor",
+ long_description="""
+libthumbor is the python extension to thumbor.
+It allows users to generate safe urls easily.
+""",
+ keywords='imaging face detection feature thumbor thumbnail' +
+ ' imagemagick pil opencv',
+ author='Bernardo Heynemann',
+ author_email='heynemann at gmail.com',
+ url='http://github.com/heynemann/libthumbor',
+ license='MIT',
+ classifiers=['Development Status :: 4 - Beta',
+ 'Intended Audience :: Developers',
+ 'License :: OSI Approved :: MIT License',
+ 'Natural Language :: English',
+ 'Operating System :: MacOS',
+ 'Operating System :: POSIX :: Linux',
+ 'Programming Language :: Python :: 2.6',
+ 'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
+ 'Topic :: Multimedia :: Graphics :: Presentation'
+ ],
+ packages=find_packages(),
+ package_dir={"libthumbor": "libthumbor"},
+ include_package_data=True,
+ package_data={
+ },
+
+ extras_require={
+ 'tests': tests_require,
+ },
+
+ install_requires=[
+ "six"
+ ],
+)
diff --git a/test_requirements.txt b/test_requirements.txt
new file mode 100644
index 0000000..f3c7e8e
--- /dev/null
+++ b/test_requirements.txt
@@ -0,0 +1 @@
+nose
diff --git a/tests/test_cryptourl.py b/tests/test_cryptourl.py
new file mode 100644
index 0000000..d765271
--- /dev/null
+++ b/tests/test_cryptourl.py
@@ -0,0 +1,64 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+# libthumbor - python extension to thumbor
+# http://github.com/heynemann/libthumbor
+
+# Licensed under the MIT license:
+# http://www.opensource.org/licenses/mit-license
+# Copyright (c) 2011 Bernardo Heynemann heynemann at gmail.com
+
+'''libthumbor cryptography tests'''
+from unittest import TestCase
+
+from six import text_type
+
+from libthumbor.crypto import CryptoURL
+
+IMAGE_URL = 'my.server.com/some/path/to/image.jpg'
+KEY = 'my-security-key'
+
+
+class NewFormatUrlTestsMixin:
+ def test_generated_url_1(self):
+ url = self.crypto.generate(image_url=IMAGE_URL, width=300, height=200)
+ assert url == '/8ammJH8D-7tXy6kU3lTvoXlhu4o=/300x200/my.server.com/some/path/to/image.jpg'
+
+ def test_generated_url_2(self):
+ url = self.crypto.generate(image_url=IMAGE_URL, width=300, height=200, crop=((10, 10), (200, 200)))
+ assert url == '/B35oBEIwztbc3jm7vsdqLez2C78=/10x10:200x200/300x200/my.server.com/some/path/to/image.jpg'
+
+ def test_generated_url_3(self):
+ url = self.crypto.generate(image_url=IMAGE_URL, width=300, height=200, crop=((10, 10), (200, 200)), filters=("brightness(20)", "contrast(10)"))
+ assert url == '/as8U2DbUUtTMgvPF26LkjS3MocY=/10x10:200x200/300x200/filters:brightness(20):contrast(10)/my.server.com/some/path/to/image.jpg'
+
+ def test_generated_url_4(self):
+ url = self.crypto.generate(image_url=IMAGE_URL, width=300, height=200, crop=((10, 10), (200, 200)), filters=("brightness(20)", "contrast(10)"))
+ assert url == '/as8U2DbUUtTMgvPF26LkjS3MocY=/10x10:200x200/300x200/filters:brightness(20):contrast(10)/my.server.com/some/path/to/image.jpg'
+ # making sure no internal state affects subsequent calls.
+ url = self.crypto.generate(image_url=IMAGE_URL, width=300, height=200, crop=((10, 10), (200, 200)), filters=("brightness(20)", "contrast(10)"))
+ assert url == '/as8U2DbUUtTMgvPF26LkjS3MocY=/10x10:200x200/300x200/filters:brightness(20):contrast(10)/my.server.com/some/path/to/image.jpg'
+
+
+class NewFormatUrl(TestCase, NewFormatUrlTestsMixin):
+ def setUp(self):
+ self.crypto = CryptoURL(KEY)
+
+
+class NewFormatUrlWithUnicodeKey(TestCase, NewFormatUrlTestsMixin):
+ def setUp(self):
+ self.crypto = CryptoURL(text_type(KEY))
+
+
+class GenerateWithUnsafeTestCase(TestCase):
+
+ def setUp(self):
+ self.crypto = CryptoURL(KEY)
+
+ def test_should_pass_unsafe_to_generate_and_get_an_unsafe_url(self):
+ url = self.crypto.generate(image_url=IMAGE_URL, crop=((10, 20), (30, 40)), unsafe=True)
+ self.assertTrue(url.startswith('unsafe'), "url should starts with unsafe")
+
+ def test_should_not_get_an_unsafe_url_when_unsafe_is_false(self):
+ url = self.crypto.generate(image_url=IMAGE_URL, crop=((10, 20), (30, 40)), unsafe=False)
+ self.assertFalse(url.startswith('unsafe'), "url should not starts with unsafe")
diff --git a/tests/test_generic_views.py b/tests/test_generic_views.py
new file mode 100644
index 0000000..2d44af3
... 1269 lines suppressed ...
--
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/python-modules/packages/libthumbor.git
More information about the Python-modules-commits
mailing list