diff --git a/.gitignore b/.gitignore index e39c2e8..9c06952 100644 --- a/.gitignore +++ b/.gitignore @@ -1,3 +1,4 @@ +.* *~ __pycache__ *egg-info* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..61bee17 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,14 @@ +repos: +- repo: https://github.com/ambv/black + rev: 19.10b0 + hooks: + - id: black +- repo: https://github.com/timothycrosley/isort + rev: '5.4.2' + hooks: + - id: isort + additional_dependencies: ['toml'] +- repo: https://gitlab.com/pycqa/flake8 + rev: '3.8.3' + hooks: + - id: flake8 \ No newline at end of file diff --git a/.travis.yml b/.travis.yml index b68c370..32ccfa9 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,17 +1,17 @@ language: python python: - - "3.3" - - "3.4" - "3.5" - "3.6" + - "3.7" + - "3.8" env: - EXTRAS="N" - EXTRAS="Y" install: - - if [ $EXTRAS == 'Y' ]; then pip install -r supported_pkgs.txt; fi + - if [ $EXTRAS == 'Y' ]; then pip install .[all]; fi - pip install coveralls script: diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..79021c0 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,13 @@ +[build-system] +requires = ["setuptools", "wheel", "setuptools_scm[toml]"] +build-backend = "setuptools.build_meta" + +[tool.setuptools_scm] + +[tool.isort] +line_length = 88 # Black default +multi_line_output = 3 +include_trailing_comma = true + +[tool.check-manifest] +ignore = [".pre-commit-config.yaml"] \ No newline at end of file diff --git a/serialize/__init__.py b/serialize/__init__.py index 110c3f7..8779cf5 100644 --- a/serialize/__init__.py +++ b/serialize/__init__.py @@ -9,28 +9,26 @@ :license: BSD, see LICENSE for more details. """ +from contextlib import suppress from importlib import import_module +from .all import dump, dumps, load, loads, register_class + # Modules that help serialize use other packages. -_MODULES = ('bson', - 'dill', - 'json', - 'msgpack', - 'phpserialize', - 'pickle', - 'serpent', - 'yaml') +_MODULES = ( + "bson", + "dill", + "json", + "msgpack", + "phpserialize", + "pickle", + "serpent", + "yaml", +) for name in _MODULES: - try: - import_module('.' + name, 'serialize') - except: - pass - -# Others to consider in the future for specialized serialization: -# CSV, pandas.DATAFRAMES, hickle, hdf5 - -from .all import dump, dumps, load, loads, register_class + with suppress(ImportError): + import_module("." + name, "serialize") -__all__ = ['dump', 'dumps', 'load', 'loads', 'register_class'] +__all__ = ["dump", "dumps", "load", "loads", "register_class"] diff --git a/serialize/all.py b/serialize/all.py index b10c87b..fd2a68c 100644 --- a/serialize/all.py +++ b/serialize/all.py @@ -13,13 +13,14 @@ from collections import namedtuple from io import BytesIO from os.path import splitext +from pathlib import Path #: Stores the functions to convert custom classes to and from builtin types. -ClassHelper = namedtuple('ClassHelper', 'to_builtin from_builtin') +ClassHelper = namedtuple("ClassHelper", "to_builtin from_builtin") #: Stores information and function about each format type. -Format = namedtuple('Format', 'extension dump dumps load loads') -UnavailableFormat = namedtuple('UnavailableFormat', 'extension msg') +Format = namedtuple("Format", "extension dump dumps load loads") +UnavailableFormat = namedtuple("UnavailableFormat", "extension msg") #: Map unavailable formats to the corresponding error message. # :type: str -> UnavailableFormat @@ -52,9 +53,14 @@ def _get_format(fmt): return FORMATS[fmt] if fmt in UNAVAILABLE_FORMATS: - raise ValueError(("'%s' is an unavailable format. " % fmt) + UNAVAILABLE_FORMATS[fmt].msg) + raise ValueError( + ("'%s' is an unavailable format. " % fmt) + UNAVAILABLE_FORMATS[fmt].msg + ) - raise ValueError("'%s' is an unknown format. Valid options are %s" % (fmt, ', '.join(FORMATS.keys()))) + raise ValueError( + "'%s' is an unknown format. Valid options are %s" + % (fmt, ", ".join(FORMATS.keys())) + ) def _get_format_from_ext(ext): @@ -67,10 +73,11 @@ def _get_format_from_ext(ext): if ext in FORMAT_BY_EXTENSION: return FORMAT_BY_EXTENSION[ext] - valid = ', '.join(FORMAT_BY_EXTENSION.keys()) + valid = ", ".join(FORMAT_BY_EXTENSION.keys()) - raise ValueError("'%s' is an unknown extension. " - "Valid options are %s" % (ext, valid)) + raise ValueError( + "'%s' is an unknown extension. " "Valid options are %s" % (ext, valid) + ) def encode_helper(obj, to_builtin): @@ -78,8 +85,7 @@ def encode_helper(obj, to_builtin): that can convert it to a builtin data type. """ - return dict(__class_name__= str(obj.__class__), - __dumped_obj__=to_builtin(obj)) + return dict(__class_name__=str(obj.__class__), __dumped_obj__=to_builtin(obj)) def encode(obj, defaultfunc=None): @@ -98,8 +104,10 @@ def encode(obj, defaultfunc=None): def _traverse_dict_ec(obj, ef, td): - return {traverse_and_encode(k, ef, td): traverse_and_encode(v, ef, td) - for k, v in obj.items()} + return { + traverse_and_encode(k, ef, td): traverse_and_encode(v, ef, td) + for k, v in obj.items() + } def _traverse_list_ec(obj, ef, td): @@ -145,14 +153,14 @@ def decode(dct, classes_by_name=None): if not isinstance(dct, dict): return dct - s = dct.get('__class_name__', None) + s = dct.get("__class_name__", None) if s is None: return dct classes_by_name = classes_by_name or CLASSES_BY_NAME try: _, from_builtin = classes_by_name[s] - c = dct['__dumped_obj__'] + c = dct["__dumped_obj__"] except KeyError: return dct @@ -160,15 +168,19 @@ def decode(dct, classes_by_name=None): def _traverse_dict_dc(obj, df, td): - if '__class_name__' in obj: + if "__class_name__" in obj: return df(obj) - return {traverse_and_decode(k, df, td): traverse_and_decode(v, df, td) - for k, v in obj.items()} + return { + traverse_and_decode(k, df, td): traverse_and_decode(v, df, td) + for k, v in obj.items() + } + def _traverse_list_dc(obj, df, td): return [traverse_and_decode(el, df, td) for el in obj] + def _traverse_tuple_dc(obj, df, td): return tuple(traverse_and_decode(el, df, td) for el in obj) @@ -203,7 +215,9 @@ def traverse_and_decode(obj, decode_func=None, trav_dict=None): MISSING = object() -def register_format(fmt, dumpser=None, loadser=None, dumper=None, loader=None, extension=MISSING): +def register_format( + fmt, dumpser=None, loadser=None, dumper=None, loader=None, extension=MISSING +): """Register an available serialization format. `fmt` is a unique string identifying the format, such as `json`. Use a colon (`:`) to @@ -228,38 +242,44 @@ def register_format(fmt, dumpser=None, loadser=None, dumper=None, loader=None, e # Here we generate dumper/dumpser if they are not present. if dumper and not dumpser: + def dumpser(obj): buf = BytesIO() dumper(obj, buf) return buf.getvalue() elif not dumper and dumpser: + def dumper(obj, fp): fp.write(dumpser(obj)) elif not dumper and not dumpser: + def raiser(*args, **kwargs): - raise ValueError('dump/dumps is not defined for %s' % fmt) + raise ValueError("dump/dumps is not defined for %s" % fmt) dumper = dumpser = raiser # Here we generate loader/loadser if they are not present. if loader and not loadser: + def loadser(serialized): return loader(BytesIO(serialized)) elif not loader and loadser: + def loader(fp): return loadser(fp.read()) elif not loader and not loadser: + def raiser(*args, **kwargs): - raise ValueError('load/loads is not defined for %s' % fmt) + raise ValueError("load/loads is not defined for %s" % fmt) loader = loadser = raiser if extension is MISSING: - extension = fmt.split(':', 1)[0] + extension = fmt.split(":", 1)[0] FORMATS[fmt] = Format(extension, dumper, dumpser, loader, loadser) @@ -267,7 +287,7 @@ def raiser(*args, **kwargs): FORMAT_BY_EXTENSION[extension.lower()] = fmt -def register_unavailable(fmt, msg='', pkg='', extension=MISSING): +def register_unavailable(fmt, msg="", pkg="", extension=MISSING): """Register an unavailable serialization format. Unavailable formats are those known by Serialize but that cannot be used @@ -275,10 +295,10 @@ def register_unavailable(fmt, msg='', pkg='', extension=MISSING): """ if pkg: - msg = 'This serialization format requires the %s package.' % pkg + msg = "This serialization format requires the %s package." % pkg if extension is MISSING: - extension = fmt.split(':', 1)[0] + extension = fmt.split(":", 1)[0] UNAVAILABLE_FORMATS[fmt] = UnavailableFormat(extension, msg) @@ -300,11 +320,11 @@ def dump(obj, filename_or_file, fmt=None): In the latter case the fmt is not need if it can be guessed from the extension. """ - if isinstance(filename_or_file, str): + if isinstance(filename_or_file, (str, Path)): if fmt is None: _, ext = splitext(filename_or_file) - fmt = _get_format_from_ext(ext.strip('.')) - with open(filename_or_file, 'wb') as fp: + fmt = _get_format_from_ext(ext.strip(".")) + with open(filename_or_file, "wb") as fp: dump(obj, fp, fmt) else: _get_format(fmt).dump(obj, filename_or_file) @@ -324,11 +344,11 @@ def load(filename_or_file, fmt=None): In the latter case the fmt is not need if it can be guessed from the extension. """ - if isinstance(filename_or_file, str): + if isinstance(filename_or_file, (str, Path)): if fmt is None: _, ext = splitext(filename_or_file) - fmt = _get_format_from_ext(ext.strip('.')) - with open(filename_or_file, 'rb') as fp: + fmt = _get_format_from_ext(ext.strip(".")) + with open(filename_or_file, "rb") as fp: return load(fp, fmt) return _get_format(fmt).load(filename_or_file) diff --git a/serialize/bson.py b/serialize/bson.py index 88f2af5..f512775 100644 --- a/serialize/bson.py +++ b/serialize/bson.py @@ -16,7 +16,7 @@ try: import bson except ImportError: - all.register_unavailable('bson', pkg='bson') + all.register_unavailable("bson", pkg="bson") raise @@ -24,6 +24,7 @@ # If necessary, we put the object in dummy dictionary # under the key __bson_follow__ + def dumps(obj): if not isinstance(obj, dict): obj = dict(__bson_follow__=obj) @@ -32,6 +33,7 @@ def dumps(obj): def loads(content): obj = all.traverse_and_decode(bson.loads(content)) - return obj.get('__bson_follow__', obj) + return obj.get("__bson_follow__", obj) + -all.register_format('bson', dumps, loads) +all.register_format("bson", dumps, loads) diff --git a/serialize/dill.py b/serialize/dill.py index e1d6987..193e147 100644 --- a/serialize/dill.py +++ b/serialize/dill.py @@ -11,15 +11,15 @@ :license: BSD, see LICENSE for more details. """ -from . import all -from . import pickle +from . import all, pickle try: import dill except ImportError: - all.register_unavailable('dill', pkg='dill') + all.register_unavailable("dill", pkg="dill") raise + class MyPickler(dill.Pickler): dispatch_table = pickle.DispatchTable() @@ -32,4 +32,5 @@ def dump(obj, fp): def load(fp): return dill.Unpickler(fp).load() -all.register_format('dill', dumper=dump, loader=load) + +all.register_format("dill", dumper=dump, loader=load) diff --git a/serialize/json.py b/serialize/json.py index d9f96df..f6ac6ad 100644 --- a/serialize/json.py +++ b/serialize/json.py @@ -15,33 +15,32 @@ try: import json -except ImportError: # pragma: no cover - all.register_unavailable('json', pkg='json') +except ImportError: # pragma: no cover + all.register_unavailable("json", pkg="json") raise class Encoder(json.JSONEncoder): - def default(self, obj): return all.encode(obj, super().default) def dumps(obj): - return json.dumps(obj, cls=Encoder).encode('utf-8') + return json.dumps(obj, cls=Encoder).encode("utf-8") def dumps_pretty(obj): - return json.dumps(obj, cls=Encoder, sort_keys=True, - indent=4, separators=(',', ': ')).encode('utf-8') + return json.dumps( + obj, cls=Encoder, sort_keys=True, indent=4, separators=(",", ": ") + ).encode("utf-8") def loads(content): - return json.loads(content.decode('utf-8'), - object_hook=all.decode) + return json.loads(content.decode("utf-8"), object_hook=all.decode) # We create two different subformats for json. # The first (default) is compact, the second is pretty. -all.register_format('json', dumps, loads) -all.register_format('json:pretty', dumps_pretty, loads) +all.register_format("json", dumps, loads) +all.register_format("json:pretty", dumps_pretty, loads) diff --git a/serialize/msgpack.py b/serialize/msgpack.py index 54752b5..27c40d8 100644 --- a/serialize/msgpack.py +++ b/serialize/msgpack.py @@ -16,7 +16,7 @@ try: import msgpack except ImportError: - all.register_unavailable('msgpack', pkg='msgpack-python') + all.register_unavailable("msgpack", pkg="msgpack-python") raise @@ -25,6 +25,7 @@ def dumps(obj): def loads(content): - return msgpack.unpackb(content, object_hook=all.decode, encoding='utf-8') + return msgpack.unpackb(content, object_hook=all.decode, raw=False) -all.register_format('msgpack', dumps, loads) + +all.register_format("msgpack", dumps, loads) diff --git a/serialize/phpserialize.py b/serialize/phpserialize.py index a5b2e5d..f1c956c 100644 --- a/serialize/phpserialize.py +++ b/serialize/phpserialize.py @@ -18,34 +18,47 @@ try: import phpserialize except ImportError: - all.register_unavailable('phpserialize', pkg='phpserialize') + all.register_unavailable("phpserialize", pkg="phpserialize") raise # PHP Serialize does not support list and tuples, so we convert them to maps. + def _traverse_list_ec(obj, ef, td): - return dict(__class_name__='builtin_list', - __dumped_obj__={ndx: all.traverse_and_encode(val, ef, td) - for ndx, val in enumerate(obj)}) + return dict( + __class_name__="builtin_list", + __dumped_obj__={ + ndx: all.traverse_and_encode(val, ef, td) for ndx, val in enumerate(obj) + }, + ) + def _traverse_tuple_ec(obj, ef, td): - return dict(__class_name__='builtin_tuple', - __dumped_obj__={ndx: all.traverse_and_encode(val, ef, td) - for ndx, val in enumerate(obj)}) + return dict( + __class_name__="builtin_tuple", + __dumped_obj__={ + ndx: all.traverse_and_encode(val, ef, td) for ndx, val in enumerate(obj) + }, + ) -CUSTOM_TRAVERSE = ChainMap({list: _traverse_list_ec, - tuple: _traverse_tuple_ec}, - all.DEFAULT_TRAVERSE_EC) +CUSTOM_TRAVERSE = ChainMap( + {list: _traverse_list_ec, tuple: _traverse_tuple_ec}, all.DEFAULT_TRAVERSE_EC +) def _helper(dct): - return (mytransverse(dct[n]) - for n in range(len(dct))) + return (mytransverse(dct[n]) for n in range(len(dct))) + + +CUSTOM_CLASSES_BY_NAME = ChainMap( + { + "builtin_list": all.ClassHelper(None, lambda obj: list(_helper(obj))), + "builtin_tuple": all.ClassHelper(None, lambda obj: tuple(_helper(obj))), + }, + all.CLASSES_BY_NAME, +) -CUSTOM_CLASSES_BY_NAME = ChainMap({'builtin_list': all.ClassHelper(None, lambda obj: list(_helper(obj))), - 'builtin_tuple': all.ClassHelper(None, lambda obj: tuple(_helper(obj)))}, - all.CLASSES_BY_NAME) def mytransverse(obj): return all.traverse_and_decode(obj, lambda o: all.decode(o, CUSTOM_CLASSES_BY_NAME)) @@ -56,8 +69,8 @@ def dumps(obj): def loads(content): - obj = phpserialize.loads(content, - charset='utf-8', decode_strings=True) + obj = phpserialize.loads(content, charset="utf-8", decode_strings=True) return mytransverse(obj) -all.register_format('phpserialize', dumps, loads) + +all.register_format("phpserialize", dumps, loads) diff --git a/serialize/pickle.py b/serialize/pickle.py index 16f1dd9..e3ecac8 100644 --- a/serialize/pickle.py +++ b/serialize/pickle.py @@ -11,38 +11,41 @@ :license: BSD, see LICENSE for more details. """ -import collections +from collections.abc import MutableMapping from . import all try: - import pickle import copyreg -except ImportError: # pragma: no cover - all.register_unavailable('pickle', pkg='pickle') + import pickle +except ImportError: # pragma: no cover + all.register_unavailable("pickle", pkg="pickle") raise -class DispatchTable(collections.MutableMapping): - +class DispatchTable(MutableMapping): def __getitem__(self, item): if item in all.CLASSES: - return lambda obj: (all.CLASSES[item].from_builtin, - (all.CLASSES[item].to_builtin(obj),), - None, None, None) + return lambda obj: ( + all.CLASSES[item].from_builtin, + (all.CLASSES[item].to_builtin(obj),), + None, + None, + None, + ) - return copyreg.dispatch_table[item] # pragma: no cover + return copyreg.dispatch_table[item] # pragma: no cover - def __setitem__(self, key, value): # pragma: no cover + def __setitem__(self, key, value): # pragma: no cover copyreg.dispatch_table[key] = value - def __delitem__(self, key): # pragma: no cover + def __delitem__(self, key): # pragma: no cover del copyreg.dispatch_table[key] - def __iter__(self): # pragma: no cover + def __iter__(self): # pragma: no cover return copyreg.dispatch_table.__iter__() - def __len__(self): # pragma: no cover + def __len__(self): # pragma: no cover return copyreg.dispatch_table.__len__() @@ -58,4 +61,5 @@ def dump(obj, fp): def load(fp): return pickle.Unpickler(fp).load() -all.register_format('pickle', dumper=dump, loader=load) + +all.register_format("pickle", dumper=dump, loader=load) diff --git a/serialize/serpent.py b/serialize/serpent.py index 49157ce..260f56a 100644 --- a/serialize/serpent.py +++ b/serialize/serpent.py @@ -16,12 +16,11 @@ try: import serpent except ImportError: - all.register_unavailable('serpent', pkg='serpent') + all.register_unavailable("serpent", pkg="serpent") raise class MySerializer(serpent.Serializer): - def _serialize(self, obj, out, level): obj = all.encode(obj) return super()._serialize(obj, out, level) @@ -34,4 +33,5 @@ def dumps(obj): def loads(content): return all.traverse_and_decode(serpent.loads(content)) -all.register_format('serpent', dumps, loads) + +all.register_format("serpent", dumps, loads) diff --git a/serialize/simplejson.py b/serialize/simplejson.py index e9d840e..d357505 100644 --- a/serialize/simplejson.py +++ b/serialize/simplejson.py @@ -17,18 +17,20 @@ try: import simplejson as json except ImportError: # pragma: no cover - all.register_unavailable('simplejson', pkg='simplejson') + all.register_unavailable("simplejson", pkg="simplejson") raise class Encoder(json.JSONEncoder): - def default(self, obj): return all.encode(obj, super().default) def _traverse_tuple_ec(obj, ef, td): - return {'__class_name__': 'tuple', '__dumped_obj': list(all.traverse_and_encode(el, ef, td) for el in obj)} + return { + "__class_name__": "tuple", + "__dumped_obj": list(all.traverse_and_encode(el, ef, td) for el in obj), + } trav_dict = { @@ -40,31 +42,38 @@ def _traverse_tuple_ec(obj, ef, td): def df(obj): """ Decode function that handles encoded tuples """ - if obj['__class_name__'] == 'tuple': - return tuple(obj['__dumped_obj']) + if obj["__class_name__"] == "tuple": + return tuple(obj["__dumped_obj"]) else: - return obj['__dumped_obj'] + return obj["__dumped_obj"] def dumps(obj): - return json.dumps(all.traverse_and_encode(obj, trav_dict=trav_dict), cls=Encoder, tuple_as_array=True).encode( - 'utf-8') + return json.dumps( + all.traverse_and_encode(obj, trav_dict=trav_dict), + cls=Encoder, + tuple_as_array=True, + ).encode("utf-8") def dumps_pretty(obj): - return json.dumps(all.traverse_and_encode(obj, trav_dict=trav_dict), cls=Encoder, sort_keys=True, - tuple_as_array=True, - indent=4, separators=(',', ': ')).encode('utf-8') + return json.dumps( + all.traverse_and_encode(obj, trav_dict=trav_dict), + cls=Encoder, + sort_keys=True, + tuple_as_array=True, + indent=4, + separators=(",", ": "), + ).encode("utf-8") def loads(content): - obj = json.loads(content.decode('utf-8'), - object_hook=all.decode) + obj = json.loads(content.decode("utf-8"), object_hook=all.decode) return all.traverse_and_decode(obj, decode_func=df) # We create two different subformats for json. # The first (default) is compact, the second is pretty. -all.register_format('simplejson', dumps, loads) -all.register_format('simplejson:pretty', dumps_pretty, loads) +all.register_format("simplejson", dumps, loads) +all.register_format("simplejson:pretty", dumps_pretty, loads) diff --git a/serialize/testsuite/__init__.py b/serialize/testsuite/__init__.py index f45ca88..0a1df64 100644 --- a/serialize/testsuite/__init__.py +++ b/serialize/testsuite/__init__.py @@ -1,6 +1,6 @@ # -*- coding: utf-8 -*- -from __future__ import division, unicode_literals, print_function, absolute_import +from __future__ import absolute_import, division, print_function, unicode_literals import os import unittest @@ -18,7 +18,7 @@ def main(): try: unittest.main() except Exception as e: - print('Error: %s' % e) + print("Error: %s" % e) def run(): @@ -28,4 +28,3 @@ def run(): """ test_runner = unittest.TextTestRunner() return test_runner.run(testsuite()) - diff --git a/serialize/testsuite/test_basic.py b/serialize/testsuite/test_basic.py index 0b65420..d2ddb0e 100644 --- a/serialize/testsuite/test_basic.py +++ b/serialize/testsuite/test_basic.py @@ -1,22 +1,25 @@ - import io import os +from pathlib import Path from unittest import TestCase, skipIf -from serialize import register_class, loads, dumps, load, dump -from serialize.all import (FORMATS, UNAVAILABLE_FORMATS, - _get_format_from_ext, _get_format, - register_format) +from serialize import dump, dumps, load, loads, register_class +from serialize.all import ( + FORMATS, + UNAVAILABLE_FORMATS, + _get_format, + _get_format_from_ext, + register_format, +) class X: - def __init__(self, a, b): self.a = a self.b = b def __eq__(self, other): - if not self.__class__ is other.__class__: + if self.__class__ is not other.__class__: return False if self.a != other.a: return False @@ -25,7 +28,7 @@ def __eq__(self, other): return True def __str__(self): - return 'X(%s, %s)' % (self.a, self.b) + return "X(%s, %s)" % (self.a, self.b) __repr__ = __str__ @@ -40,46 +43,37 @@ def from_builtin(content): register_class(X, to_builtin, from_builtin) -register_format('_test') +register_format("_test") class TestAvailable(TestCase): - - @skipIf(os.environ.get('EXTRAS', None) == 'N', 'Extras not installed') + @skipIf(os.environ.get("EXTRAS", None) == "N", "Extras not installed") def test_available(self): self.assertFalse(UNAVAILABLE_FORMATS) def test_unknown_format(self): - self.assertRaises(ValueError, dumps, 'hello', 'dummy_format') - self.assertRaises(ValueError, _get_format_from_ext, 'dummy_format') + self.assertRaises(ValueError, dumps, "hello", "dummy_format") + self.assertRaises(ValueError, _get_format_from_ext, "dummy_format") def test_no_replace(self): - self.assertRaises(ValueError, register_format, '_test') + self.assertRaises(ValueError, register_format, "_test") def test_no_dumper_no_loader(self): - self.assertRaises(ValueError, dumps, 'hello', '_test') - self.assertRaises(ValueError, loads, 'hello', '_test') + self.assertRaises(ValueError, dumps, "hello", "_test") + self.assertRaises(ValueError, loads, "hello", "_test") buf = io.BytesIO() - self.assertRaises(ValueError, dump, 'hello', buf, 'test') - self.assertRaises(ValueError, load, buf, 'test') + self.assertRaises(ValueError, dump, "hello", buf, "test") + self.assertRaises(ValueError, load, buf, "test") + NESTED_DICT = { - "level1_1": { - "level2_1": [1, 2, 3], - "level2_2": [4, 5, 6] - }, - "level1_2": { - "level2_1": [1, 2, 3], - "level2_2": [4, 5, 6] - }, - "level1_3": { - "level2_1": { - "level3_1": [1, 2, 3], - "level3_2": [4, 5, 6] - }, - "level2_2": [4, 5, 6] - } - } + "level1_1": {"level2_1": [1, 2, 3], "level2_2": [4, 5, 6]}, + "level1_2": {"level2_1": [1, 2, 3], "level2_2": [4, 5, 6]}, + "level1_3": { + "level2_1": {"level3_1": [1, 2, 3], "level3_2": [4, 5, 6]}, + "level2_2": [4, 5, 6], + }, +} class _TestEncoderDecoder: @@ -107,7 +101,7 @@ def test_file_by_name(self): fh = _get_format(self.FMT) obj = dict(answer=42) - filename1 = 'tmp.' + fh.extension + filename1 = "tmp." + fh.extension dump(obj, filename1) try: obj1 = load(filename1) @@ -115,7 +109,7 @@ def test_file_by_name(self): finally: os.remove(filename1) - filename2 = 'tmp.' + fh.extension + '.bla' + filename2 = "tmp." + fh.extension + ".bla" dump(obj, filename2, fmt=self.FMT) try: obj2 = load(filename2, fmt=self.FMT) @@ -123,20 +117,28 @@ def test_file_by_name(self): finally: os.remove(filename2) + filename3 = Path("tmp." + fh.extension) + dump(obj, filename3) + try: + obj1 = load(filename3) + self.assertEqual(obj, obj1) + finally: + os.remove(filename3) + def test_format_from_ext(self): - if ':' in self.FMT: + if ":" in self.FMT: return fh = FORMATS[self.FMT] self.assertEqual(_get_format_from_ext(fh.extension), self.FMT) def test_response_bytes(self): - obj = 'here I am' + obj = "here I am" self.assertIsInstance(dumps(obj, self.FMT), bytes) def test_simple_types(self): - self._test_round_trip('hello') + self._test_round_trip("hello") self._test_round_trip(1) self._test_round_trip(1.1) self._test_round_trip(None) @@ -166,26 +168,21 @@ def test_custom_object_in_container(self): class _TestUnavailable: - def test_raise(self): - self.assertRaises(ValueError, dumps, 'hello', self.FMT) + self.assertRaises(ValueError, dumps, "hello", self.FMT) def test_raise_from_ext(self): - self.assertRaises(ValueError, dumps, 'hello', self.FMT) + self.assertRaises(ValueError, dumps, "hello", self.FMT) for key in FORMATS.keys(): - if key.startswith('_test'): + if key.startswith("_test"): continue - name = "TestEncoderDecoder_%s" % key.replace(':', '_') - globals()[name] = type(name, - (_TestEncoderDecoder, TestCase), - dict(FMT=key)) + name = "TestEncoderDecoder_%s" % key.replace(":", "_") + globals()[name] = type(name, (_TestEncoderDecoder, TestCase), dict(FMT=key)) for key in UNAVAILABLE_FORMATS.keys(): - if key.startswith('_test'): + if key.startswith("_test"): continue name = "TestEncoderDecoder_%s" % key - globals()[name] = type(name, - (_TestUnavailable, TestCase), - dict(FMT=key)) + globals()[name] = type(name, (_TestUnavailable, TestCase), dict(FMT=key)) diff --git a/serialize/yaml.py b/serialize/yaml.py index 1cba581..cce8c63 100644 --- a/serialize/yaml.py +++ b/serialize/yaml.py @@ -17,18 +17,16 @@ import yaml from yaml.constructor import MappingNode except ImportError: - all.register_unavailable('yaml', pkg='pyyaml') + all.register_unavailable("yaml", pkg="pyyaml") raise class Dumper(yaml.Dumper): - def represent_data(self, data): return super().represent_data(all.encode(data)) class Loader(yaml.Loader): - def construct_object(self, node, deep=False): # It seems that pyyaml is changing the internal structure of the node @@ -43,14 +41,12 @@ def construct_object(self, node, deep=False): return tmp - def dumps(obj): - return yaml.dump(obj, Dumper=Dumper).encode('utf-8') + return yaml.dump(obj, Dumper=Dumper).encode("utf-8") def loads(content): - return yaml.load(content.decode('utf-8'), - Loader=Loader) + return yaml.load(content.decode("utf-8"), Loader=Loader) -all.register_format('yaml', dumps, loads) +all.register_format("yaml", dumps, loads) diff --git a/setup.cfg b/setup.cfg new file mode 100644 index 0000000..da78ff5 --- /dev/null +++ b/setup.cfg @@ -0,0 +1,45 @@ +[metadata] +name = Serialize +description = "A common API for multiple serialization formats with support for custom classes." +url = "https://github.com/hgrecco/serialize" +author = "Hernan E. Grecco" +author_email = "hernan.grecco@gmail.com" +license = "BSD" +long_description = file: README.rst +keywords = serialization, deserialization, packing, unpacking +classifiers = + Development Status :: 4 - Beta + Intended Audience :: Developers + Intended Audience :: End Users/Desktop + License :: OSI Approved :: BSD License + Operating System :: MacOS :: MacOS X + Operating System :: Microsoft :: Windows + Operating System :: POSIX + Programming Language :: Python + Topic :: Software Development :: Libraries + Programming Language :: Python :: 3.5 + Programming Language :: Python :: 3.6 + Programming Language :: Python :: 3.7 + Programming Language :: Python :: 3.8 + +[options] +packages = serialize +include_package_data = True + +[options.extras_require] +all = + bson + dill + msgpack-python + phpserialize + serpent + simplejson + pyyaml +test = pytest +dev = pre-commit; pytest + +[bdist_wheel] +universal = 1 + +[flake8] +ignore = E501 # line too long \ No newline at end of file diff --git a/setup.py b/setup.py index 6333b4b..7f1a176 100644 --- a/setup.py +++ b/setup.py @@ -1,53 +1,4 @@ -#!/usr/bin/env python -# -*- coding: utf-8 -*- +from setuptools import setup -import sys - -try: - from setuptools import setup -except ImportError: - print('Please install or upgrade setuptools or pip to continue') - sys.exit(1) - -import codecs - - -def read(filename): - return codecs.open(filename, encoding='utf-8').read() - - -long_description = '\n\n'.join([read('README.rst'), - read('AUTHORS'), - read('CHANGES')]) - -__doc__ = long_description - -setup( - name='Serialize', - version='0.2.dev0', - description='A common API for multiple serialization formats with support for custom classes', - long_description=long_description, - keywords='serialization deserialization packing unpacking ', - author='Hernan E. Grecco', - author_email='hernan.grecco@gmail.com', - url='https://github.com/hgrecco/serialize', - test_suite='serialize.testsuite.testsuite', - zip_safe=True, - packages=['serialize'], - license='BSD', - classifiers=[ - 'Development Status :: 4 - Beta', - 'Intended Audience :: Developers', - 'Intended Audience :: End Users/Desktop', - 'License :: OSI Approved :: BSD License', - 'Operating System :: MacOS :: MacOS X', - 'Operating System :: Microsoft :: Windows', - 'Operating System :: POSIX', - 'Programming Language :: Python', - 'Topic :: Software Development :: Libraries', - 'Programming Language :: Python :: 3.3', - 'Programming Language :: Python :: 3.4', - 'Programming Language :: Python :: 3.5', - 'Programming Language :: Python :: 3.6', - 'Programming Language :: Python :: 3.7', - ]) +if __name__ == "__main__": + setup() diff --git a/supported_pkgs.txt b/supported_pkgs.txt deleted file mode 100644 index 7a5e4e8..0000000 --- a/supported_pkgs.txt +++ /dev/null @@ -1,7 +0,0 @@ -bson -dill -msgpack-python -phpserialize -serpent -simplejson -pyyaml