Skip to content

Commit

Permalink
Merge branch 'release/0.3.0'
Browse files Browse the repository at this point in the history
  • Loading branch information
suminb committed May 21, 2017
2 parents 5912b36 + a266a37 commit dfb499c
Show file tree
Hide file tree
Showing 5 changed files with 131 additions and 19 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ python:
# - "pypy" # disable pypy builds until supported by trusty containers

install:
- pip install --requirement requirements.txt
- pip install --requirement tests/requirements.txt

script:
- py.test tests --cov base62 --durations=10
Expand Down
16 changes: 16 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -30,3 +30,19 @@ Usage
:target: https://travis-ci.org/suminb/base62
.. |PyPI| image:: https://img.shields.io/pypi/v/pybase62.svg
:target: https://pypi.python.org/pypi/pybase62


Tests
-----

You may run some test cases to ensure all functionalities are operational.

::

py.test -v

If ``pytest`` is not installed, you may want to run the following commands:

::

pip install -r tests/requirements.txt
88 changes: 70 additions & 18 deletions base62.py
Original file line number Diff line number Diff line change
@@ -1,31 +1,63 @@

"""
Original PHP code from http://blog.suminb.com/archives/558
"""

__author__ = 'Sumin Byeon'
__email__ = 'suminb@gmail.com'
__version__ = '0.1.3'
__version__ = '0.3.0'

CHARSET = '0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
BASE = 62
DEFAULT_ENCODING = 'utf-8'


def bytes_to_int(s, byteorder='big', signed=False):
"""Converts a byte array to an integer value. Python 3 comes with a
built-in function to do this, but we would like to keep our code Python 2
compatible.
"""
try:
return int.from_bytes(s, byteorder, signed=signed)
except AttributeError:
# For Python 2.x
if byteorder != 'big' or signed:
raise NotImplementedError()

# NOTE: This won't work if a generator is given
n = len(s)
ds = (x << (8 * (n - 1 - i)) for i, x in enumerate(bytearray(s)))

return sum(ds)

def encode(n):

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

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

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

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

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


def encodebytes(s):
"""Encode a bytestring into a base62 string.
:param s: A byte array
"""
_check_bytes_type(s)
return encode(bytes_to_int(s))

return ''.join(s)

def decode(b):
"""Encodes a base62 encoded value ``b``."""
Expand All @@ -35,19 +67,39 @@ def decode(b):

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

return v


def decodebytes(s):
"""Decodes a string of base62 data into a bytes object.
:param s: A string to be decoded in base62
:rtype: bytes
"""
decoded = decode(s)
buf = bytearray()
while decoded > 0:
buf.append(decoded & 0xff)
decoded //= 256
buf.reverse()

return bytes(buf)


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

if ch in '01234567890':
return ord(ch) - ord('0')
elif ch in 'ABCDEFGHIJKLMNOPQRSTUVWXYZ':
return ord(ch) - ord('A') + 10
elif ch in 'abcdefghijklmnopqrstuvwxyz':
return ord(ch) - ord('a') + 36
else:
raise RuntimeError('base62: Invalid character (%s)' % ch)
try:
return CHARSET.index(ch)
except ValueError:
raise ValueError('base62: Invalid character (%s)' % ch)


def _check_bytes_type(s):
"""Checks if the input is in an appropriate type."""
if not isinstance(s, bytes):
msg = 'expected bytes-like object, not %s' % s.__class__.__name__
raise TypeError(msg)
File renamed without changes.
44 changes: 44 additions & 0 deletions tests/test_basic.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,52 @@
import pytest

import base62


bytes_int_pairs = [
(b'\x00', 0),
(b'\x01', 1),
(b'\x01\x01', 0x0101),
(b'\xff\xff', 0xffff),
(b'\x01\x01\x01', 0x010101),
(b'\x01\x02\x03\x04\x05\x06\x07\x08', 0x0102030405060708),
]


def test_basic():
assert base62.encode(0) == '0'
assert base62.encode(0, minlen=0) == '0'
assert base62.encode(0, minlen=1) == '0'
assert base62.encode(0, minlen=5) == '00000'
assert base62.decode('0') == 0
assert base62.decode('0000') == 0
assert base62.decode('000001') == 1

assert base62.encode(34441886726) == 'base62'
assert base62.decode('base62') == 34441886726


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


@pytest.mark.parametrize('b, i', bytes_int_pairs)
def test_encodebytes(b, i):
assert base62.encodebytes(b) == base62.encode(i)


@pytest.mark.parametrize('s', ['0', '1', 'a', 'z', 'ykzvd7ga'])
def test_decodebytes(s):
assert base62.bytes_to_int(base62.decodebytes(s)) == base62.decode(s)


@pytest.mark.parametrize('input_bytes', [
b'', b'0', b'bytes to encode', b'\x01\x00\x80'])
def test_roundtrip(input_bytes):
"""Ensures type consistency. Suggested by @dhimmel"""
base62_encoded = base62.encodebytes(input_bytes)
assert isinstance(base62_encoded, str)
output_bytes = base62.decodebytes(base62_encoded)
assert isinstance(output_bytes, bytes)
assert input_bytes == output_bytes

0 comments on commit dfb499c

Please sign in to comment.