pax_global_header00006660000000000000000000000064142171650470014521gustar00rootroot0000000000000052 comment=3c973e2e0abc6d8beedc0bfc6024d0171353fae5 libthumbor-2.0.2/000077500000000000000000000000001421716504700136715ustar00rootroot00000000000000libthumbor-2.0.2/.coveragerc000066400000000000000000000004501421716504700160110ustar00rootroot00000000000000[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 libthumbor-2.0.2/.flake8000066400000000000000000000002451421716504700150450ustar00rootroot00000000000000[flake8] ignore = W801,E501,W605,W504,W606,W503,E203,E266 max-line-length = 88 max-complexity = 20 select = B,C,E,F,W,T4 exclude = ./.tox/*,./build/*,./docs/conf.py libthumbor-2.0.2/.gitignore000066400000000000000000000001311421716504700156540ustar00rootroot00000000000000*.swp *.pyc .DS_Store dist libthumbor.egg-info *.db .coverage /tests/testproj/db.sqlite3 libthumbor-2.0.2/.pylintrc000066400000000000000000000362201421716504700155410ustar00rootroot00000000000000[MASTER] # A comma-separated list of package or module names from where C extensions may # be loaded. Extensions are loading into the active Python interpreter and may # run arbitrary code. extension-pkg-whitelist= # Add files or directories to the blacklist. They should be base names, not # paths. ignore=CVS # Add files or directories matching the regex patterns to the blacklist. The # regex matches against base names, not paths. ignore-patterns= # Python code to execute, usually for sys.path manipulation such as # pygtk.require(). #init-hook= # Use multiple processes to speed up Pylint. Specifying 0 will auto-detect the # number of processors available to use. jobs=1 # Control the amount of potential inferred values when inferring a single # object. This can help the performance when dealing with large functions or # complex, nested conditions. limit-inference-results=100 # List of plugins (as comma separated values of python module names) to load, # usually to register additional checkers. load-plugins= # Pickle collected data for later comparisons. persistent=yes # Specify a configuration file. #rcfile= # When enabled, pylint would attempt to guess common misconfiguration and emit # user-friendly hints instead of false-positive error messages. suggestion-mode=yes # Allow loading of arbitrary C extensions. Extensions are imported into the # active Python interpreter and may run arbitrary code. unsafe-load-any-extension=no [MESSAGES CONTROL] # Disable the message, report, category or checker with the given id(s). You # can either give multiple identifiers separated by comma (,) or put this # option multiple times (only on the command line, not in the configuration # file where it should appear only once). You can also use "--disable=all" to # disable everything first and then reenable specific checks. For example, if # you want to run only the similarities checker, you can use "--disable=all # --enable=similarities". If you want to run only the classes checker, but have # no Warning level messages displayed, use "--disable=all --enable=classes # --disable=W". disable= missing-function-docstring, missing-module-docstring, missing-class-docstring, bad-continuation # Only show warnings with the listed confidence levels. Leave empty to show # all. Valid levels: HIGH, INFERENCE, INFERENCE_FAILURE, UNDEFINED. confidence= enable= [REPORTS] # Python expression which should return a score less than or equal to 10. You # have access to the variables 'error', 'warning', 'refactor', and 'convention' # which contain the number of messages in each category, as well as 'statement' # which is the total number of statements analyzed. This score is used by the # global evaluation report (RP0004). evaluation=10.0 - ((float(5 * error + warning + refactor + convention) / statement) * 10) # Template used to display messages. This is a python new-style format string # used to format the message information. See doc for all details. #msg-template= # Set the output format. Available formats are text, parseable, colorized, json # and msvs (visual studio). You can also give a reporter class, e.g. # mypackage.mymodule.MyReporterClass. output-format=text # Tells whether to display a full report or only the messages. reports=no # Activate the evaluation score. score=yes [REFACTORING] # Maximum number of nested blocks for function / method body max-nested-blocks=5 # Complete name of functions that never returns. When checking for # inconsistent-return-statements if a never returning function is called then # it will be considered as an explicit return statement and no message will be # printed. never-returning-functions=sys.exit [LOGGING] # Format style used to check logging format string. `old` means using % # formatting, `new` is for `{}` formatting,and `fstr` is for f-strings. logging-format-style=old # Logging modules to check that the string format arguments are in logging # function parameter format. logging-modules=logging [STRING] # This flag controls whether the implicit-str-concat-in-sequence should # generate a warning on implicit string concatenation in sequences defined over # several lines. check-str-concat-over-line-jumps=no [MISCELLANEOUS] # List of note tags to take in consideration, separated by a comma. notes=FIXME, XXX, TODO [SPELLING] # Limits count of emitted suggestions for spelling mistakes. max-spelling-suggestions=4 # Spelling dictionary name. Available dictionaries: none. To make it work, # install the python-enchant package. spelling-dict= # List of comma separated words that should not be checked. spelling-ignore-words= # A path to a file that contains the private dictionary; one word per line. spelling-private-dict-file= # Tells whether to store unknown words to the private dictionary (see the # --spelling-private-dict-file option) instead of raising a message. spelling-store-unknown-words=no [BASIC] # Naming style matching correct argument names. argument-naming-style=snake_case # Regular expression matching correct argument names. Overrides argument- # naming-style. #argument-rgx= # Naming style matching correct attribute names. attr-naming-style=snake_case # Regular expression matching correct attribute names. Overrides attr-naming- # style. #attr-rgx= # Bad variable names which should always be refused, separated by a comma. bad-names=foo, bar, baz, toto, tutu, tata # Naming style matching correct class attribute names. class-attribute-naming-style=any # Regular expression matching correct class attribute names. Overrides class- # attribute-naming-style. #class-attribute-rgx= # Naming style matching correct class names. class-naming-style=PascalCase # Regular expression matching correct class names. Overrides class-naming- # style. #class-rgx= # Naming style matching correct constant names. const-naming-style=UPPER_CASE # Regular expression matching correct constant names. Overrides const-naming- # style. #const-rgx= # Minimum line length for functions/classes that require docstrings, shorter # ones are exempt. docstring-min-length=-1 # Naming style matching correct function names. function-naming-style=snake_case # Regular expression matching correct function names. Overrides function- # naming-style. #function-rgx= # Good variable names which should always be accepted, separated by a comma. good-names=i, j, k, ex, Run, _ # Include a hint for the correct naming format with invalid-name. include-naming-hint=no # Naming style matching correct inline iteration names. inlinevar-naming-style=any # Regular expression matching correct inline iteration names. Overrides # inlinevar-naming-style. #inlinevar-rgx= # Naming style matching correct method names. method-naming-style=snake_case # Regular expression matching correct method names. Overrides method-naming- # style. #method-rgx= # Naming style matching correct module names. module-naming-style=snake_case # Regular expression matching correct module names. Overrides module-naming- # style. #module-rgx= # Colon-delimited sets of names that determine each other's naming style when # the name regexes allow several styles. name-group= # Regular expression which should only match function or class names that do # not require a docstring. no-docstring-rgx=^_ # List of decorators that produce properties, such as abc.abstractproperty. Add # to this list to register other decorators that produce valid properties. # These decorators are taken in consideration only for invalid-name. property-classes=abc.abstractproperty # Naming style matching correct variable names. variable-naming-style=snake_case # Regular expression matching correct variable names. Overrides variable- # naming-style. #variable-rgx= [FORMAT] # Expected format of line ending, e.g. empty (any line ending), LF or CRLF. expected-line-ending-format= # Regexp for a line that is allowed to be longer than the limit. ignore-long-lines=^\s*(# )??$ # Number of spaces of indent required inside a hanging or continued line. indent-after-paren=4 # String used as indentation unit. This is usually " " (4 spaces) or "\t" (1 # tab). indent-string=' ' # Maximum number of characters on a single line. max-line-length=88 # Maximum number of lines in a module. max-module-lines=1000 # List of optional constructs for which whitespace checking is disabled. `dict- # separator` is used to allow tabulation in dicts, etc.: {1 : 1,\n222: 2}. # `trailing-comma` allows a space between comma and closing bracket: (a, ). # `empty-line` allows space-only lines. no-space-check=trailing-comma, dict-separator # Allow the body of a class to be on the same line as the declaration if body # contains single statement. single-line-class-stmt=no # Allow the body of an if to be on the same line as the test if there is no # else. single-line-if-stmt=no [SIMILARITIES] # Ignore comments when computing similarities. ignore-comments=yes # Ignore docstrings when computing similarities. ignore-docstrings=yes # Ignore imports when computing similarities. ignore-imports=no # Minimum lines number of a similarity. min-similarity-lines=4 [VARIABLES] # List of additional names supposed to be defined in builtins. Remember that # you should avoid defining new builtins when possible. additional-builtins= # Tells whether unused global variables should be treated as a violation. allow-global-unused-variables=yes # List of strings which can identify a callback function by name. A callback # name must start or end with one of those strings. callbacks=cb_, _cb # A regular expression matching the name of dummy variables (i.e. expected to # not be used). dummy-variables-rgx=_+$|(_[a-zA-Z0-9_]*[a-zA-Z0-9]+?$)|dummy|^ignored_|^unused_ # Argument names that match this expression will be ignored. Default to name # with leading underscore. ignored-argument-names=_.*|^ignored_|^unused_ # Tells whether we should check for unused import in __init__ files. init-import=no # List of qualified module names which can have objects that can redefine # builtins. redefining-builtins-modules=six.moves,past.builtins,future.builtins,builtins,io [TYPECHECK] # List of decorators that produce context managers, such as # contextlib.contextmanager. Add to this list to register other decorators that # produce valid context managers. contextmanager-decorators=contextlib.contextmanager # List of members which are set dynamically and missed by pylint inference # system, and so shouldn't trigger E1101 when accessed. Python regular # expressions are accepted. generated-members= # Tells whether missing members accessed in mixin class should be ignored. A # mixin class is detected if its name ends with "mixin" (case insensitive). ignore-mixin-members=yes # Tells whether to warn about missing members when the owner of the attribute # is inferred to be None. ignore-none=yes # This flag controls whether pylint should warn about no-member and similar # checks whenever an opaque object is returned when inferring. The inference # can return multiple potential results while evaluating a Python object, but # some branches might not be evaluated, which results in partial inference. In # that case, it might be useful to still emit no-member and other checks for # the rest of the inferred objects. ignore-on-opaque-inference=yes # List of class names for which member attributes should not be checked (useful # for classes with dynamically set attributes). This supports the use of # qualified names. ignored-classes=optparse.Values,thread._local,_thread._local # List of module names for which member attributes should not be checked # (useful for modules/projects where namespaces are manipulated during runtime # and thus existing member attributes cannot be deduced by static analysis). It # supports qualified module names, as well as Unix pattern matching. ignored-modules= # Show a hint with possible names when a member name was not found. The aspect # of finding the hint is based on edit distance. missing-member-hint=yes # The minimum edit distance a name should have in order to be considered a # similar match for a missing member name. missing-member-hint-distance=1 # The total number of similar names that should be taken in consideration when # showing a hint for a missing member. missing-member-max-choices=1 # List of decorators that change the signature of a decorated function. signature-mutators= [DESIGN] # Maximum number of arguments for function / method. max-args=5 # Maximum number of attributes for a class (see R0902). max-attributes=7 # Maximum number of boolean expressions in an if statement (see R0916). max-bool-expr=5 # Maximum number of branch for function / method body. max-branches=12 # Maximum number of locals for function / method body. max-locals=15 # Maximum number of parents for a class (see R0901). max-parents=7 # Maximum number of public methods for a class (see R0904). max-public-methods=20 # Maximum number of return / yield for function / method body. max-returns=6 # Maximum number of statements in function / method body. max-statements=50 # Minimum number of public methods for a class (see R0903). min-public-methods=2 [CLASSES] # List of method names used to declare (i.e. assign) instance attributes. defining-attr-methods=__init__, __new__, setUp, __post_init__ # List of member names, which should be excluded from the protected access # warning. exclude-protected=_asdict, _fields, _replace, _source, _make # List of valid names for the first argument in a class method. valid-classmethod-first-arg=cls # List of valid names for the first argument in a metaclass class method. valid-metaclass-classmethod-first-arg=cls [IMPORTS] # List of modules that can be imported at any level, not just the top level # one. allow-any-import-level= # Allow wildcard imports from modules that define __all__. allow-wildcard-with-all=no # Analyse import fallback blocks. This can be used to support both Python 2 and # 3 compatible code, which means that the block might have code that exists # only in one or another interpreter, leading to false positives when analysed. analyse-fallback-blocks=no # Deprecated modules which should not be used, separated by a comma. deprecated-modules=optparse,tkinter.tix # Create a graph of external dependencies in the given file (report RP0402 must # not be disabled). ext-import-graph= # Create a graph of every (i.e. internal and external) dependencies in the # given file (report RP0402 must not be disabled). import-graph= # Create a graph of internal dependencies in the given file (report RP0402 must # not be disabled). int-import-graph= # Force import order to recognize a module as part of the standard # compatibility libraries. known-standard-library= # Force import order to recognize a module as part of a third party library. known-third-party=enchant # Couples of modules and preferred modules, separated by a comma. preferred-modules= [EXCEPTIONS] # Exceptions that will emit a warning when being caught. Defaults to # "BaseException, Exception". overgeneral-exceptions=BaseException, Exception libthumbor-2.0.2/.python-version000066400000000000000000000000261421716504700166740ustar00rootroot000000000000003.8.1/envs/libthumbor libthumbor-2.0.2/.travis.yml000066400000000000000000000004541421716504700160050ustar00rootroot00000000000000language: python python: - 3.6 - 3.7 - 3.8 cache: directories: - $HOME/.cache/pip matrix: fast_finish: true install: - pip install --upgrade pip - pip install poetry - cd $TRAVIS_BUILD_DIR && make setup - pip install coveralls script: make ci_test after_success: coveralls libthumbor-2.0.2/CONTRIBUTING000066400000000000000000000023111421716504700155200ustar00rootroot00000000000000So 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/). libthumbor-2.0.2/LICENSE000066400000000000000000000020641421716504700147000ustar00rootroot00000000000000The 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. libthumbor-2.0.2/MANIFEST.in000066400000000000000000000000311421716504700154210ustar00rootroot00000000000000prune dist prune build libthumbor-2.0.2/Makefile000066400000000000000000000005521421716504700153330ustar00rootroot00000000000000COVERAGE = $(or $(shell which coverage), $(shell which python-coverage), coverage) test ci_test: unit coverage flake8 pylint unit: @poetry run pytest -n `nproc` --cov=libthumbor tests/ coverage: @$(COVERAGE) report -m --fail-under=75 setup: @poetry install flake flake8: @poetry run flake8 pylint lint: @poetry run pylint --exit-zero libthumbor tests libthumbor-2.0.2/README.md000066400000000000000000000016631421716504700151560ustar00rootroot00000000000000[![Build Status](https://secure.travis-ci.org/thumbor/libthumbor.png)](http://travis-ci.org/thumbor/libthumbor) [![Coverage Status](https://coveralls.io/repos/github/thumbor/libthumbor/badge.svg?branch=master)](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). ## Using it ```python 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 libthumbor-2.0.2/libthumbor/000077500000000000000000000000001421716504700160405ustar00rootroot00000000000000libthumbor-2.0.2/libthumbor/__init__.py000066400000000000000000000010021421716504700201420ustar00rootroot00000000000000#!/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@gmail.com """libthumbor is the library used to access thumbor's images in python""" from libthumbor.crypto import CryptoURL # NOQA from libthumbor.url import Url # NOQA from libthumbor.url_signers.base64_hmac_sha1 import UrlSigner as Signer # NOQA libthumbor-2.0.2/libthumbor/crypto.py000066400000000000000000000026571421716504700177440ustar00rootroot00000000000000#!/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@gmail.com """Encrypted URLs for thumbor encryption.""" from __future__ import absolute_import import base64 import hashlib import hmac from six import PY3, b, text_type from libthumbor.url import plain_image_url, unsafe_url class CryptoURL: """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. """ if isinstance(key, text_type): key = b(key) self.key = key self.hmac = hmac.new(self.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) return self.generate_new(options) libthumbor-2.0.2/libthumbor/django/000077500000000000000000000000001421716504700173025ustar00rootroot00000000000000libthumbor-2.0.2/libthumbor/django/__init__.py000066400000000000000000000000001421716504700214010ustar00rootroot00000000000000libthumbor-2.0.2/libthumbor/django/urls.py000066400000000000000000000003031421716504700206350ustar00rootroot00000000000000from django.urls import path from libthumbor.django.views import generate_url urlpatterns = [ # pylint: disable=invalid-name path("gen_url/", generate_url, name="generate_thumbor_url"), ] libthumbor-2.0.2/libthumbor/django/views.py000066400000000000000000000047671421716504700210270ustar00rootroot00000000000000#!/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@gmail.com """Generic view for create thumbor encrypted urls.""" import logging from django.conf import settings from django.http import (HttpResponse, HttpResponseBadRequest, HttpResponseNotAllowed) 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: error_message = "The width value '%s' is not an integer." % args["width"] try: if "height" in args: args["height"] = int(args["height"]) except ValueError: 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: error_message = """ Missing values for cropping. Expected all 'crop_left', 'crop_top', 'crop_right', 'crop_bottom' values. """ except ValueError: 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 error: error_message = str(error) logging.warning(error_message) return HttpResponseBadRequest(error_message) libthumbor-2.0.2/libthumbor/url.py000066400000000000000000000214641421716504700172230ustar00rootroot00000000000000#!/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@gmail.com """URL composer to create options-based URLs for thumbor encryption.""" # pylint: disable=line-too-long,too-many-branches,too-many-locals,too-many-arguments 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) ) calculate_fit_in(options, url_parts) 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 def calculate_fit_in(options, url_parts): 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." ) class Url: unsafe_or_hash = r"(?:(?:(?Punsafe)|(?P.+?))/)?" debug = r"(?:(?Pdebug)/)?" meta = r"(?:(?Pmeta)/)?" trim = r"(?:(?Ptrim(?::(?:top-left|bottom-right))?(?::\d+)?)/)?" crop = r"(?:(?P\d+)x(?P\d+):(?P\d+)x(?P\d+)/)?" fit_in = r"(?:(?Padaptive-)?(?Pfull-)?(?Pfit-in)/)?" dimensions = r"(?:(?P-)?(?P(?:\d+|orig))?x(?P-)?(?P(?:\d+|orig))?/)?" halign = r"(?:(?Pleft|right|center)/)?" valign = r"(?:(?Ptop|bottom|middle)/)?" smart = r"(?:(?Psmart)/)?" filters = r"(?:filters:(?P.+?\))/)?" image = r"(?P.+)" 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) libthumbor-2.0.2/libthumbor/url_signers/000077500000000000000000000000001421716504700203745ustar00rootroot00000000000000libthumbor-2.0.2/libthumbor/url_signers/__init__.py000066400000000000000000000012631421716504700225070ustar00rootroot00000000000000#!/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@corp.globo.com from six import text_type class BaseUrlSigner: 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() libthumbor-2.0.2/libthumbor/url_signers/base64_hmac_sha1.py000066400000000000000000000007021421716504700237350ustar00rootroot00000000000000# -*- 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() ) libthumbor-2.0.2/poetry.lock000066400000000000000000001046631421716504700160770ustar00rootroot00000000000000[[package]] category = "dev" description = "apipkg: namespace control and lazy-import mechanism" name = "apipkg" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.5" [[package]] category = "dev" description = "A small Python module for determining appropriate platform-specific dirs, e.g. a \"user data dir\"." name = "appdirs" optional = false python-versions = "*" version = "1.4.3" [[package]] category = "dev" description = "ASGI specs, helper code, and adapters" name = "asgiref" optional = false python-versions = "*" version = "3.2.3" [package.extras] tests = ["pytest (>=4.3.0,<4.4.0)", "pytest-asyncio (>=0.10.0,<0.11.0)"] [[package]] category = "dev" description = "An abstract syntax tree for Python with inference support." name = "astroid" optional = false python-versions = ">=3.5.*" version = "2.3.3" [package.dependencies] lazy-object-proxy = ">=1.4.0,<1.5.0" six = ">=1.12,<2.0" wrapt = ">=1.11.0,<1.12.0" [package.dependencies.typed-ast] python = "<3.8" version = ">=1.4.0,<1.5" [[package]] category = "dev" description = "Atomic file writes." marker = "sys_platform == \"win32\"" name = "atomicwrites" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.3.0" [[package]] category = "dev" description = "Classes Without Boilerplate" name = "attrs" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "19.3.0" [package.extras] azure-pipelines = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "pytest-azurepipelines"] dev = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface", "sphinx", "pre-commit"] docs = ["sphinx", "zope.interface"] tests = ["coverage", "hypothesis", "pympler", "pytest (>=4.3.0)", "six", "zope.interface"] [[package]] category = "dev" description = "The uncompromising code formatter." name = "black" optional = false python-versions = ">=3.6" version = "19.10b0" [package.dependencies] appdirs = "*" attrs = ">=18.1.0" click = ">=6.5" pathspec = ">=0.6,<1" regex = "*" toml = ">=0.9.4" typed-ast = ">=1.4.0" [package.extras] d = ["aiohttp (>=3.3.2)", "aiohttp-cors"] [[package]] category = "dev" description = "Composable command line interface toolkit" name = "click" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "7.0" [[package]] category = "dev" description = "Cross-platform colored terminal text." marker = "sys_platform == \"win32\"" name = "colorama" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.4.3" [[package]] category = "dev" description = "Code coverage measurement for Python" name = "coverage" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*, <4" version = "5.0.3" [package.extras] toml = ["toml"] [[package]] category = "dev" description = "A high-level Python Web framework that encourages rapid development and clean, pragmatic design." name = "django" optional = false python-versions = ">=3.6" version = "3.0.3" [package.dependencies] asgiref = ">=3.2,<4.0" pytz = "*" sqlparse = ">=0.2.2" [package.extras] argon2 = ["argon2-cffi (>=16.1.0)"] bcrypt = ["bcrypt"] [[package]] category = "dev" description = "Discover and load entry points from installed packages." name = "entrypoints" optional = false python-versions = ">=2.7" version = "0.3" [[package]] category = "dev" description = "execnet: rapid multi-Python deployment" name = "execnet" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.7.1" [package.dependencies] apipkg = ">=1.4" [package.extras] testing = ["pre-commit"] [[package]] category = "dev" description = "the modular source code checker: pep8, pyflakes and co" name = "flake8" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "3.7.9" [package.dependencies] entrypoints = ">=0.3.0,<0.4.0" mccabe = ">=0.6.0,<0.7.0" pycodestyle = ">=2.5.0,<2.6.0" pyflakes = ">=2.1.0,<2.2.0" [[package]] category = "dev" description = "Read metadata from Python packages" marker = "python_version < \"3.8\"" name = "importlib-metadata" optional = false python-versions = "!=3.0.*,!=3.1.*,!=3.2.*,!=3.3.*,!=3.4.*,>=2.7" version = "1.5.0" [package.dependencies] zipp = ">=0.5" [package.extras] docs = ["sphinx", "rst.linker"] testing = ["packaging", "importlib-resources"] [[package]] category = "dev" description = "A Python utility / library to sort Python imports." name = "isort" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "4.3.21" [package.extras] pipfile = ["pipreqs", "requirementslib"] pyproject = ["toml"] requirements = ["pipreqs", "pip-api"] xdg_home = ["appdirs (>=1.4.0)"] [[package]] category = "dev" description = "A fast and thorough lazy object proxy." name = "lazy-object-proxy" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.4.3" [[package]] category = "dev" description = "McCabe checker, plugin for flake8" name = "mccabe" optional = false python-versions = "*" version = "0.6.1" [[package]] category = "dev" description = "More routines for operating on iterables, beyond itertools" name = "more-itertools" optional = false python-versions = ">=3.5" version = "8.2.0" [[package]] category = "dev" description = "Core utilities for Python packages" name = "packaging" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "20.1" [package.dependencies] pyparsing = ">=2.0.2" six = "*" [[package]] category = "dev" description = "Utility library for gitignore style pattern matching of file paths." name = "pathspec" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*, !=3.4.*" version = "0.7.0" [[package]] category = "dev" description = "plugin and hook calling mechanisms for python" name = "pluggy" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "0.13.1" [package.dependencies] [package.dependencies.importlib-metadata] python = "<3.8" version = ">=0.12" [package.extras] dev = ["pre-commit", "tox"] [[package]] category = "dev" description = "preggy is an assertion library for Python.** What were you `expect`ing?" name = "preggy" optional = false python-versions = "*" version = "1.4.4" [package.dependencies] six = "*" unidecode = "*" [package.extras] tests = ["nose", "yanc", "coverage", "tox"] [[package]] category = "dev" description = "library with cross-python path, ini-parsing, io, code, log facilities" name = "py" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.8.1" [[package]] category = "dev" description = "Python style guide checker" name = "pycodestyle" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.5.0" [[package]] category = "dev" description = "passive checker of Python programs" name = "pyflakes" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.1.1" [[package]] category = "dev" description = "python code static checker" name = "pylint" optional = false python-versions = ">=3.5.*" version = "2.4.4" [package.dependencies] astroid = ">=2.3.0,<2.4" colorama = "*" isort = ">=4.2.5,<5" mccabe = ">=0.6,<0.7" [[package]] category = "dev" description = "Python parsing module" name = "pyparsing" optional = false python-versions = ">=2.6, !=3.0.*, !=3.1.*, !=3.2.*" version = "2.4.6" [[package]] category = "dev" description = "pytest: simple powerful testing with Python" name = "pytest" optional = false python-versions = ">=3.5" version = "5.3.5" [package.dependencies] atomicwrites = ">=1.0" attrs = ">=17.4.0" colorama = "*" more-itertools = ">=4.0.0" packaging = "*" pluggy = ">=0.12,<1.0" py = ">=1.5.0" wcwidth = "*" [package.dependencies.importlib-metadata] python = "<3.8" version = ">=0.12" [package.extras] checkqa-mypy = ["mypy (v0.761)"] testing = ["argcomplete", "hypothesis (>=3.56)", "mock", "nose", "requests", "xmlschema"] [[package]] category = "dev" description = "Pytest plugin for measuring coverage." name = "pytest-cov" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "2.8.1" [package.dependencies] coverage = ">=4.4" pytest = ">=3.6" [package.extras] testing = ["fields", "hunter", "process-tests (2.0.2)", "six", "virtualenv"] [[package]] category = "dev" description = "run tests in isolated forked subprocesses" name = "pytest-forked" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.1.3" [package.dependencies] pytest = ">=3.1.0" [[package]] category = "dev" description = "A pytest plugin that limits the output to just the things you need." name = "pytest-tldr" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "0.2.1" [package.dependencies] pytest = ">=3.5.0" [[package]] category = "dev" description = "pytest xdist plugin for distributed testing and loop-on-failing modes" name = "pytest-xdist" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.31.0" [package.dependencies] execnet = ">=1.1" pytest = ">=4.4.0" pytest-forked = "*" six = "*" [package.extras] testing = ["filelock"] [[package]] category = "dev" description = "World timezone definitions, modern and historical" name = "pytz" optional = false python-versions = "*" version = "2019.3" [[package]] category = "dev" description = "Alternative regular expression module, to replace re." name = "regex" optional = false python-versions = "*" version = "2020.1.8" [[package]] category = "main" description = "Python 2 and 3 compatibility utilities" name = "six" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*" version = "1.14.0" [[package]] category = "dev" description = "Non-validating SQL parser" name = "sqlparse" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "0.3.0" [[package]] category = "dev" description = "Python Library for Tom's Obvious, Minimal Language" name = "toml" optional = false python-versions = "*" version = "0.10.0" [[package]] category = "dev" description = "a fork of Python 2 and 3 ast modules with type comment support" name = "typed-ast" optional = false python-versions = "*" version = "1.4.1" [[package]] category = "dev" description = "ASCII transliterations of Unicode text" name = "unidecode" optional = false python-versions = ">=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*" version = "1.1.1" [[package]] category = "dev" description = "Measures number of Terminal column cells of wide-character codes" name = "wcwidth" optional = false python-versions = "*" version = "0.1.8" [[package]] category = "dev" description = "Module for decorators, wrappers and monkey patching." name = "wrapt" optional = false python-versions = "*" version = "1.11.2" [[package]] category = "dev" description = "Backport of pathlib-compatible object wrapper for zip files" marker = "python_version < \"3.8\"" name = "zipp" optional = false python-versions = ">=3.6" version = "2.2.0" [package.extras] docs = ["sphinx", "jaraco.packaging (>=3.2)", "rst.linker (>=1.9)"] testing = ["jaraco.itertools"] [metadata] content-hash = "038e49e4b1c5ff16295dceb1761723c2e5f6ff6bd4036839394a3ac45ab11d09" python-versions = "^3.7" [metadata.files] apipkg = [ {file = "apipkg-1.5-py2.py3-none-any.whl", hash = "sha256:58587dd4dc3daefad0487f6d9ae32b4542b185e1c36db6993290e7c41ca2b47c"}, {file = "apipkg-1.5.tar.gz", hash = "sha256:37228cda29411948b422fae072f57e31d3396d2ee1c9783775980ee9c9990af6"}, ] appdirs = [ {file = "appdirs-1.4.3-py2.py3-none-any.whl", hash = "sha256:d8b24664561d0d34ddfaec54636d502d7cea6e29c3eaf68f3df6180863e2166e"}, {file = "appdirs-1.4.3.tar.gz", hash = "sha256:9e5896d1372858f8dd3344faf4e5014d21849c756c8d5701f78f8a103b372d92"}, ] asgiref = [ {file = "asgiref-3.2.3-py2.py3-none-any.whl", hash = "sha256:ea448f92fc35a0ef4b1508f53a04c4670255a3f33d22a81c8fc9c872036adbe5"}, {file = "asgiref-3.2.3.tar.gz", hash = "sha256:7e06d934a7718bf3975acbf87780ba678957b87c7adc056f13b6215d610695a0"}, ] astroid = [ {file = "astroid-2.3.3-py3-none-any.whl", hash = "sha256:840947ebfa8b58f318d42301cf8c0a20fd794a33b61cc4638e28e9e61ba32f42"}, {file = "astroid-2.3.3.tar.gz", hash = "sha256:71ea07f44df9568a75d0f354c49143a4575d90645e9fead6dfb52c26a85ed13a"}, ] atomicwrites = [ {file = "atomicwrites-1.3.0-py2.py3-none-any.whl", hash = "sha256:03472c30eb2c5d1ba9227e4c2ca66ab8287fbfbbda3888aa93dc2e28fc6811b4"}, {file = "atomicwrites-1.3.0.tar.gz", hash = "sha256:75a9445bac02d8d058d5e1fe689654ba5a6556a1dfd8ce6ec55a0ed79866cfa6"}, ] attrs = [ {file = "attrs-19.3.0-py2.py3-none-any.whl", hash = "sha256:08a96c641c3a74e44eb59afb61a24f2cb9f4d7188748e76ba4bb5edfa3cb7d1c"}, {file = "attrs-19.3.0.tar.gz", hash = "sha256:f7b7ce16570fe9965acd6d30101a28f62fb4a7f9e926b3bbc9b61f8b04247e72"}, ] black = [ {file = "black-19.10b0-py36-none-any.whl", hash = "sha256:1b30e59be925fafc1ee4565e5e08abef6b03fe455102883820fe5ee2e4734e0b"}, {file = "black-19.10b0.tar.gz", hash = "sha256:c2edb73a08e9e0e6f65a0e6af18b059b8b1cdd5bef997d7a0b181df93dc81539"}, ] click = [ {file = "Click-7.0-py2.py3-none-any.whl", hash = "sha256:2335065e6395b9e67ca716de5f7526736bfa6ceead690adf616d925bdc622b13"}, {file = "Click-7.0.tar.gz", hash = "sha256:5b94b49521f6456670fdb30cd82a4eca9412788a93fa6dd6df72c94d5a8ff2d7"}, ] colorama = [ {file = "colorama-0.4.3-py2.py3-none-any.whl", hash = "sha256:7d73d2a99753107a36ac6b455ee49046802e59d9d076ef8e47b61499fa29afff"}, {file = "colorama-0.4.3.tar.gz", hash = "sha256:e96da0d330793e2cb9485e9ddfd918d456036c7149416295932478192f4436a1"}, ] coverage = [ {file = "coverage-5.0.3-cp27-cp27m-macosx_10_12_x86_64.whl", hash = "sha256:cc1109f54a14d940b8512ee9f1c3975c181bbb200306c6d8b87d93376538782f"}, {file = "coverage-5.0.3-cp27-cp27m-macosx_10_13_intel.whl", hash = "sha256:be18f4ae5a9e46edae3f329de2191747966a34a3d93046dbdf897319923923bc"}, {file = "coverage-5.0.3-cp27-cp27m-manylinux1_i686.whl", hash = "sha256:3230d1003eec018ad4a472d254991e34241e0bbd513e97a29727c7c2f637bd2a"}, {file = "coverage-5.0.3-cp27-cp27m-manylinux1_x86_64.whl", hash = "sha256:e69215621707119c6baf99bda014a45b999d37602cb7043d943c76a59b05bf52"}, {file = "coverage-5.0.3-cp27-cp27m-win32.whl", hash = "sha256:1daa3eceed220f9fdb80d5ff950dd95112cd27f70d004c7918ca6dfc6c47054c"}, {file = "coverage-5.0.3-cp27-cp27m-win_amd64.whl", hash = "sha256:51bc7710b13a2ae0c726f69756cf7ffd4362f4ac36546e243136187cfcc8aa73"}, {file = "coverage-5.0.3-cp27-cp27mu-manylinux1_i686.whl", hash = "sha256:9bea19ac2f08672636350f203db89382121c9c2ade85d945953ef3c8cf9d2a68"}, {file = "coverage-5.0.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:5012d3b8d5a500834783689a5d2292fe06ec75dc86ee1ccdad04b6f5bf231691"}, {file = "coverage-5.0.3-cp35-cp35m-macosx_10_12_x86_64.whl", hash = "sha256:d513cc3db248e566e07a0da99c230aca3556d9b09ed02f420664e2da97eac301"}, {file = "coverage-5.0.3-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:3dbb72eaeea5763676a1a1efd9b427a048c97c39ed92e13336e726117d0b72bf"}, {file = "coverage-5.0.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:15cf13a6896048d6d947bf7d222f36e4809ab926894beb748fc9caa14605d9c3"}, {file = "coverage-5.0.3-cp35-cp35m-win32.whl", hash = "sha256:fca1669d464f0c9831fd10be2eef6b86f5ebd76c724d1e0706ebdff86bb4adf0"}, {file = "coverage-5.0.3-cp35-cp35m-win_amd64.whl", hash = "sha256:1e44a022500d944d42f94df76727ba3fc0a5c0b672c358b61067abb88caee7a0"}, {file = "coverage-5.0.3-cp36-cp36m-macosx_10_13_x86_64.whl", hash = "sha256:b26aaf69713e5674efbde4d728fb7124e429c9466aeaf5f4a7e9e699b12c9fe2"}, {file = "coverage-5.0.3-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:722e4557c8039aad9592c6a4213db75da08c2cd9945320220634f637251c3894"}, {file = "coverage-5.0.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:7afad9835e7a651d3551eab18cbc0fdb888f0a6136169fbef0662d9cdc9987cf"}, {file = "coverage-5.0.3-cp36-cp36m-win32.whl", hash = "sha256:25dbf1110d70bab68a74b4b9d74f30e99b177cde3388e07cc7272f2168bd1477"}, {file = "coverage-5.0.3-cp36-cp36m-win_amd64.whl", hash = "sha256:c312e57847db2526bc92b9bfa78266bfbaabac3fdcd751df4d062cd4c23e46dc"}, {file = "coverage-5.0.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:a8b8ac7876bc3598e43e2603f772d2353d9931709345ad6c1149009fd1bc81b8"}, {file = "coverage-5.0.3-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:527b4f316e6bf7755082a783726da20671a0cc388b786a64417780b90565b987"}, {file = "coverage-5.0.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d649dc0bcace6fcdb446ae02b98798a856593b19b637c1b9af8edadf2b150bea"}, {file = "coverage-5.0.3-cp37-cp37m-win32.whl", hash = "sha256:cd60f507c125ac0ad83f05803063bed27e50fa903b9c2cfee3f8a6867ca600fc"}, {file = "coverage-5.0.3-cp37-cp37m-win_amd64.whl", hash = "sha256:c60097190fe9dc2b329a0eb03393e2e0829156a589bd732e70794c0dd804258e"}, {file = "coverage-5.0.3-cp38-cp38-macosx_10_13_x86_64.whl", hash = "sha256:d7008a6796095a79544f4da1ee49418901961c97ca9e9d44904205ff7d6aa8cb"}, {file = "coverage-5.0.3-cp38-cp38-manylinux1_i686.whl", hash = "sha256:ea9525e0fef2de9208250d6c5aeeee0138921057cd67fcef90fbed49c4d62d37"}, {file = "coverage-5.0.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:c62a2143e1313944bf4a5ab34fd3b4be15367a02e9478b0ce800cb510e3bbb9d"}, {file = "coverage-5.0.3-cp38-cp38m-win32.whl", hash = "sha256:b0840b45187699affd4c6588286d429cd79a99d509fe3de0f209594669bb0954"}, {file = "coverage-5.0.3-cp38-cp38m-win_amd64.whl", hash = "sha256:76e2057e8ffba5472fd28a3a010431fd9e928885ff480cb278877c6e9943cc2e"}, {file = "coverage-5.0.3-cp39-cp39m-win32.whl", hash = "sha256:b63dd43f455ba878e5e9f80ba4f748c0a2156dde6e0e6e690310e24d6e8caf40"}, {file = "coverage-5.0.3-cp39-cp39m-win_amd64.whl", hash = "sha256:da93027835164b8223e8e5af2cf902a4c80ed93cb0909417234f4a9df3bcd9af"}, {file = "coverage-5.0.3.tar.gz", hash = "sha256:77afca04240c40450c331fa796b3eab6f1e15c5ecf8bf2b8bee9706cd5452fef"}, ] django = [ {file = "Django-3.0.3-py3-none-any.whl", hash = "sha256:c91c91a7ad6ef67a874a4f76f58ba534f9208412692a840e1d125eb5c279cb0a"}, {file = "Django-3.0.3.tar.gz", hash = "sha256:2f1ba1db8648484dd5c238fb62504777b7ad090c81c5f1fd8d5eb5ec21b5f283"}, ] entrypoints = [ {file = "entrypoints-0.3-py2.py3-none-any.whl", hash = "sha256:589f874b313739ad35be6e0cd7efde2a4e9b6fea91edcc34e58ecbb8dbe56d19"}, {file = "entrypoints-0.3.tar.gz", hash = "sha256:c70dd71abe5a8c85e55e12c19bd91ccfeec11a6e99044204511f9ed547d48451"}, ] execnet = [ {file = "execnet-1.7.1-py2.py3-none-any.whl", hash = "sha256:d4efd397930c46415f62f8a31388d6be4f27a91d7550eb79bc64a756e0056547"}, {file = "execnet-1.7.1.tar.gz", hash = "sha256:cacb9df31c9680ec5f95553976c4da484d407e85e41c83cb812aa014f0eddc50"}, ] flake8 = [ {file = "flake8-3.7.9-py2.py3-none-any.whl", hash = "sha256:49356e766643ad15072a789a20915d3c91dc89fd313ccd71802303fd67e4deca"}, {file = "flake8-3.7.9.tar.gz", hash = "sha256:45681a117ecc81e870cbf1262835ae4af5e7a8b08e40b944a8a6e6b895914cfb"}, ] importlib-metadata = [ {file = "importlib_metadata-1.5.0-py2.py3-none-any.whl", hash = "sha256:b97607a1a18a5100839aec1dc26a1ea17ee0d93b20b0f008d80a5a050afb200b"}, {file = "importlib_metadata-1.5.0.tar.gz", hash = "sha256:06f5b3a99029c7134207dd882428a66992a9de2bef7c2b699b5641f9886c3302"}, ] isort = [ {file = "isort-4.3.21-py2.py3-none-any.whl", hash = "sha256:6e811fcb295968434526407adb8796944f1988c5b65e8139058f2014cbe100fd"}, {file = "isort-4.3.21.tar.gz", hash = "sha256:54da7e92468955c4fceacd0c86bd0ec997b0e1ee80d97f67c35a78b719dccab1"}, ] lazy-object-proxy = [ {file = "lazy-object-proxy-1.4.3.tar.gz", hash = "sha256:f3900e8a5de27447acbf900b4750b0ddfd7ec1ea7fbaf11dfa911141bc522af0"}, {file = "lazy_object_proxy-1.4.3-cp27-cp27m-macosx_10_13_x86_64.whl", hash = "sha256:a2238e9d1bb71a56cd710611a1614d1194dc10a175c1e08d75e1a7bcc250d442"}, {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win32.whl", hash = "sha256:efa1909120ce98bbb3777e8b6f92237f5d5c8ea6758efea36a473e1d38f7d3e4"}, {file = "lazy_object_proxy-1.4.3-cp27-cp27m-win_amd64.whl", hash = "sha256:4677f594e474c91da97f489fea5b7daa17b5517190899cf213697e48d3902f5a"}, {file = "lazy_object_proxy-1.4.3-cp27-cp27mu-manylinux1_x86_64.whl", hash = "sha256:0c4b206227a8097f05c4dbdd323c50edf81f15db3b8dc064d08c62d37e1a504d"}, {file = "lazy_object_proxy-1.4.3-cp34-cp34m-manylinux1_x86_64.whl", hash = "sha256:d945239a5639b3ff35b70a88c5f2f491913eb94871780ebfabb2568bd58afc5a"}, {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win32.whl", hash = "sha256:9651375199045a358eb6741df3e02a651e0330be090b3bc79f6d0de31a80ec3e"}, {file = "lazy_object_proxy-1.4.3-cp34-cp34m-win_amd64.whl", hash = "sha256:eba7011090323c1dadf18b3b689845fd96a61ba0a1dfbd7f24b921398affc357"}, {file = "lazy_object_proxy-1.4.3-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:48dab84ebd4831077b150572aec802f303117c8cc5c871e182447281ebf3ac50"}, {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win32.whl", hash = "sha256:ca0a928a3ddbc5725be2dd1cf895ec0a254798915fb3a36af0964a0a4149e3db"}, {file = "lazy_object_proxy-1.4.3-cp35-cp35m-win_amd64.whl", hash = "sha256:194d092e6f246b906e8f70884e620e459fc54db3259e60cf69a4d66c3fda3449"}, {file = "lazy_object_proxy-1.4.3-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:97bb5884f6f1cdce0099f86b907aa41c970c3c672ac8b9c8352789e103cf3156"}, {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win32.whl", hash = "sha256:cb2c7c57005a6804ab66f106ceb8482da55f5314b7fcb06551db1edae4ad1531"}, {file = "lazy_object_proxy-1.4.3-cp36-cp36m-win_amd64.whl", hash = "sha256:8d859b89baf8ef7f8bc6b00aa20316483d67f0b1cbf422f5b4dc56701c8f2ffb"}, {file = "lazy_object_proxy-1.4.3-cp37-cp37m-macosx_10_13_x86_64.whl", hash = "sha256:1be7e4c9f96948003609aa6c974ae59830a6baecc5376c25c92d7d697e684c08"}, {file = "lazy_object_proxy-1.4.3-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:d74bb8693bf9cf75ac3b47a54d716bbb1a92648d5f781fc799347cfc95952383"}, {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win32.whl", hash = "sha256:9b15f3f4c0f35727d3a0fba4b770b3c4ebbb1fa907dbcc046a1d2799f3edd142"}, {file = "lazy_object_proxy-1.4.3-cp37-cp37m-win_amd64.whl", hash = "sha256:9254f4358b9b541e3441b007a0ea0764b9d056afdeafc1a5569eee1cc6c1b9ea"}, {file = "lazy_object_proxy-1.4.3-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:a6ae12d08c0bf9909ce12385803a543bfe99b95fe01e752536a60af2b7797c62"}, {file = "lazy_object_proxy-1.4.3-cp38-cp38-win32.whl", hash = "sha256:5541cada25cd173702dbd99f8e22434105456314462326f06dba3e180f203dfd"}, {file = "lazy_object_proxy-1.4.3-cp38-cp38-win_amd64.whl", hash = "sha256:59f79fef100b09564bc2df42ea2d8d21a64fdcda64979c0fa3db7bdaabaf6239"}, ] mccabe = [ {file = "mccabe-0.6.1-py2.py3-none-any.whl", hash = "sha256:ab8a6258860da4b6677da4bd2fe5dc2c659cff31b3ee4f7f5d64e79735b80d42"}, {file = "mccabe-0.6.1.tar.gz", hash = "sha256:dd8d182285a0fe56bace7f45b5e7d1a6ebcbf524e8f3bd87eb0f125271b8831f"}, ] more-itertools = [ {file = "more-itertools-8.2.0.tar.gz", hash = "sha256:b1ddb932186d8a6ac451e1d95844b382f55e12686d51ca0c68b6f61f2ab7a507"}, {file = "more_itertools-8.2.0-py3-none-any.whl", hash = "sha256:5dd8bcf33e5f9513ffa06d5ad33d78f31e1931ac9a18f33d37e77a180d393a7c"}, ] packaging = [ {file = "packaging-20.1-py2.py3-none-any.whl", hash = "sha256:170748228214b70b672c581a3dd610ee51f733018650740e98c7df862a583f73"}, {file = "packaging-20.1.tar.gz", hash = "sha256:e665345f9eef0c621aa0bf2f8d78cf6d21904eef16a93f020240b704a57f1334"}, ] pathspec = [ {file = "pathspec-0.7.0-py2.py3-none-any.whl", hash = "sha256:163b0632d4e31cef212976cf57b43d9fd6b0bac6e67c26015d611a647d5e7424"}, {file = "pathspec-0.7.0.tar.gz", hash = "sha256:562aa70af2e0d434367d9790ad37aed893de47f1693e4201fd1d3dca15d19b96"}, ] pluggy = [ {file = "pluggy-0.13.1-py2.py3-none-any.whl", hash = "sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d"}, {file = "pluggy-0.13.1.tar.gz", hash = "sha256:15b2acde666561e1298d71b523007ed7364de07029219b604cf808bfa1c765b0"}, ] preggy = [ {file = "preggy-1.4.4.tar.gz", hash = "sha256:25ba803afde4f35ef543a60915ced2e634926235064df717c3cb3e4e3eb4670c"}, ] py = [ {file = "py-1.8.1-py2.py3-none-any.whl", hash = "sha256:c20fdd83a5dbc0af9efd622bee9a5564e278f6380fffcacc43ba6f43db2813b0"}, {file = "py-1.8.1.tar.gz", hash = "sha256:5e27081401262157467ad6e7f851b7aa402c5852dbcb3dae06768434de5752aa"}, ] pycodestyle = [ {file = "pycodestyle-2.5.0-py2.py3-none-any.whl", hash = "sha256:95a2219d12372f05704562a14ec30bc76b05a5b297b21a5dfe3f6fac3491ae56"}, {file = "pycodestyle-2.5.0.tar.gz", hash = "sha256:e40a936c9a450ad81df37f549d676d127b1b66000a6c500caa2b085bc0ca976c"}, ] pyflakes = [ {file = "pyflakes-2.1.1-py2.py3-none-any.whl", hash = "sha256:17dbeb2e3f4d772725c777fabc446d5634d1038f234e77343108ce445ea69ce0"}, {file = "pyflakes-2.1.1.tar.gz", hash = "sha256:d976835886f8c5b31d47970ed689944a0262b5f3afa00a5a7b4dc81e5449f8a2"}, ] pylint = [ {file = "pylint-2.4.4-py3-none-any.whl", hash = "sha256:886e6afc935ea2590b462664b161ca9a5e40168ea99e5300935f6591ad467df4"}, {file = "pylint-2.4.4.tar.gz", hash = "sha256:3db5468ad013380e987410a8d6956226963aed94ecb5f9d3a28acca6d9ac36cd"}, ] pyparsing = [ {file = "pyparsing-2.4.6-py2.py3-none-any.whl", hash = "sha256:c342dccb5250c08d45fd6f8b4a559613ca603b57498511740e65cd11a2e7dcec"}, {file = "pyparsing-2.4.6.tar.gz", hash = "sha256:4c830582a84fb022400b85429791bc551f1f4871c33f23e44f353119e92f969f"}, ] pytest = [ {file = "pytest-5.3.5-py3-none-any.whl", hash = "sha256:ff615c761e25eb25df19edddc0b970302d2a9091fbce0e7213298d85fb61fef6"}, {file = "pytest-5.3.5.tar.gz", hash = "sha256:0d5fe9189a148acc3c3eb2ac8e1ac0742cb7618c084f3d228baaec0c254b318d"}, ] pytest-cov = [ {file = "pytest-cov-2.8.1.tar.gz", hash = "sha256:cc6742d8bac45070217169f5f72ceee1e0e55b0221f54bcf24845972d3a47f2b"}, {file = "pytest_cov-2.8.1-py2.py3-none-any.whl", hash = "sha256:cdbdef4f870408ebdbfeb44e63e07eb18bb4619fae852f6e760645fa36172626"}, ] pytest-forked = [ {file = "pytest-forked-1.1.3.tar.gz", hash = "sha256:1805699ed9c9e60cb7a8179b8d4fa2b8898098e82d229b0825d8095f0f261100"}, {file = "pytest_forked-1.1.3-py2.py3-none-any.whl", hash = "sha256:1ae25dba8ee2e56fb47311c9638f9e58552691da87e82d25b0ce0e4bf52b7d87"}, ] pytest-tldr = [ {file = "pytest-tldr-0.2.1.tar.gz", hash = "sha256:008b7d53cabbce9d49ee7a92754ed4adafc056bc49ae01b257c2ffb1f5ce2408"}, {file = "pytest_tldr-0.2.1-py2.py3-none-any.whl", hash = "sha256:dca4a464a002f389677f4c42f5b9c815aae43219d73ecfe6b7fffe2d190e38eb"}, ] pytest-xdist = [ {file = "pytest-xdist-1.31.0.tar.gz", hash = "sha256:7dc0d027d258cd0defc618fb97055fbd1002735ca7a6d17037018cf870e24011"}, {file = "pytest_xdist-1.31.0-py2.py3-none-any.whl", hash = "sha256:0f46020d3d9619e6d17a65b5b989c1ebbb58fc7b1da8fb126d70f4bac4dfeed1"}, ] pytz = [ {file = "pytz-2019.3-py2.py3-none-any.whl", hash = "sha256:1c557d7d0e871de1f5ccd5833f60fb2550652da6be2693c1e02300743d21500d"}, {file = "pytz-2019.3.tar.gz", hash = "sha256:b02c06db6cf09c12dd25137e563b31700d3b80fcc4ad23abb7a315f2789819be"}, ] regex = [ {file = "regex-2020.1.8-cp27-cp27m-win32.whl", hash = "sha256:4e8f02d3d72ca94efc8396f8036c0d3bcc812aefc28ec70f35bb888c74a25161"}, {file = "regex-2020.1.8-cp27-cp27m-win_amd64.whl", hash = "sha256:e6c02171d62ed6972ca8631f6f34fa3281d51db8b326ee397b9c83093a6b7242"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:4eae742636aec40cf7ab98171ab9400393360b97e8f9da67b1867a9ee0889b26"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:bd25bb7980917e4e70ccccd7e3b5740614f1c408a642c245019cff9d7d1b6149"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux2010_i686.whl", hash = "sha256:3e77409b678b21a056415da3a56abfd7c3ad03da71f3051bbcdb68cf44d3c34d"}, {file = "regex-2020.1.8-cp36-cp36m-manylinux2010_x86_64.whl", hash = "sha256:07b39bf943d3d2fe63d46281d8504f8df0ff3fe4c57e13d1656737950e53e525"}, {file = "regex-2020.1.8-cp36-cp36m-win32.whl", hash = "sha256:23e2c2c0ff50f44877f64780b815b8fd2e003cda9ce817a7fd00dea5600c84a0"}, {file = "regex-2020.1.8-cp36-cp36m-win_amd64.whl", hash = "sha256:27429b8d74ba683484a06b260b7bb00f312e7c757792628ea251afdbf1434003"}, {file = "regex-2020.1.8-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:0e182d2f097ea8549a249040922fa2b92ae28be4be4895933e369a525ba36576"}, {file = "regex-2020.1.8-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:e3cd21cc2840ca67de0bbe4071f79f031c81418deb544ceda93ad75ca1ee9f7b"}, {file = "regex-2020.1.8-cp37-cp37m-manylinux2010_i686.whl", hash = "sha256:ecc6de77df3ef68fee966bb8cb4e067e84d4d1f397d0ef6fce46913663540d77"}, {file = "regex-2020.1.8-cp37-cp37m-manylinux2010_x86_64.whl", hash = "sha256:26ff99c980f53b3191d8931b199b29d6787c059f2e029b2b0c694343b1708c35"}, {file = "regex-2020.1.8-cp37-cp37m-win32.whl", hash = "sha256:7bcd322935377abcc79bfe5b63c44abd0b29387f267791d566bbb566edfdd146"}, {file = "regex-2020.1.8-cp37-cp37m-win_amd64.whl", hash = "sha256:10671601ee06cf4dc1bc0b4805309040bb34c9af423c12c379c83d7895622bb5"}, {file = "regex-2020.1.8-cp38-cp38-manylinux1_i686.whl", hash = "sha256:98b8ed7bb2155e2cbb8b76f627b2fd12cf4b22ab6e14873e8641f266e0fb6d8f"}, {file = "regex-2020.1.8-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:6a6ba91b94427cd49cd27764679024b14a96874e0dc638ae6bdd4b1a3ce97be1"}, {file = "regex-2020.1.8-cp38-cp38-manylinux2010_i686.whl", hash = "sha256:6a6ae17bf8f2d82d1e8858a47757ce389b880083c4ff2498dba17c56e6c103b9"}, {file = "regex-2020.1.8-cp38-cp38-manylinux2010_x86_64.whl", hash = "sha256:0932941cdfb3afcbc26cc3bcf7c3f3d73d5a9b9c56955d432dbf8bbc147d4c5b"}, {file = "regex-2020.1.8-cp38-cp38-win32.whl", hash = "sha256:d58e4606da2a41659c84baeb3cfa2e4c87a74cec89a1e7c56bee4b956f9d7461"}, {file = "regex-2020.1.8-cp38-cp38-win_amd64.whl", hash = "sha256:e7c7661f7276507bce416eaae22040fd91ca471b5b33c13f8ff21137ed6f248c"}, {file = "regex-2020.1.8.tar.gz", hash = "sha256:d0f424328f9822b0323b3b6f2e4b9c90960b24743d220763c7f07071e0778351"}, ] six = [ {file = "six-1.14.0-py2.py3-none-any.whl", hash = "sha256:8f3cd2e254d8f793e7f3d6d9df77b92252b52637291d0f0da013c76ea2724b6c"}, {file = "six-1.14.0.tar.gz", hash = "sha256:236bdbdce46e6e6a3d61a337c0f8b763ca1e8717c03b369e87a7ec7ce1319c0a"}, ] sqlparse = [ {file = "sqlparse-0.3.0-py2.py3-none-any.whl", hash = "sha256:40afe6b8d4b1117e7dff5504d7a8ce07d9a1b15aeeade8a2d10f130a834f8177"}, {file = "sqlparse-0.3.0.tar.gz", hash = "sha256:7c3dca29c022744e95b547e867cee89f4fce4373f3549ccd8797d8eb52cdb873"}, ] toml = [ {file = "toml-0.10.0-py2.7.egg", hash = "sha256:f1db651f9657708513243e61e6cc67d101a39bad662eaa9b5546f789338e07a3"}, {file = "toml-0.10.0-py2.py3-none-any.whl", hash = "sha256:235682dd292d5899d361a811df37e04a8828a5b1da3115886b73cf81ebc9100e"}, {file = "toml-0.10.0.tar.gz", hash = "sha256:229f81c57791a41d65e399fc06bf0848bab550a9dfd5ed66df18ce5f05e73d5c"}, ] typed-ast = [ {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_i686.whl", hash = "sha256:73d785a950fc82dd2a25897d525d003f6378d1cb23ab305578394694202a58c3"}, {file = "typed_ast-1.4.1-cp35-cp35m-manylinux1_x86_64.whl", hash = "sha256:aaee9905aee35ba5905cfb3c62f3e83b3bec7b39413f0a7f19be4e547ea01ebb"}, {file = "typed_ast-1.4.1-cp35-cp35m-win32.whl", hash = "sha256:0c2c07682d61a629b68433afb159376e24e5b2fd4641d35424e462169c0a7919"}, {file = "typed_ast-1.4.1-cp35-cp35m-win_amd64.whl", hash = "sha256:4083861b0aa07990b619bd7ddc365eb7fa4b817e99cf5f8d9cf21a42780f6e01"}, {file = "typed_ast-1.4.1-cp36-cp36m-macosx_10_9_x86_64.whl", hash = "sha256:269151951236b0f9a6f04015a9004084a5ab0d5f19b57de779f908621e7d8b75"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_i686.whl", hash = "sha256:24995c843eb0ad11a4527b026b4dde3da70e1f2d8806c99b7b4a7cf491612652"}, {file = "typed_ast-1.4.1-cp36-cp36m-manylinux1_x86_64.whl", hash = "sha256:fe460b922ec15dd205595c9b5b99e2f056fd98ae8f9f56b888e7a17dc2b757e7"}, {file = "typed_ast-1.4.1-cp36-cp36m-win32.whl", hash = "sha256:4e3e5da80ccbebfff202a67bf900d081906c358ccc3d5e3c8aea42fdfdfd51c1"}, {file = "typed_ast-1.4.1-cp36-cp36m-win_amd64.whl", hash = "sha256:249862707802d40f7f29f6e1aad8d84b5aa9e44552d2cc17384b209f091276aa"}, {file = "typed_ast-1.4.1-cp37-cp37m-macosx_10_9_x86_64.whl", hash = "sha256:8ce678dbaf790dbdb3eba24056d5364fb45944f33553dd5869b7580cdbb83614"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_i686.whl", hash = "sha256:c9e348e02e4d2b4a8b2eedb48210430658df6951fa484e59de33ff773fbd4b41"}, {file = "typed_ast-1.4.1-cp37-cp37m-manylinux1_x86_64.whl", hash = "sha256:bcd3b13b56ea479b3650b82cabd6b5343a625b0ced5429e4ccad28a8973f301b"}, {file = "typed_ast-1.4.1-cp37-cp37m-win32.whl", hash = "sha256:d5d33e9e7af3b34a40dc05f498939f0ebf187f07c385fd58d591c533ad8562fe"}, {file = "typed_ast-1.4.1-cp37-cp37m-win_amd64.whl", hash = "sha256:0666aa36131496aed8f7be0410ff974562ab7eeac11ef351def9ea6fa28f6355"}, {file = "typed_ast-1.4.1-cp38-cp38-macosx_10_15_x86_64.whl", hash = "sha256:d205b1b46085271b4e15f670058ce182bd1199e56b317bf2ec004b6a44f911f6"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_i686.whl", hash = "sha256:6daac9731f172c2a22ade6ed0c00197ee7cc1221aa84cfdf9c31defeb059a907"}, {file = "typed_ast-1.4.1-cp38-cp38-manylinux1_x86_64.whl", hash = "sha256:498b0f36cc7054c1fead3d7fc59d2150f4d5c6c56ba7fb150c013fbc683a8d2d"}, {file = "typed_ast-1.4.1-cp38-cp38-win32.whl", hash = "sha256:715ff2f2df46121071622063fc7543d9b1fd19ebfc4f5c8895af64a77a8c852c"}, {file = "typed_ast-1.4.1-cp38-cp38-win_amd64.whl", hash = "sha256:fc0fea399acb12edbf8a628ba8d2312f583bdbdb3335635db062fa98cf71fca4"}, {file = "typed_ast-1.4.1-cp39-cp39-macosx_10_15_x86_64.whl", hash = "sha256:d43943ef777f9a1c42bf4e552ba23ac77a6351de620aa9acf64ad54933ad4d34"}, {file = "typed_ast-1.4.1.tar.gz", hash = "sha256:8c8aaad94455178e3187ab22c8b01a3837f8ee50e09cf31f1ba129eb293ec30b"}, ] unidecode = [ {file = "Unidecode-1.1.1-py2.py3-none-any.whl", hash = "sha256:1d7a042116536098d05d599ef2b8616759f02985c85b4fef50c78a5aaf10822a"}, {file = "Unidecode-1.1.1.tar.gz", hash = "sha256:2b6aab710c2a1647e928e36d69c21e76b453cd455f4e2621000e54b2a9b8cce8"}, ] wcwidth = [ {file = "wcwidth-0.1.8-py2.py3-none-any.whl", hash = "sha256:8fd29383f539be45b20bd4df0dc29c20ba48654a41e661925e612311e9f3c603"}, {file = "wcwidth-0.1.8.tar.gz", hash = "sha256:f28b3e8a6483e5d49e7f8949ac1a78314e740333ae305b4ba5defd3e74fb37a8"}, ] wrapt = [ {file = "wrapt-1.11.2.tar.gz", hash = "sha256:565a021fd19419476b9362b05eeaa094178de64f8361e44468f9e9d7843901e1"}, ] zipp = [ {file = "zipp-2.2.0-py36-none-any.whl", hash = "sha256:d65287feb793213ffe11c0f31b81602be31448f38aeb8ffc2eb286c4f6f6657e"}, {file = "zipp-2.2.0.tar.gz", hash = "sha256:5c56e330306215cd3553342cfafc73dda2c60792384117893f3a83f8a1209f50"}, ] libthumbor-2.0.2/pyproject.toml000066400000000000000000000017551421716504700166150ustar00rootroot00000000000000[tool.poetry] name = "libthumbor" version = "2.0.2" description = "libthumbor is the python extension to generate thumbor URLs" authors = ["Bernardo Heynemann "] readme = "README.md" keywords = ["imaging", "face", "detection", "feature", "thumbor", "thumbnail", "imagemagick", "pil", "opencv"] license = "MIT" [tool.poetry.dependencies] python = "^3.6" six = "^1.14.0" [tool.poetry.dev-dependencies] pytest = "^5.3.5" pytest-tldr = "^0.2.1" pytest-xdist = "^1.31.0" black = "^19.10b0" flake8 = "^3.7.9" pylint = "^2.4.4" preggy = "^1.4.4" pytest-cov = "^2.8.1" django = "^3.0.3" [tool.black] line-length = 88 target-version = ['py37'] include = '\.pyi?$' exclude = ''' ( /( \.eggs # exclude a few common directories in the | \.git # root of the project | \.hg | \.mypy_cache | \.tox | \.venv | _build | buck-out | build | dist )/ ) ''' [build-system] requires = ["poetry>=0.12"] build-backend = "poetry.masonry.api" libthumbor-2.0.2/test_requirements.txt000066400000000000000000000000051421716504700202070ustar00rootroot00000000000000nose libthumbor-2.0.2/tests/000077500000000000000000000000001421716504700150335ustar00rootroot00000000000000libthumbor-2.0.2/tests/__init__.py000066400000000000000000000000001421716504700171320ustar00rootroot00000000000000libthumbor-2.0.2/tests/test_cryptourl.py000066400000000000000000000064321421716504700205140ustar00rootroot00000000000000#!/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@gmail.com """libthumbor cryptography tests""" from unittest import TestCase from preggy import expect from six import ensure_text from libthumbor.crypto import CryptoURL IMAGE_URL = "my.server.com/some/path/to/image.jpg" KEY = b"my-security-key" class NewFormatUrlTestsMixin: def test_generated_url_1(self): url = self.crypto.generate(image_url=IMAGE_URL, width=300, height=200) expect(url).to_equal( "/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)) ) expect(url).to_equal( "/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)"), ) expect(url).to_equal( "/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)"), ) expect(url).to_equal( "/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)"), ) expect(url).to_equal( "/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(ensure_text(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 ) expect(url.startswith("unsafe")).to_be_true() 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 ) expect(url.startswith("unsafe")).to_be_false() libthumbor-2.0.2/tests/test_generic_views.py000066400000000000000000000123151421716504700212770ustar00rootroot00000000000000#!/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@gmail.com """libthumbor generic views tests""" import os import pytest from preggy import expect from libthumbor.crypto import CryptoURL os.environ["DJANGO_SETTINGS_MODULE"] = "tests.testproj.testproj.settings" try: from django.conf import settings from django.test import TestCase from django.http import QueryDict DJANGO_PRESENT = True except ImportError: DJANGO_PRESENT = False HTTP_NOT_FOUND = 404 HTTP_METHOD_NOT_ALLOWED = 405 HTTP_OK = 200 HTTP_BAD_REQUEST = 400 @pytest.mark.skip_if(not DJANGO_PRESENT, "django must be present to run this test") class GenericViewsTestCase(TestCase): def setUp(self): self.url_query = QueryDict("", mutable=True) def test_without_url_param(self): response = self.client.get("/gen_url/") expect(response.status_code).to_equal(HTTP_BAD_REQUEST) def test_generate_url_with_params_via_post(self): image_args = {"image_url": "globo.com/media/img/my_image.jpg"} response = self.client.post("/gen_url/", image_args) expect(response.status_code).to_equal(HTTP_METHOD_NOT_ALLOWED) def test_generate_url_with_params_via_get(self): crypto = CryptoURL(settings.THUMBOR_SECURITY_KEY) image_args = {"image_url": "globo.com/media/img/my_image.jpg"} self.url_query.update(image_args) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_OK) expect(response.content).to_equal( settings.THUMBOR_SERVER + crypto.generate(**image_args).strip("/") ) def test_passing_invalid_value_for_width(self): self.url_query.update( {"image_url": "globo.com/media/img/my_image.jpg", "width": 1.2} ) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_BAD_REQUEST) expect(str(response.content)).to_include( "The width value '1.2' is not an integer." ) def test_passing_invalid_value_for_height(self): self.url_query.update( {"image_url": "globo.com/media/img/my_image.jpg", "height": "s"} ) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_BAD_REQUEST) expect(str(response.content)).to_include( "The height value 's' is not an integer." ) def test_passing_invalid_aligns(self): self.url_query.update( {"image_url": "globo.com/media/img/my_image.jpg", "halign": "sss"} ) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_BAD_REQUEST) def test_passing_only_one_crop_value(self): self.url_query.update( {"image_url": "globo.com/media/img/my_image.jpg", "crop_left": 100} ) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_BAD_REQUEST) expect(str(response.content)).to_include("Missing values for cropping") def test_passing_only_one_crop_with_invalid_value(self): self.url_query.update( { "image_url": "globo.com/media/img/my_image.jpg", "crop_top": "bla", "crop_left": 200, "crop_right": "1", "crop_bottom": "blas", } ) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_BAD_REQUEST) expect(str(response.content)).to_include("Invalid values for cropping") def test_passing_various_erroneous_values(self): self.url_query.update( { "image_url": "globo.com/media/img/my_image.jpg", "crop_left": 100, "width": "aaa", "height": 123, } ) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_BAD_REQUEST) def test_passing_all_params(self): image_args = { "image_url": "globo.com/media/img/my_image.jpg", "halign": "left", "valign": "middle", "meta": True, "smart": True, "width": 400, "height": 400, "flip": True, "flop": True, } self.url_query.update(image_args) self.url_query.update( {"crop_top": 100, "crop_left": 100, "crop_bottom": 200, "crop_right": 200} ) image_args.update({"crop": ((100, 100), (200, 200))}) crypto = CryptoURL(settings.THUMBOR_SECURITY_KEY) response = self.client.get("/gen_url/?" + self.url_query.urlencode()) expect(response.status_code).to_equal(HTTP_OK) expect(response.content).to_equal( settings.THUMBOR_SERVER + crypto.generate(**image_args).strip("/") ) libthumbor-2.0.2/tests/test_libthumbor.py000066400000000000000000000032571421716504700206220ustar00rootroot00000000000000#!/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@gmail.com import re from six import b, PY3 from libthumbor import CryptoURL, Url, Signer def test_usage_new_format(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_signer = Signer(key) thumbor_url = Url.generate_options( width=300, height=200, smart=True, adaptive=False, fit_in=False, horizontal_flip=False, vertical_flip=False, halign="center", valign="middle", crop_left=0, crop_top=0, crop_right=0, crop_bottom=0, filters=[], ) thumbor_url = ("%s/%s" % (thumbor_url, image)).lstrip("/") signature = thumbor_signer.signature(thumbor_url) if PY3: signature = signature.decode("ascii") thumbor_url = "/%s/%s" % (signature, thumbor_url) crypto = CryptoURL(key=key) url = crypto.generate(width=300, height=200, smart=True, image_url=image) assert url == thumbor_url def test_thumbor_can_decrypt_lib_thumbor_generated_url_new_format(): key = "my-security-key" image = "s.glbimg.com/et/bb/f/original/2011/03/24/VN0JiwzmOw0b0lg.jpg" thumbor_signer = Signer(key) crypto = CryptoURL(key=key) url = crypto.generate(width=300, height=200, smart=True, image_url=image) reg = "/([^/]+)/(.+)" (signature, url) = re.match(reg, url).groups() assert thumbor_signer.validate(b(signature), url) libthumbor-2.0.2/tests/test_url.py000066400000000000000000000115331421716504700172510ustar00rootroot00000000000000#!/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@corp.globo.com # pylint: disable=line-too-long,no-self-use from unittest import TestCase from preggy import expect from libthumbor.url import Url class UrlTestCase(TestCase): def setUp(self): Url.compiled_regex = None def test_can_get_regex(self): regex = Url.regex() expect(regex).to_equal( "/?(?:(?:(?Punsafe)|(?P.+?))/)?(?:(?Pdebug)/)?(?:(?Pmeta)/)?" "(?:(?Ptrim(?::(?:top-left|bottom-right))?(?::\\d+)?)/)?" "(?:(?P\\d+)x(?P\\d+):(?P\\d+)x(?P\\d+)/)?" "(?:(?Padaptive-)?(?Pfull-)?(?Pfit-in)/)?(?:(?P-)?" "(?P(?:\\d+|orig))?x(?P-)?(?P(?:\\d+|orig))?/)?" "(?:(?Pleft|right|center)/)?(?:(?Ptop|bottom|middle)/)?" "(?:(?Psmart)/)?(?:filters:(?P.+?\\))/)?(?P.+)" ) def test_can_get_regex_without_unsafe(self): regex = Url.regex(False) expect(regex).to_equal( "/?(?:(?Pdebug)/)?(?:(?Pmeta)/)?" "(?:(?Ptrim(?::(?:top-left|bottom-right))?(?::\\d+)?)/)?" "(?:(?P\\d+)x(?P\\d+):(?P\\d+)x(?P\\d+)/)?" "(?:(?Padaptive-)?(?Pfull-)?(?Pfit-in)/)?(?:(?P-)?" "(?P(?:\\d+|orig))?x(?P-)?(?P(?:\\d+|orig))?/)?" "(?:(?Pleft|right|center)/)?(?:(?Ptop|bottom|middle)/)?" "(?:(?Psmart)/)?(?:filters:(?P.+?\\))/)?(?P.+)" ) def test_parsing_invalid_url(self): expect(Url.compiled_regex).to_be_null() url = "" expect(Url.parse_decrypted(url)).to_be_null() def test_parsing_complete_url(self): url = ( "/debug/meta/trim/300x200:400x500/adaptive-full-fit-in/-300x-400/" "left/top/smart/filters:brightness(100)/some/image.jpg" ) expected = { "trim": "trim", "full": True, "halign": "left", "fit_in": True, "vertical_flip": True, "image": "some/image.jpg", "crop": {"top": 200, "right": 400, "bottom": 500, "left": 300}, "height": 400, "width": 300, "meta": True, "horizontal_flip": True, "filters": "brightness(100)", "valign": "top", "debug": True, "adaptive": True, "smart": True, } result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected) # do it again to use compiled regex result = Url.parse_decrypted(url) expect(result).not_to_be_null() expect(result).to_be_like(expected) def test_can_generate_url(self): url = Url.generate_options( debug=True, width=300, height=200, smart=True, meta=True, trim=True, adaptive=True, full=True, fit_in=True, horizontal_flip=True, vertical_flip=True, halign="left", valign="top", crop_left=100, crop_top=100, crop_right=400, crop_bottom=400, filters="brightness(100)", ) expect(url).to_equal( "debug/meta/trim/100x100:400x400/adaptive-full-fit-in/-300x-200/left/top/smart/filters:brightness(100)" ) def test_can_generate_url_with_defaults(self): url = Url.generate_options() expect(url).to_be_empty() def test_can_generate_url_with_fitin(self): url = Url.generate_options(fit_in=True, adaptive=False, full=False) expect(url).to_equal("fit-in") def test_can_generate_url_with_custom_trim(self): url = Url.generate_options( debug=True, width=300, height=200, smart=True, meta=True, trim="300x200", adaptive=True, full=True, fit_in=True, horizontal_flip=True, vertical_flip=True, halign="left", valign="top", crop_left=100, crop_top=100, crop_right=400, crop_bottom=400, filters="brightness(100)", ) expect(url).to_equal( "debug/meta/trim:300x200/100x100:400x400/adaptive-full-fit-in/-300x-200/left/top/smart/filters:brightness(100)" ) libthumbor-2.0.2/tests/test_url_composer.py000066400000000000000000000410161421716504700211570ustar00rootroot00000000000000#!/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@gmail.com # pylint: disable=no-self-use """libthumbor URL composer tests""" from unittest import TestCase from preggy import expect from libthumbor.url import unsafe_url, url_for IMAGE_URL = "my.server.com/some/path/to/image.jpg" IMAGE_MD5 = "84996242f65a4d864aceb125e1c4c5ba" def test_no_options_specified(): """test_no_options_specified Given An image URL of "my.server.com/some/path/to/image.jpg" When I ask my library for an URL Then I get "84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(image_url=IMAGE_URL) expect(url).to_equal(IMAGE_MD5) def test_url_raises_if_no_url(): """test_url_raises_if_no_url Given An image URL of "" or null When I ask my library for an URL Then I get an exception that says image URL is mandatory """ with expect.error_to_happen( ValueError, message="The image_url argument is mandatory." ): url_for() def test_url_width_height_1(): """test_url_width_height_1 Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 300 When I ask my library for an URL Then I get "300x0/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=300, image_url=IMAGE_URL) expect(url).to_equal("300x0/84996242f65a4d864aceb125e1c4c5ba") def test_url_width_height_2(): """test_url_width_height_2 Given An image URL of "my.server.com/some/path/to/image.jpg" And a height of 300 When I ask my library for an URL Then I get "0x300/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(height=300, image_url=IMAGE_URL) expect(url).to_equal("0x300/84996242f65a4d864aceb125e1c4c5ba") def test_url_width_height_3(): """test_url_width_height_3 Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And a height of 300 When I ask my library for an URL Then I get "200x300/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=200, height=300, image_url=IMAGE_URL) expect(url).to_equal("200x300/84996242f65a4d864aceb125e1c4c5ba") def test_url_width_height_4(): """test_url_width_height_4 Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of orig When I ask my library for an URL Then I get "origx0/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width="orig", image_url=IMAGE_URL) expect(url).to_equal("origx0/84996242f65a4d864aceb125e1c4c5ba") def test_url_width_height_5(): """test_url_width_height_5 Given An image URL of "my.server.com/some/path/to/image.jpg" And a height of orig When I ask my library for an URL Then I get "0xorig/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(height="orig", image_url=IMAGE_URL) expect(url).to_equal("0xorig/84996242f65a4d864aceb125e1c4c5ba") def test_url_width_height_6(): """test_url_width_height_6 Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 100 And a height of orig When I ask my library for an URL Then I get "100xorig/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=100, height="orig", image_url=IMAGE_URL) expect(url).to_equal("100xorig/84996242f65a4d864aceb125e1c4c5ba") def test_url_width_height_7(): """test_url_width_height_7 Given An image URL of "my.server.com/some/path/to/image.jpg" And a height of 100 And a width of orig When I ask my library for an URL Then I get "origx100/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width="orig", height=100, image_url=IMAGE_URL) expect(url).to_equal("origx100/84996242f65a4d864aceb125e1c4c5ba") def test_url_width_height_8(): """test_url_width_height_8 Given An image URL of "my.server.com/some/path/to/image.jpg" And a height of orig And a width of orig When I ask my library for an URL Then I get "origxorig/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width="orig", height="orig", image_url=IMAGE_URL) expect(url).to_equal("origxorig/84996242f65a4d864aceb125e1c4c5ba") def test_smart_url(): """test_smart_url Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And a height of 300 And the smart flag When I ask my library for an URL Then I get "200x300/smart/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=200, height=300, smart=True, image_url=IMAGE_URL) expect(url).to_equal("200x300/smart/84996242f65a4d864aceb125e1c4c5ba") def test_fit_in_url(): """test_fit_in_url Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And a height of 300 And the fit-in flag When I ask my library for an URL Then I get "fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=200, height=300, fit_in=True, image_url=IMAGE_URL) expect(url).to_equal("fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba") def test_adaptive_fit_in_url(): """test_adaptive_fit_in_url Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And a height of 300 And the adaptive fit-in flag When I ask my library for an URL Then I get "adaptive-fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=200, height=300, adaptive_fit_in=True, image_url=IMAGE_URL) expect(url).to_equal("adaptive-fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba") def test_fit_in_fails_if_no_width_supplied(): with expect.error_to_happen( ValueError, message="When using fit-in or full-fit-in, " "you must specify width and/or height.", ): url_for(fit_in=True, image_url=IMAGE_URL) def test_full_fit_in_fails_if_no_width_supplied(): with expect.error_to_happen( ValueError, message="When using fit-in or full-fit-in, " "you must specify width and/or height.", ): url_for(full_fit_in=True, image_url=IMAGE_URL) def test_adaptive_fit_in_fails_if_no_width_supplied(): with expect.error_to_happen( ValueError, message="When using fit-in or full-fit-in, " "you must specify width and/or height.", ): url_for(adaptive_fit_in=True, image_url=IMAGE_URL) def test_adaptive_full_fit_in_fails_if_no_width_supplied(): with expect.error_to_happen( ValueError, message="When using fit-in or full-fit-in, " "you must specify width and/or height.", ): url_for(adaptive_full_fit_in=True, image_url=IMAGE_URL) def test_full_fit_in_url(): """test_full_fit_in_url Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And a height of 300 And the full-fit-in flag When I ask my library for an URL Then I get "full-fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=200, height=300, full_fit_in=True, image_url=IMAGE_URL) expect(url).to_equal("full-fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba") def test_adaptive_full_fit_in_url(): """test_adaptive_full_fit_in_url Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And a height of 300 And the adaptive full-fit-in flag When I ask my library for an URL Then I get "adaptive-full-fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(width=200, height=300, adaptive_full_fit_in=True, image_url=IMAGE_URL) expect(url).to_equal( "adaptive-full-fit-in/200x300/84996242f65a4d864aceb125e1c4c5ba" ) def test_flip_1(): """test_flip_1 Given An image URL of "my.server.com/some/path/to/image.jpg" And the flip flag When I ask my library for an URL Then I get "-0x0/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(flip=True, image_url=IMAGE_URL) expect(url).to_equal("-0x0/84996242f65a4d864aceb125e1c4c5ba") def test_flip_2(): """test_flip_2 Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And the flip flag When I ask my library for an URL Then I get "-200x0/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(flip=True, width=200, image_url=IMAGE_URL) expect(url).to_equal("-200x0/84996242f65a4d864aceb125e1c4c5ba") def test_flop_1(): """test_flop_1 Given An image URL of "my.server.com/some/path/to/image.jpg" And the flop flag When I ask my library for an URL Then I get "0x-0/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(flop=True, image_url=IMAGE_URL) expect(url).to_equal("0x-0/84996242f65a4d864aceb125e1c4c5ba") def test_flop_2(): """test_flop_2 Given An image URL of "my.server.com/some/path/to/image.jpg" And a height of 200 And the flop flag When I ask my library for an URL Then I get "0x-200/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(flop=True, height=200, image_url=IMAGE_URL) expect(url).to_equal("0x-200/84996242f65a4d864aceb125e1c4c5ba") def test_flip_flop(): """test_flip_flop Given An image URL of "my.server.com/some/path/to/image.jpg" And the flip flag And the flop flag When I ask my library for an URL Then I get "-0x-0/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(flip=True, flop=True, image_url=IMAGE_URL) expect(url).to_equal("-0x-0/84996242f65a4d864aceb125e1c4c5ba") def test_flip_flop2(): """test_flip_flop2 Given An image URL of "my.server.com/some/path/to/image.jpg" And a width of 200 And a height of 300 And the flip flag And the flop flag When I ask my library for an URL Then I get "-200x-300/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(flip=True, flop=True, width=200, height=300, image_url=IMAGE_URL) expect(url).to_equal("-200x-300/84996242f65a4d864aceb125e1c4c5ba") def test_horizontal_alignment(): """test_horizontal_alignment Given An image URL of "my.server.com/some/path/to/image.jpg" And a 'left' horizontal alignment option When I ask my library for an URL Then I get "left/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(halign="left", image_url=IMAGE_URL) expect(url).to_equal("left/84996242f65a4d864aceb125e1c4c5ba") def test_horizontal_alignment2(): """test_horizontal_alignment2 Given An image URL of "my.server.com/some/path/to/image.jpg" And a 'center' horizontal alignment option When I ask my library for an URL Then I get "84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(halign="center", image_url=IMAGE_URL) expect(url).to_equal("84996242f65a4d864aceb125e1c4c5ba") def test_vertical_alignment(): """test_vertical_alignment Given An image URL of "my.server.com/some/path/to/image.jpg" And a 'top' vertical alignment option When I ask my library for an URL Then I get "top/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(valign="top", image_url=IMAGE_URL) expect(url).to_equal("top/84996242f65a4d864aceb125e1c4c5ba") def test_vertical_alignment2(): """test_vertical_alignment2 Given An image URL of "my.server.com/some/path/to/image.jpg" And a 'middle' vertical alignment option When I ask my library for an URL Then I get "84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(valign="middle", image_url=IMAGE_URL) expect(url).to_equal("84996242f65a4d864aceb125e1c4c5ba") def test_both_alignments(): """test_both_alignments Given An image URL of "my.server.com/some/path/to/image.jpg" And a 'left' horizontal alignment option And a 'top' vertical alignment option When I ask my library for an URL Then I get "left/top/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(halign="left", valign="top", image_url=IMAGE_URL) expect(url).to_equal("left/top/84996242f65a4d864aceb125e1c4c5ba") def test_proper_haligns(): """test_proper_haligns""" with expect.error_to_happen( ValueError, message=( 'Only "left", "center" and "right"' " are valid values for horizontal alignment." ), ): url_for(halign="wrong", image_url=IMAGE_URL) def test_proper_valigns(): """test_proper_haligns""" with expect.error_to_happen( ValueError, message=( 'Only "top", "middle" and "bottom"' " are valid values for vertical alignment." ), ): url_for(valign="wrong", image_url=IMAGE_URL) def test_proper_meta(): """test_proper_meta Given An image URL of "my.server.com/some/path/to/image.jpg" And a 'meta' flag When I ask my library for an URL Then I get "meta/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(meta=True, image_url=IMAGE_URL) expect("meta/84996242f65a4d864aceb125e1c4c5ba").to_equal(url) def test_trim_standard(): url = url_for(trim=True, image_url=IMAGE_URL) expect("trim/84996242f65a4d864aceb125e1c4c5ba").to_equal(url) def test_trim_pixel_and_tolerance(): url = url_for(trim=("bottom-right", 15), image_url=IMAGE_URL) expect("trim:bottom-right:15/84996242f65a4d864aceb125e1c4c5ba").to_equal(url) def test_trim_pixel_only(): url = url_for(trim=("top-left", None), image_url=IMAGE_URL) expect("trim:top-left/84996242f65a4d864aceb125e1c4c5ba").to_equal(url) def test_trim_tolerance_only(): url = url_for(trim=(None, 15), image_url=IMAGE_URL) expect("trim::15/84996242f65a4d864aceb125e1c4c5ba").to_equal(url) def test_manual_crop_1(): """test_manual_crop_1 Given An image URL of "my.server.com/some/path/to/image.jpg" And a manual crop left-top point of (10, 20) And a manual crop right-bottom point of (30, 40) When I ask my library for an URL Then I get "10x20:30x40/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(crop=((10, 20), (30, 40)), image_url=IMAGE_URL) expect("10x20:30x40/84996242f65a4d864aceb125e1c4c5ba").to_equal(url) def test_manual_crop_2(): """test_manual_crop_2 Given An image URL of "my.server.com/some/path/to/image.jpg" And a manual crop left-top point of (0, 0) And a manual crop right-bottom point of (0, 0) When I ask my library for an URL Then I get "84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(crop=((0, 0), (0, 0)), image_url=IMAGE_URL) expect("84996242f65a4d864aceb125e1c4c5ba").to_equal(url) def test_smart_after_alignments(): """test_smart_after_alignments Given An image URL of "my.server.com/some/path/to/image.jpg" And a 'smart' flag And a 'left' horizontal alignment option When I ask my library for an URL Then I get "left/smart/84996242f65a4d864aceb125e1c4c5ba" as URL """ url = url_for(smart=True, halign="left", image_url=IMAGE_URL) expect("left/smart/84996242f65a4d864aceb125e1c4c5ba").to_equal(url) class UnsafeUrlTestCase(TestCase): def test_should_return_a_valid_unsafe_url_with_no_params(self): expect("unsafe/%s" % IMAGE_URL).to_equal(unsafe_url(image_url=IMAGE_URL)) def test_should_return_an_unsafe_url_with_width_and_height(self): expect("unsafe/100x140/%s" % IMAGE_URL).to_equal( unsafe_url(image_url=IMAGE_URL, width=100, height=140), ) def test_should_return_an_unsafe_url_with_crop_and_smart(self): expect("unsafe/100x140/smart/%s" % IMAGE_URL).to_equal( unsafe_url(image_url=IMAGE_URL, width=100, height=140, smart=True), ) libthumbor-2.0.2/tests/testproj/000077500000000000000000000000001421716504700167055ustar00rootroot00000000000000libthumbor-2.0.2/tests/testproj/manage.py000077500000000000000000000011641421716504700205140ustar00rootroot00000000000000#!/usr/bin/env python """Django's command-line utility for administrative tasks.""" import os import sys def main(): os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testproj.settings') try: from django.core.management import execute_from_command_line except ImportError as exc: raise ImportError( "Couldn't import Django. Are you sure it's installed and " "available on your PYTHONPATH environment variable? Did you " "forget to activate a virtual environment?" ) from exc execute_from_command_line(sys.argv) if __name__ == '__main__': main() libthumbor-2.0.2/tests/testproj/testproj/000077500000000000000000000000001421716504700205575ustar00rootroot00000000000000libthumbor-2.0.2/tests/testproj/testproj/__init__.py000066400000000000000000000000001421716504700226560ustar00rootroot00000000000000libthumbor-2.0.2/tests/testproj/testproj/asgi.py000066400000000000000000000006111421716504700220520ustar00rootroot00000000000000""" ASGI config for testproj project. It exposes the ASGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/3.0/howto/deployment/asgi/ """ import os from django.core.asgi import get_asgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testproj.settings') application = get_asgi_application() libthumbor-2.0.2/tests/testproj/testproj/settings.py000066400000000000000000000062361421716504700230000ustar00rootroot00000000000000""" Django settings for testproj project. Generated by 'django-admin startproject' using Django 3.0.3. For more information on this file, see https://docs.djangoproject.com/en/3.0/topics/settings/ For the full list of settings and their values, see https://docs.djangoproject.com/en/3.0/ref/settings/ """ import os import django # Build paths inside the project like this: os.path.join(BASE_DIR, ...) BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__))) # Quick-start development settings - unsuitable for production # See https://docs.djangoproject.com/en/3.0/howto/deployment/checklist/ # SECURITY WARNING: keep the secret key used in production secret! SECRET_KEY = "a@$(--e0iqkamq+4v84w$$1*cn$fa5n&g1(&2fk$e0@c!f06s9" # SECURITY WARNING: don't run with debug turned on in production! DEBUG = True ALLOWED_HOSTS = ["testserver"] # Application definition INSTALLED_APPS = [ "django.contrib.admin", "django.contrib.auth", "django.contrib.contenttypes", "django.contrib.sessions", "django.contrib.messages", "django.contrib.staticfiles", ] MIDDLEWARE = [ "django.middleware.security.SecurityMiddleware", "django.contrib.sessions.middleware.SessionMiddleware", "django.middleware.common.CommonMiddleware", "django.middleware.csrf.CsrfViewMiddleware", "django.contrib.auth.middleware.AuthenticationMiddleware", "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", ] ROOT_URLCONF = "tests.testproj.testproj.urls" TEMPLATES = [ { "BACKEND": "django.template.backends.django.DjangoTemplates", "DIRS": [], "APP_DIRS": True, "OPTIONS": { "context_processors": [ "django.template.context_processors.debug", "django.template.context_processors.request", "django.contrib.auth.context_processors.auth", "django.contrib.messages.context_processors.messages", ], }, }, ] WSGI_APPLICATION = "tests.testproj.testproj.wsgi.application" # Database # https://docs.djangoproject.com/en/3.0/ref/settings/#databases DATABASES = { "default": { "ENGINE": "django.db.backends.sqlite3", "NAME": os.path.join(BASE_DIR, "db.sqlite3"), } } # Password validation # https://docs.djangoproject.com/en/3.0/ref/settings/#auth-password-validators AUTH_PASSWORD_VALIDATORS = [ { "NAME": ( "django.contrib.auth.password_validation.UserAttributeSimilarityValidator" ), }, {"NAME": "django.contrib.auth.password_validation.MinimumLengthValidator"}, {"NAME": "django.contrib.auth.password_validation.CommonPasswordValidator"}, {"NAME": "django.contrib.auth.password_validation.NumericPasswordValidator"}, ] # Internationalization # https://docs.djangoproject.com/en/3.0/topics/i18n/ LANGUAGE_CODE = "en-us" TIME_ZONE = "UTC" USE_I18N = True USE_L10N = True USE_TZ = True # Static files (CSS, JavaScript, Images) # https://docs.djangoproject.com/en/3.0/howto/static-files/ STATIC_URL = "/static/" THUMBOR_SECURITY_KEY = "my-security-key" THUMBOR_SERVER = "http://localhost:8888/" django.setup() libthumbor-2.0.2/tests/testproj/testproj/urls.py000066400000000000000000000015101421716504700221130ustar00rootroot00000000000000"""testproj URL Configuration The `urlpatterns` list routes URLs to views. For more information please see: https://docs.djangoproject.com/en/3.0/topics/http/urls/ Examples: Function views 1. Add an import: from my_app import views 2. Add a URL to urlpatterns: path('', views.home, name='home') Class-based views 1. Add an import: from other_app.views import Home 2. Add a URL to urlpatterns: path('', Home.as_view(), name='home') Including another URLconf 1. Import the include() function: from django.urls import include, path 2. Add a URL to urlpatterns: path('blog/', include('blog.urls')) """ from django.contrib import admin from django.urls import include, path urlpatterns = [ # pylint: disable=invalid-name path("admin/", admin.site.urls), path("", include("libthumbor.django.urls")), ] libthumbor-2.0.2/tests/testproj/testproj/wsgi.py000066400000000000000000000006111421716504700221000ustar00rootroot00000000000000""" WSGI config for testproj project. It exposes the WSGI callable as a module-level variable named ``application``. For more information on this file, see https://docs.djangoproject.com/en/3.0/howto/deployment/wsgi/ """ import os from django.core.wsgi import get_wsgi_application os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'testproj.settings') application = get_wsgi_application() libthumbor-2.0.2/tests/url_signers/000077500000000000000000000000001421716504700173675ustar00rootroot00000000000000libthumbor-2.0.2/tests/url_signers/__init__.py000066400000000000000000000000141421716504700214730ustar00rootroot00000000000000# Test file libthumbor-2.0.2/tests/url_signers/test_base64_hmac_sha1_signer.py000066400000000000000000000021711421716504700253400ustar00rootroot00000000000000#!/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@corp.globo.com # pylint: disable=no-self-use import base64 import hashlib import hmac from unittest import TestCase from preggy import expect from six import text_type from libthumbor.url_signers.base64_hmac_sha1 import UrlSigner class Base64HmacSha1UrlSignerTestCase(TestCase): def test_can_create_signer(self): signer = UrlSigner(security_key="something") expect(signer).to_be_instance_of(UrlSigner) expect(signer.security_key).to_equal("something") def test_can_sign_url(self): signer = UrlSigner(security_key="something") url = "10x11:12x13/-300x-300/center/middle/smart/some/image.jpg" expected = base64.urlsafe_b64encode( hmac.new( "something".encode(), text_type(url).encode("utf-8"), hashlib.sha1 ).digest() ) actual = signer.signature(url) expect(actual).to_equal(expected) libthumbor-2.0.2/tests/url_signers/test_base_url_signer.py000066400000000000000000000025741421716504700241530ustar00rootroot00000000000000#!/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@corp.globo.com # pylint: disable=no-self-use from __future__ import unicode_literals from unittest import TestCase from preggy import expect from libthumbor.url_signers import BaseUrlSigner class BaseSignerTestCase(TestCase): def test_can_create_signer(self): signer = BaseUrlSigner(security_key="something") expect(signer).to_be_instance_of(BaseUrlSigner) expect(signer.security_key).to_equal("something") def test_can_create_unicode_signer(self): signer = BaseUrlSigner(security_key="téste") expect(signer).to_be_instance_of(BaseUrlSigner) expect(signer.security_key).to_equal("téste") def test_can_validate_url(self): class TestSigner(BaseUrlSigner): def signature(self, url): return "%s+1" % url signer = TestSigner(security_key="téste") expect( signer.validate("http://www.test.com+1", "http://www.test.com") ).to_be_true() def test_has_abstract_method(self): signer = BaseUrlSigner(security_key="téste") with expect.error_to_happen(NotImplementedError): signer.signature("test-url")