[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 @@
+[![Build Status](https://secure.travis-ci.org/thumbor/libthumbor.png)](http://travis-ci.org/thumbor/libthumbor) [![Coverage Status](https://coveralls.io/repos/thumbor/libthumbor/badge.svg?branch=master&service=github)](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