pax_global_header00006660000000000000000000000064137073746550014533gustar00rootroot0000000000000052 comment=138a12e85d09e7e76eebc1d0e059b2e28ba488e6 hashids-python-1.3.1/000077500000000000000000000000001370737465500144775ustar00rootroot00000000000000hashids-python-1.3.1/.github/000077500000000000000000000000001370737465500160375ustar00rootroot00000000000000hashids-python-1.3.1/.github/workflows/000077500000000000000000000000001370737465500200745ustar00rootroot00000000000000hashids-python-1.3.1/.github/workflows/publish-to-pypi.yml000066400000000000000000000015701370737465500236670ustar00rootroot00000000000000name: Upload Python Package on: [create, push] jobs: deploy: runs-on: ubuntu-latest steps: - uses: actions/checkout@v2 - name: Set up Python uses: actions/setup-python@v2 with: python-version: '3.x' - name: Install dependencies run: | python -m pip install --upgrade pip pip install pep517 - name: Build sdist and bdist run: | python -m pep517.build --binary --source --out-dir dist . - name: Publish package to TestPyPI uses: pypa/gh-action-pypi-publish@master with: password: ${{ secrets.TEST_PYPI_TOKEN }} repository_url: https://test.pypi.org/legacy/ skip_existing: true - name: Publish package to PyPI uses: pypa/gh-action-pypi-publish@master if: startsWith(github.ref, 'refs/tags/v') with: password: ${{ secrets.PYPI_TOKEN }} hashids-python-1.3.1/.gitignore000066400000000000000000000000411370737465500164620ustar00rootroot00000000000000MANIFEST dist/ hashids.egg-info/ hashids-python-1.3.1/.travis.yml000066400000000000000000000002071370737465500166070ustar00rootroot00000000000000language: python python: - "2.7" - "3.5" - "3.6" - "3.7" - "3.8" - "pypy" - "pypy3" script: python -m pytest sudo: false hashids-python-1.3.1/CHANGELOG.md000066400000000000000000000040751370737465500163160ustar00rootroot00000000000000# Changelog The format is based on [Keep a Changelog], and this project adheres to [Semantic Versioning]. ## [1.3.1] - 2020-07-26 ### Fixed Python requirement changed to `>=2.7` ## 1.3.0 - 2020-07-26 ### Added - Test for short alphabet ### Changed - Changes the list of tested Python versions to the currently active versions: 2.7, 3.5–3.8, and pypy2/3. - Build changed from `setup.py` to `pyproject.toml` - Changelog format changed to *[Keep a Changelog].* ### Fixed - Deprecation warnings for `encrypt`/`decrypt` ## 1.2.0 - 2017-01-06 ### Changed - performance optimizations (Jakub Kramarz) - version classifiers (Patrick Mézard) ## 1.1.0 - 2015-03-31 ### Added - add encode_hex() / decode_hex() ## 1.0.3 - 2015-02-26 ### Changed - remove dependency to `future` ## 1.0.2 - 2015-01-15 ### Changed - compatibility with JS version 1.0.x ## 1.0.1 - 2014-06-19 ### Changed - only decode hashids if the re-encoded result equals the input ## 1.0.0 - 2014-04-21 ### Changed - compatibility with JS version 0.3.x ## 0.8.4 - 2014-02-04 ### Changed - Make setup.py compatible with older python versions ## [0.8.3] - 2013-06-02 Added - initial release, compatible with JS version 0.1.x [1.3.0]: https://github.com/davidaurelio/hashids-python/compare/1.3.0...1.3.1 [1.3.0]: https://github.com/davidaurelio/hashids-python/compare/1.2.0...1.3.0 [1.2.0]: https://github.com/davidaurelio/hashids-python/compare/1.1.0...1.2.0 [1.1.0]: https://github.com/davidaurelio/hashids-python/compare/1.0.3...1.1.0 [1.0.3]: https://github.com/davidaurelio/hashids-python/compare/1.0.2...1.0.3 [1.0.2]: https://github.com/davidaurelio/hashids-python/compare/1.0.1...1.0.2 [1.0.1]: https://github.com/davidaurelio/hashids-python/compare/1.0.0...1.0.1 [1.0.0]: https://github.com/davidaurelio/hashids-python/compare/0.8.4...1.0.0 [0.8.4]: https://github.com/davidaurelio/hashids-python/compare/0.8.3...0.8.4 [0.8.3]: https://github.com/davidaurelio/hashids-python/releases/tag/v0.8.3 [Keep a Changelog]: https://keepachangelog.com/en/1.0.0/ [Semantic Versioning]: https://semver.org/spec/v2.0.0.html hashids-python-1.3.1/LICENSE000066400000000000000000000020631370737465500155050ustar00rootroot00000000000000Copyright (c) 2012-2014 Ivan Akimov, David Aurelio 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. hashids-python-1.3.1/README.rst000066400000000000000000000111361370737465500161700ustar00rootroot00000000000000========================== hashids for Python 2.7 & 3 ========================== A python port of the JavaScript *hashids* implementation. It generates YouTube-like hashes from one or many numbers. Use hashids when you do not want to expose your database ids to the user. Website: http://www.hashids.org/ Compatibility ============= hashids is tested with python 2.7 and 3.5–3.8. PyPy and PyPy 3 work as well. .. image:: https://travis-ci.org/davidaurelio/hashids-python.svg?branch=master :target: https://travis-ci.org/davidaurelio/hashids-python Compatibility with the JavaScript implementation ------------------------------------------------ ================== ============== hashids/JavaScript hashids/Python ------------------ -------------- v0.1.x v0.8.x v0.3.x+ v1.0.2+ ================== ============== The JavaScript implementation produces different hashes in versions 0.1.x and 0.3.x. For compatibility with the older 0.1.x version install hashids 0.8.4 from pip, otherwise the newest hashids. Installation ============ Install the module from PyPI, e. g. with pip: .. code:: bash pip install hashids pip install hashids==0.8.4 # for compatibility with hashids.js 0.1.x Run the tests ============= The tests are written with `pytest `_. The pytest module has to be installed. .. code:: bash python -m pytest Usage ===== Import the constructor from the ``hashids`` module: .. code:: python from hashids import Hashids hashids = Hashids() Basic Usage ----------- Encode a single integer: .. code:: python hashid = hashids.encode(123) # 'Mj3' Decode a hash: .. code:: python ints = hashids.decode('xoz') # (456,) To encode several integers, pass them all at once: .. code:: python hashid = hashids.encode(123, 456, 789) # 'El3fkRIo3' Decoding is done the same way: .. code:: python ints = hashids.decode('1B8UvJfXm') # (517, 729, 185) Using A Custom Salt ------------------- Hashids supports salting hashes by accepting a salt value. If you don’t want others to decode your hashes, provide a unique string to the constructor. .. code:: python hashids = Hashids(salt='this is my salt 1') hashid = hashids.encode(123) # 'nVB' The generated hash changes whenever the salt is changed: .. code:: python hashids = Hashids(salt='this is my salt 2') hashid = hashids.encode(123) # 'ojK' A salt string between 6 and 32 characters provides decent randomization. Controlling Hash Length ----------------------- By default, hashes are going to be the shortest possible. One reason you might want to increase the hash length is to obfuscate how large the integer behind the hash is. This is done by passing the minimum hash length to the constructor. Hashes are padded with extra characters to make them seem longer. .. code:: python hashids = Hashids(min_length=16) hashid = hashids.encode(1) # '4q2VolejRejNmGQB' Using A Custom Alphabet ----------------------- It’s possible to set a custom alphabet for your hashes. The default alphabet is ``'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890'``. To have only lowercase letters in your hashes, pass in the following custom alphabet: .. code:: python hashids = Hashids(alphabet='abcdefghijklmnopqrstuvwxyz') hashid = hashids.encode(123456789) # 'kekmyzyk' A custom alphabet must contain at least 16 characters. Randomness ========== The primary purpose of hashids is to obfuscate ids. It's not meant or tested to be used for security purposes or compression. Having said that, this algorithm does try to make these hashes unguessable and unpredictable: Repeating numbers ----------------- There are no repeating patterns that might show that there are 4 identical numbers in the hash: .. code:: python hashids = Hashids("this is my salt") hashids.encode(5, 5, 5, 5) # '1Wc8cwcE' The same is valid for incremented numbers: .. code:: python hashids.encode(1, 2, 3, 4, 5, 6, 7, 8, 9, 10) # 'kRHnurhptKcjIDTWC3sx' hashids.encode(1) # 'NV' hashids.encode(2) # '6m' hashids.encode(3) # 'yD' hashids.encode(4) # '2l' hashids.encode(5) # 'rD' Curses! #$%@ ============ This code was written with the intent of placing generated hashes in visible places – like the URL. Which makes it unfortunate if generated hashes accidentally formed a bad word. Therefore, the algorithm tries to avoid generating most common English curse words by never placing the following letters next to each other: **c, C, s, S, f, F, h, H, u, U, i, I, t, T.** License ======= MIT license, see the LICENSE file. You can use hashids in open source projects and commercial products. hashids-python-1.3.1/hashids.py000066400000000000000000000211261370737465500164760ustar00rootroot00000000000000"""Implements the hashids algorithm in python. For more information, visit http://hashids.org/""" import warnings from functools import wraps from math import ceil __version__ = '1.3.1' RATIO_SEPARATORS = 3.5 RATIO_GUARDS = 12 try: StrType = basestring except NameError: StrType = str def _is_str(candidate): """Returns whether a value is a string.""" return isinstance(candidate, StrType) def _is_uint(number): """Returns whether a value is an unsigned integer.""" try: return number == int(number) and number >= 0 except ValueError: return False def _split(string, splitters): """Splits a string into parts at multiple characters""" part = '' for character in string: if character in splitters: yield part part = '' else: part += character yield part def _hash(number, alphabet): """Hashes `number` using the given `alphabet` sequence.""" hashed = '' len_alphabet = len(alphabet) while True: hashed = alphabet[number % len_alphabet] + hashed number //= len_alphabet if not number: return hashed def _unhash(hashed, alphabet): """Restores a number tuple from hashed using the given `alphabet` index.""" number = 0 len_alphabet = len(alphabet) for character in hashed: position = alphabet.index(character) number *= len_alphabet number += position return number def _reorder(string, salt): """Reorders `string` according to `salt`.""" len_salt = len(salt) if len_salt != 0: string = list(string) index, integer_sum = 0, 0 for i in range(len(string) - 1, 0, -1): integer = ord(salt[index]) integer_sum += integer j = (integer + index + integer_sum) % i string[i], string[j] = string[j], string[i] index = (index + 1) % len_salt string = ''.join(string) return string def _index_from_ratio(dividend, divisor): """Returns the ceiled ratio of two numbers as int.""" return int(ceil(float(dividend) / divisor)) def _ensure_length(encoded, min_length, alphabet, guards, values_hash): """Ensures the minimal hash length""" len_guards = len(guards) guard_index = (values_hash + ord(encoded[0])) % len_guards encoded = guards[guard_index] + encoded if len(encoded) < min_length: guard_index = (values_hash + ord(encoded[2])) % len_guards encoded += guards[guard_index] split_at = len(alphabet) // 2 while len(encoded) < min_length: alphabet = _reorder(alphabet, alphabet) encoded = alphabet[split_at:] + encoded + alphabet[:split_at] excess = len(encoded) - min_length if excess > 0: from_index = excess // 2 encoded = encoded[from_index:from_index+min_length] return encoded def _encode(values, salt, min_length, alphabet, separators, guards): """Helper function that does the hash building without argument checks.""" len_alphabet = len(alphabet) len_separators = len(separators) values_hash = sum(x % (i + 100) for i, x in enumerate(values)) encoded = lottery = alphabet[values_hash % len(alphabet)] for i, value in enumerate(values): alphabet_salt = (lottery + salt + alphabet)[:len_alphabet] alphabet = _reorder(alphabet, alphabet_salt) last = _hash(value, alphabet) encoded += last value %= ord(last[0]) + i encoded += separators[value % len_separators] encoded = encoded[:-1] # cut off last separator return (encoded if len(encoded) >= min_length else _ensure_length(encoded, min_length, alphabet, guards, values_hash)) def _decode(hashid, salt, alphabet, separators, guards): """Helper method that restores the values encoded in a hashid without argument checks.""" parts = tuple(_split(hashid, guards)) hashid = parts[1] if 2 <= len(parts) <= 3 else parts[0] if not hashid: return lottery_char = hashid[0] hashid = hashid[1:] hash_parts = _split(hashid, separators) for part in hash_parts: alphabet_salt = (lottery_char + salt + alphabet)[:len(alphabet)] alphabet = _reorder(alphabet, alphabet_salt) yield _unhash(part, alphabet) def _deprecated(func, name): """A decorator that warns about deprecation when the passed-in function is invoked.""" @wraps(func) def with_warning(*args, **kwargs): warnings.warn( ('The %s method is deprecated and will be removed in v2.*.*' % name), DeprecationWarning ) return func(*args, **kwargs) return with_warning class Hashids(object): """Hashes and restores values using the "hashids" algorithm.""" ALPHABET = 'abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ1234567890' def __init__(self, salt='', min_length=0, alphabet=ALPHABET): """ Initializes a Hashids object with salt, minimum length, and alphabet. :param salt: A string influencing the generated hash ids. :param min_length: The minimum length for generated hashes :param alphabet: The characters to use for the generated hash ids. """ self._min_length = max(int(min_length), 0) self._salt = salt separators = ''.join(x for x in 'cfhistuCFHISTU' if x in alphabet) alphabet = ''.join(x for i, x in enumerate(alphabet) if alphabet.index(x) == i and x not in separators) len_alphabet, len_separators = len(alphabet), len(separators) if len_alphabet + len_separators < 16: raise ValueError('Alphabet must contain at least 16 ' 'unique characters.') separators = _reorder(separators, salt) min_separators = _index_from_ratio(len_alphabet, RATIO_SEPARATORS) number_of_missing_separators = min_separators - len_separators if number_of_missing_separators > 0: separators += alphabet[:number_of_missing_separators] alphabet = alphabet[number_of_missing_separators:] len_alphabet = len(alphabet) alphabet = _reorder(alphabet, salt) num_guards = _index_from_ratio(len_alphabet, RATIO_GUARDS) if len_alphabet < 3: guards = separators[:num_guards] separators = separators[num_guards:] else: guards = alphabet[:num_guards] alphabet = alphabet[num_guards:] self._alphabet = alphabet self._guards = guards self._separators = separators # Support old API self.decrypt = _deprecated(self.decode, "decrypt") self.encrypt = _deprecated(self.encode, "encrypt") def encode(self, *values): """Builds a hash from the passed `values`. :param values The values to transform into a hashid >>> hashids = Hashids('arbitrary salt', 16, 'abcdefghijkl0123456') >>> hashids.encode(1, 23, 456) '1d6216i30h53elk3' """ if not (values and all(_is_uint(x) for x in values)): return '' return _encode(values, self._salt, self._min_length, self._alphabet, self._separators, self._guards) def decode(self, hashid): """Restore a tuple of numbers from the passed `hashid`. :param hashid The hashid to decode >>> hashids = Hashids('arbitrary salt', 16, 'abcdefghijkl0123456') >>> hashids.decode('1d6216i30h53elk3') (1, 23, 456) """ if not hashid or not _is_str(hashid): return () try: numbers = tuple(_decode(hashid, self._salt, self._alphabet, self._separators, self._guards)) return numbers if hashid == self.encode(*numbers) else () except ValueError: return () def encode_hex(self, hex_str): """Converts a hexadecimal string (e.g. a MongoDB id) to a hashid. :param hex_str The hexadecimal string to encodes >>> Hashids.encode_hex('507f1f77bcf86cd799439011') 'y42LW46J9luq3Xq9XMly' """ numbers = (int('1' + hex_str[i:i+12], 16) for i in range(0, len(hex_str), 12)) try: return self.encode(*numbers) except ValueError: return '' def decode_hex(self, hashid): """Restores a hexadecimal string (e.g. a MongoDB id) from a hashid. :param hashid The hashid to decode >>> Hashids.decode_hex('y42LW46J9luq3Xq9XMly') '507f1f77bcf86cd799439011' """ return ''.join(('%x' % x)[1:] for x in self.decode(hashid)) hashids-python-1.3.1/pyproject.toml000066400000000000000000000015371370737465500174210ustar00rootroot00000000000000[build-system] requires = ["flit_core >=2,<4"] build-backend = "flit_core.buildapi" [tool.flit.metadata] module = "hashids" author = "David Aurelio" author-email = "dev@david-aurelio.com" home-page = "https://hashids.org/python/" description-file="README.rst" classifiers = [ "License :: OSI Approved :: MIT License", "Programming Language :: Python :: 2", "Programming Language :: Python :: 2.7", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3.5", "Programming Language :: Python :: 3.6", "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", ] requires-python = ">=2.7" [tool.flit.metadata.requires-extra] test = [ "pytest >=2.1.0", ] [tool.flit.sdist] include = [ "CHANGELOG", "hashids.py", "LICENSE", "pyproject.toml", "README.rst", "test/*.py", ] exclude = [ ".*" ] hashids-python-1.3.1/test/000077500000000000000000000000001370737465500154565ustar00rootroot00000000000000hashids-python-1.3.1/test/test_hashids.py000066400000000000000000000171121370737465500205140ustar00rootroot00000000000000from hashids import Hashids import pytest class TestConstructor(object): def test_small_alphabet_with_no_repeating_characters(self): pytest.raises(ValueError, Hashids, alphabet='abcdefghijklmno') def test_small_alphabet_with_repeating_characters(self): pytest.raises(ValueError, Hashids, alphabet='abcdecfghijklbmnoa') class TestEncoding(object): def test_empty_call(self): assert Hashids().encode() == '' def test_default_salt(self): assert Hashids().encode(1, 2, 3) == 'o2fXhV' def test_single_number(self): h = Hashids() assert h.encode(12345) == 'j0gW' assert h.encode(1) == 'jR' assert h.encode(22) == 'Lw' assert h.encode(333) == 'Z0E' assert h.encode(9999) == 'w0rR' def test_multiple_numbers(self): h = Hashids() assert h.encode(683, 94108, 123, 5) == 'vJvi7On9cXGtD' assert h.encode(1, 2, 3) == 'o2fXhV' assert h.encode(2, 4, 6) == 'xGhmsW' assert h.encode(99, 25) == '3lKfD' def test_salt(self): h = Hashids(salt='Arbitrary string') assert h.encode(683, 94108, 123, 5) == 'QWyf8yboH7KT2' assert h.encode(1, 2, 3) == 'neHrCa' assert h.encode(2, 4, 6) == 'LRCgf2' assert h.encode(99, 25) == 'JOMh1' def test_alphabet(self): h = Hashids(alphabet='!"#%&\',-/0123456789:;<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz~') assert h.encode(2839, 12, 32, 5) == '_nJUNTVU3' assert h.encode(1, 2, 3) == '7xfYh2' assert h.encode(23832) == 'Z6R>' assert h.encode(99, 25) == 'AYyIB' def test_short_alphabet(self): h = Hashids(alphabet='ABcfhistuCFHISTU') assert h.encode(2839, 12, 32, 5) == 'AABAABBBABAAAuBBAAUABBBBBCBAB' assert h.encode(1, 2, 3) == 'AAhBAiAA' assert h.encode(23832) == 'AABAAABABBBAABBB' assert h.encode(99, 25) == 'AAABBBAAHBBAAB' def test_min_length(self): h = Hashids(min_length=25) assert h.encode(7452, 2967, 21401) == 'pO3K69b86jzc6krI416enr2B5' assert h.encode(1, 2, 3) == 'gyOwl4B97bo2fXhVaDR0Znjrq' assert h.encode(6097) == 'Nz7x3VXyMYerRmWeOBQn6LlRG' assert h.encode(99, 25) == 'k91nqP3RBe3lKfDaLJrvy8XjV' def test_all_parameters(self): h = Hashids('arbitrary salt', 16, 'abcdefghijklmnopqrstuvwxyz') assert h.encode(7452, 2967, 21401) == 'wygqxeunkatjgkrw' assert h.encode(1, 2, 3) == 'pnovxlaxuriowydb' assert h.encode(60125) == 'jkbgxljrjxmlaonp' assert h.encode(99, 25) == 'erdjpwrgouoxlvbx' def test_alphabet_without_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890') assert h.encode(7452, 2967, 21401) == 'X50Yg6VPoAO4' assert h.encode(1, 2, 3) == 'GAbDdR' assert h.encode(60125) == '5NMPD' assert h.encode(99, 25) == 'yGya5' def test_alphabet_with_two_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890uC') assert h.encode(7452, 2967, 21401) == 'GJNNmKYzbPBw' assert h.encode(1, 2, 3) == 'DQCXa4' assert h.encode(60125) == '38V1D' assert h.encode(99, 25) == '373az' def test_negative_call(self): assert Hashids().encode(1, -2, 3) == '' def test_float_call(self): assert Hashids().encode(1, 2.5, 3) == '' def test_encode_hex(self): assert Hashids().encode_hex('507f1f77bcf86cd799439011') == 'y42LW46J9luq3Xq9XMly' assert len(Hashids(min_length=1000).encode_hex('507f1f77bcf86cd799439011')) >= 1000 assert Hashids().encode_hex('f000000000000000000000000000000000000000000000000000000000000000000000000000000000000f') == \ 'WxMLpERDrmh25Lp4L3xEfM6WovWYO3IjkRMKR2ogCMVzn4zQlqt1WK8jKq7OsEpy2qyw1Vi2p' def test_illegal_hex(self): assert Hashids().encode_hex('') == '' assert Hashids().encode_hex('1234SGT8') == '' class TestDecoding(object): def test_empty_string(self): assert Hashids().decode('') == () def test_non_string(self): assert Hashids().decode(object()) == () def test_default_salt(self): assert Hashids().decode('o2fXhV') == (1, 2, 3) def test_empty_call(self): assert Hashids().decode('') == () def test_single_number(self): h = Hashids() assert h.decode('j0gW') == (12345,) assert h.decode('jR') == (1,) assert h.decode('Lw') == (22,) assert h.decode('Z0E') == (333,) assert h.decode('w0rR') == (9999,) def test_multiple_numbers(self): h = Hashids() assert h.decode('vJvi7On9cXGtD') == (683, 94108, 123, 5) assert h.decode('o2fXhV') == (1, 2, 3) assert h.decode('xGhmsW') == (2, 4, 6) assert h.decode('3lKfD') == (99, 25) def test_salt(self): h = Hashids(salt='Arbitrary string') assert h.decode('QWyf8yboH7KT2') == (683, 94108, 123, 5) assert h.decode('neHrCa') == (1, 2, 3) assert h.decode('LRCgf2') == (2, 4, 6) assert h.decode('JOMh1') == (99, 25) def test_alphabet(self): h = Hashids(alphabet='!"#%&\',-/0123456789:;<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz~') assert h.decode('_nJUNTVU3') == (2839, 12, 32, 5) assert h.decode('7xfYh2') == (1, 2, 3) assert h.decode('Z6R>') == (23832,) assert h.decode('AYyIB') == (99, 25) def test_min_length(self): h = Hashids(min_length=25) assert h.decode('pO3K69b86jzc6krI416enr2B5') == (7452, 2967, 21401) assert h.decode('gyOwl4B97bo2fXhVaDR0Znjrq') == (1, 2, 3) assert h.decode('Nz7x3VXyMYerRmWeOBQn6LlRG') == (6097,) assert h.decode('k91nqP3RBe3lKfDaLJrvy8XjV') == (99, 25) def test_all_parameters(self): h = Hashids('arbitrary salt', 16, 'abcdefghijklmnopqrstuvwxyz') assert h.decode('wygqxeunkatjgkrw') == (7452, 2967, 21401) assert h.decode('pnovxlaxuriowydb') == (1, 2, 3) assert h.decode('jkbgxljrjxmlaonp') == (60125,) assert h.decode('erdjpwrgouoxlvbx') == (99, 25) def test_invalid_hash(self): assert Hashids(alphabet='abcdefghijklmnop').decode('qrstuvwxyz') == () def test_alphabet_without_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890') assert h.decode('X50Yg6VPoAO4') == (7452, 2967, 21401) assert h.decode('GAbDdR') == (1, 2, 3) assert h.decode('5NMPD') == (60125,) assert h.decode('yGya5') == (99, 25) def test_alphabet_with_two_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890uC') assert h.decode('GJNNmKYzbPBw') == (7452, 2967, 21401) assert h.decode('DQCXa4') == (1, 2, 3) assert h.decode('38V1D') == (60125,) assert h.decode('373az') == (99, 25) def test_only_one_valid(self): h = Hashids(min_length=6) assert h.decode(h.encode(1)[:-1] + '0') == () def test_decode_hex(self): hex_str = '507f1f77bcf86cd799439011' assert Hashids().decode_hex('y42LW46J9luq3Xq9XMly') == hex_str h = Hashids(min_length=1000) assert h.decode_hex(h.encode_hex(hex_str)) == hex_str assert Hashids().decode_hex('WxMLpERDrmh25Lp4L3xEfM6WovWYO3IjkRMKR2ogCMVzn4zQlqt1WK8jKq7OsEpy2qyw1Vi2p') == \ 'f000000000000000000000000000000000000000000000000000000000000000000000000000000000000f' def test_illegal_decode_hex(self): assert Hashids().decode_hex('') == '' assert Hashids().decode_hex('WxMLpERDrmh25Lp4L3xEfM6WovWYO3IjkRMKR2ogCMVlqt1WK8jKq7OsEp1Vi2p') == '' hashids-python-1.3.1/test/test_legacy.py000066400000000000000000000137631370737465500203450ustar00rootroot00000000000000from hashids import Hashids import pytest class TestConstructor(object): def test_small_alphabet(self): pytest.raises(ValueError, Hashids, alphabet='abcabc') class TestEncryption(object): def test_empty_call(self): assert Hashids().encrypt() == '' def test_default_salt(self): assert Hashids().encrypt(1, 2, 3) == 'o2fXhV' def test_single_number(self): h = Hashids() assert h.encrypt(12345) == 'j0gW' assert h.encrypt(1) == 'jR' assert h.encrypt(22) == 'Lw' assert h.encrypt(333) == 'Z0E' assert h.encrypt(9999) == 'w0rR' def test_multiple_numbers(self): h = Hashids() assert h.encrypt(683, 94108, 123, 5) == 'vJvi7On9cXGtD' assert h.encrypt(1, 2, 3) == 'o2fXhV' assert h.encrypt(2, 4, 6) == 'xGhmsW' assert h.encrypt(99, 25) == '3lKfD' def test_salt(self): h = Hashids(salt='Arbitrary string') assert h.encrypt(683, 94108, 123, 5) == 'QWyf8yboH7KT2' assert h.encrypt(1, 2, 3) == 'neHrCa' assert h.encrypt(2, 4, 6) == 'LRCgf2' assert h.encrypt(99, 25) == 'JOMh1' def test_alphabet(self): h = Hashids(alphabet='!"#%&\',-/0123456789:;<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz~') assert h.encrypt(2839, 12, 32, 5) == '_nJUNTVU3' assert h.encrypt(1, 2, 3) == '7xfYh2' assert h.encrypt(23832) == 'Z6R>' assert h.encrypt(99, 25) == 'AYyIB' def test_min_length(self): h = Hashids(min_length=25) assert h.encrypt(7452, 2967, 21401) == 'pO3K69b86jzc6krI416enr2B5' assert h.encrypt(1, 2, 3) == 'gyOwl4B97bo2fXhVaDR0Znjrq' assert h.encrypt(6097) == 'Nz7x3VXyMYerRmWeOBQn6LlRG' assert h.encrypt(99, 25) == 'k91nqP3RBe3lKfDaLJrvy8XjV' def test_all_parameters(self): h = Hashids('arbitrary salt', 16, 'abcdefghijklmnopqrstuvwxyz') assert h.encrypt(7452, 2967, 21401) == 'wygqxeunkatjgkrw' assert h.encrypt(1, 2, 3) == 'pnovxlaxuriowydb' assert h.encrypt(60125) == 'jkbgxljrjxmlaonp' assert h.encrypt(99, 25) == 'erdjpwrgouoxlvbx' def test_alphabet_without_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890') assert h.encrypt(7452, 2967, 21401) == 'X50Yg6VPoAO4' assert h.encrypt(1, 2, 3) == 'GAbDdR' assert h.encrypt(60125) == '5NMPD' assert h.encrypt(99, 25) == 'yGya5' def test_alphabet_with_two_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890uC') assert h.encrypt(7452, 2967, 21401) == 'GJNNmKYzbPBw' assert h.encrypt(1, 2, 3) == 'DQCXa4' assert h.encrypt(60125) == '38V1D' assert h.encrypt(99, 25) == '373az' def test_negative_call(self): assert Hashids().encrypt(1, -2, 3) == '' def test_float_call(self): assert Hashids().encrypt(1, 2.5, 3) == '' class TestDecryption(object): def test_empty_string(self): assert Hashids().decrypt('') == () def test_non_string(self): assert Hashids().decrypt(object()) == () def test_default_salt(self): assert Hashids().decrypt('o2fXhV') == (1, 2, 3) def test_empty_call(self): assert Hashids().decrypt('') == () def test_single_number(self): h = Hashids() assert h.decrypt('j0gW') == (12345,) assert h.decrypt('jR') == (1,) assert h.decrypt('Lw') == (22,) assert h.decrypt('Z0E') == (333,) assert h.decrypt('w0rR') == (9999,) def test_multiple_numbers(self): h = Hashids() assert h.decrypt('vJvi7On9cXGtD') == (683, 94108, 123, 5,) assert h.decrypt('o2fXhV') == (1, 2, 3,) assert h.decrypt('xGhmsW') == (2, 4, 6,) assert h.decrypt('3lKfD') == (99, 25,) def test_salt(self): h = Hashids(salt='Arbitrary string') assert h.decrypt('QWyf8yboH7KT2') == (683, 94108, 123, 5,) assert h.decrypt('neHrCa') == (1, 2, 3,) assert h.decrypt('LRCgf2') == (2, 4, 6,) assert h.decrypt('JOMh1') == (99, 25,) def test_alphabet(self): h = Hashids(alphabet='!"#%&\',-/0123456789:;<=>ABCDEFGHIJKLMNOPQRSTUVWXYZ_`abcdefghijklmnopqrstuvwxyz~') assert h.decrypt('_nJUNTVU3') == (2839, 12, 32, 5,) assert h.decrypt('7xfYh2') == (1, 2, 3,) assert h.decrypt('Z6R>') == (23832,) assert h.decrypt('AYyIB') == (99, 25,) def test_min_length(self): h = Hashids(min_length=25) assert h.decrypt('pO3K69b86jzc6krI416enr2B5') == (7452, 2967, 21401,) assert h.decrypt('gyOwl4B97bo2fXhVaDR0Znjrq') == (1, 2, 3,) assert h.decrypt('Nz7x3VXyMYerRmWeOBQn6LlRG') == (6097,) assert h.decrypt('k91nqP3RBe3lKfDaLJrvy8XjV') == (99, 25,) def test_all_parameters(self): h = Hashids('arbitrary salt', 16, 'abcdefghijklmnopqrstuvwxyz') assert h.decrypt('wygqxeunkatjgkrw') == (7452, 2967, 21401,) assert h.decrypt('pnovxlaxuriowydb') == (1, 2, 3,) assert h.decrypt('jkbgxljrjxmlaonp') == (60125,) assert h.decrypt('erdjpwrgouoxlvbx') == (99, 25,) def test_invalid_hash(self): assert Hashids(alphabet='abcdefghijklmnop').decrypt('qrstuvwxyz') == () def test_alphabet_without_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890') assert h.decrypt('X50Yg6VPoAO4') == (7452, 2967, 21401) assert h.decrypt('GAbDdR') == (1, 2, 3) assert h.decrypt('5NMPD') == (60125,) assert h.decrypt('yGya5') == (99, 25) def test_alphabet_with_two_standard_separators(self): h = Hashids(alphabet='abdegjklmnopqrvwxyzABDEGJKLMNOPQRVWXYZ1234567890uC') assert h.decrypt('GJNNmKYzbPBw') == (7452, 2967, 21401) assert h.decrypt('DQCXa4') == (1, 2, 3) assert h.decrypt('38V1D') == (60125,) assert h.decrypt('373az') == (99, 25) def test_only_one_valid(self): h = Hashids(min_length=6) assert h.decrypt(h.encrypt(1)[:-1] + '0') == ()