hashids-1.2.0/ 0000755 0000765 0000024 00000000000 13034020624 013341 5 ustar david staff 0000000 0000000 hashids-1.2.0/CHANGELOG 0000644 0000765 0000024 00000001105 13034017427 014557 0 ustar david staff 0000000 0000000 v1.2.0 / 2017-01-06
- performance optimizations (Jakub Kramarz)
- version classifiers (Patrick Mézard)
v1.1.0 / 2015-03-31
- add encode_hex() / decode_hex()
v1.0.3 / 2015-02-26
- remove dependency to `future`
v1.0.2 / 2015-01-15
- compatibility with JS version 1.0.x
v1.0.1 / 2014-06-19
- only decode hashids if the re-encoded result equals the input
v1.0.0 / 2014-04-21
- compatibility with JS version 0.3.x
v0.8.4 / 2014-02-04
- Make setup.py compatible with older python versions
v0.8.3 / 2013-06-02
- initial release, compatible with JS version 0.1.x
hashids-1.2.0/hashids.py 0000644 0000765 0000024 00000021156 13034020347 015345 0 ustar david staff 0000000 0000000 """Implements the hashids algorithm in python. For more information, visit
http://www.hashids.org/. Compatible with Python 2.6, 2.7 and 3"""
import warnings
from functools import wraps
from math import ceil
__version__ = '1.2.0'
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):
"""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.*.*' %
func.__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)
self.encrypt = _deprecated(self.encode)
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-1.2.0/LICENSE 0000644 0000765 0000024 00000002063 12320342040 014343 0 ustar david staff 0000000 0000000 Copyright (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-1.2.0/PKG-INFO 0000644 0000765 0000024 00000015216 13034020624 014443 0 ustar david staff 0000000 0000000 Metadata-Version: 1.1
Name: hashids
Version: 1.2.0
Summary: Python implementation of hashids (http://www.hashids.org).Compatible with python 2.6-3.
Home-page: https://github.com/davidaurelio/hashids-python
Author: David Aurelio
Author-email: dev@david-aurelio.com
License: MIT License
Description: ========================
hashids for Python 2.6–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.6, 2.7, 3.2, 3.3 and 3.4. 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 and v1.0.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.
Platform: UNKNOWN
Classifier: Programming Language :: Python :: 2
Classifier: Programming Language :: Python :: 2.6
Classifier: Programming Language :: Python :: 2.7
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.2
Classifier: Programming Language :: Python :: 3.3
Classifier: Programming Language :: Python :: 3.4
Classifier: Programming Language :: Python :: 3.5
Classifier: Programming Language :: Python :: 3.6
hashids-1.2.0/README.rst 0000644 0000765 0000024 00000011143 12473716410 015043 0 ustar david staff 0000000 0000000 ========================
hashids for Python 2.6–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.6, 2.7, 3.2, 3.3 and 3.4. 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 and v1.0.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-1.2.0/setup.py 0000755 0000765 0000024 00000002045 13034020337 015060 0 ustar david staff 0000000 0000000 #!/usr/bin/env python
from distutils.core import setup
from os.path import dirname, join
from codecs import open
setup(name='hashids',
version='1.2.0',
description='Python implementation of hashids (http://www.hashids.org).'
'Compatible with python 2.6-3.',
long_description=open(join(dirname(__file__), 'README.rst'), encoding='utf-8').read(),
author='David Aurelio',
author_email='dev@david-aurelio.com',
url='https://github.com/davidaurelio/hashids-python',
license='MIT License',
py_modules=('hashids',),
classifiers=[
'Programming Language :: Python :: 2',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3',
'Programming Language :: Python :: 3.2',
'Programming Language :: Python :: 3.3',
'Programming Language :: Python :: 3.4',
'Programming Language :: Python :: 3.5',
'Programming Language :: Python :: 3.6',
],)
hashids-1.2.0/test/ 0000755 0000765 0000024 00000000000 13034020624 014320 5 ustar david staff 0000000 0000000 hashids-1.2.0/test/test_hashids.py 0000644 0000765 0000024 00000016421 13034016012 017354 0 ustar david staff 0000000 0000000 from 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_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-1.2.0/test/test_legacy.py 0000644 0000765 0000024 00000013763 12455603633 017224 0 ustar david staff 0000000 0000000 from 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') == ()