Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 18 additions & 13 deletions base62.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,15 @@
__title__ = 'base62'
__author__ = 'Sumin Byeon'
__email__ = 'suminb@gmail.com'
__version__ = '0.3.3'
__version__ = '0.4.0'

CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
BASE = 62
CHARSET_DEFAULT = (
'0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
)
CHARSET_INVERTED = (
'0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ'
)


def bytes_to_int(s, byteorder='big', signed=False):
Expand All @@ -36,58 +41,58 @@ def bytes_to_int(s, byteorder='big', signed=False):
return sum(ds)


def encode(n, minlen=1):
def encode(n, minlen=1, charset=CHARSET_DEFAULT):
"""Encodes a given integer ``n``."""

chs = []
while n > 0:
r = n % BASE
n //= BASE

chs.append(CHARSET[r])
chs.append(charset[r])

if len(chs) > 0:
chs.reverse()
else:
chs.append('0')

s = ''.join(chs)
s = CHARSET[0] * max(minlen - len(s), 0) + s
s = charset[0] * max(minlen - len(s), 0) + s
return s


def encodebytes(s):
def encodebytes(s, charset=CHARSET_DEFAULT):
"""Encodes a bytestring into a base62 string.

:param s: A byte array
"""

_check_bytes_type(s)
return encode(bytes_to_int(s))
return encode(bytes_to_int(s), charset=charset)


def decode(b):
def decode(b, charset=CHARSET_DEFAULT):
"""Decodes a base62 encoded value ``b``."""

if b.startswith('0z'):
b = b[2:]

l, i, v = len(b), 0, 0
for x in b:
v += _value(x) * (BASE ** (l - (i + 1)))
v += _value(x, charset=charset) * (BASE ** (l - (i + 1)))
i += 1

return v


def decodebytes(s):
def decodebytes(s, charset=CHARSET_DEFAULT):
"""Decodes a string of base62 data into a bytes object.

:param s: A string to be decoded in base62
:rtype: bytes
"""

decoded = decode(s)
decoded = decode(s, charset=charset)
buf = bytearray()
while decoded > 0:
buf.append(decoded & 0xff)
Expand All @@ -97,11 +102,11 @@ def decodebytes(s):
return bytes(buf)


def _value(ch):
def _value(ch, charset):
"""Decodes an individual digit of a base62 encoded string."""

try:
return CHARSET.index(ch)
return charset.index(ch)
except ValueError:
raise ValueError('base62: Invalid character (%s)' % ch)

Expand Down
23 changes: 22 additions & 1 deletion tests/test_basic.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@


def test_const():
assert len(base62.CHARSET) == base62.BASE == 62
assert len(base62.CHARSET_DEFAULT) == base62.BASE == 62
assert len(base62.CHARSET_INVERTED) == base62.BASE == 62


def test_basic():
Expand All @@ -35,6 +36,26 @@ def test_basic():
assert base62.decode('0zbase62') == 34441886726


def test_basic_inverted():
kwargs = {'charset': base62.CHARSET_INVERTED}

assert base62.encode(0, **kwargs) == '0'
assert base62.encode(0, minlen=0, **kwargs) == '0'
assert base62.encode(0, minlen=1, **kwargs) == '0'
assert base62.encode(0, minlen=5, **kwargs) == '00000'
assert base62.decode('0', **kwargs) == 0
assert base62.decode('0000', **kwargs) == 0
assert base62.decode('000001', **kwargs) == 1

assert base62.encode(10231951886, **kwargs) == 'base62'
assert base62.decode('base62', **kwargs) == 10231951886

# NOTE: For backward compatibility. When I first wrote this module in PHP,
# I used to use the `0z` prefix to denote a base62 encoded string (similar
# to `0x` for hexadecimal strings).
assert base62.decode('0zbase62', **kwargs) == 10231951886


@pytest.mark.parametrize('b, i', bytes_int_pairs)
def test_bytes_to_int(b, i):
assert base62.bytes_to_int(b) == i
Expand Down