Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Integration of orjson for (much) faster JSON encoding and decoding #358

Merged
merged 35 commits into from Mar 12, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
35 commits
Select commit Hold shift + click to select a range
15e545a
Integrate orjson into MontyDecoder
munrojm Feb 24, 2022
2490305
Subclass JSONDecoder
munrojm Feb 24, 2022
f5fe548
Change serialization funcs
munrojm Mar 8, 2022
7fb11e6
Merge branch 'orjson_integration' of https://github.com/munrojm/monty…
munrojm Mar 8, 2022
e790ac1
Revert serialization change
munrojm Mar 8, 2022
5b45b6e
Initial commit of new encode func
munrojm Mar 8, 2022
ae76728
Add new encode func
munrojm Mar 8, 2022
19a3ce5
Fix serializing bound methods
munrojm Mar 8, 2022
f428193
Linting
munrojm Mar 8, 2022
58627ed
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 8, 2022
255a310
Update reqs
munrojm Mar 8, 2022
6efbe65
Merge branch 'orjson_integration' of https://github.com/munrojm/monty…
munrojm Mar 8, 2022
87001e1
Merge branch 'master' into orjson_integration
munrojm Mar 8, 2022
23ab17e
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 8, 2022
1e1e5b2
Linting
munrojm Mar 8, 2022
cf17994
Merge branch 'orjson_integration' of https://github.com/munrojm/monty…
munrojm Mar 8, 2022
2ebae05
More linting
munrojm Mar 8, 2022
4423912
Black + fix isort profile
munrojm Mar 8, 2022
bf5ddaf
Fix docstring
munrojm Mar 8, 2022
372fe97
Fix pylint
munrojm Mar 8, 2022
6678b16
Default to regular json if orjson not present
munrojm Mar 8, 2022
5608cce
Remove duplicate import
munrojm Mar 8, 2022
5bfa829
Fix recursive encoding
munrojm Mar 9, 2022
15dc92a
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 9, 2022
8548e1f
Remove copy retun in process
munrojm Mar 9, 2022
7349850
Merge branch 'orjson_integration' of https://github.com/munrojm/monty…
munrojm Mar 9, 2022
5a5262d
Remove copy import
munrojm Mar 9, 2022
106ffc2
Increase nesting on some tests
munrojm Mar 9, 2022
c88e552
Revert MontyEncoder changes
munrojm Mar 10, 2022
c39f740
Merge branch 'master' into orjson_integration
munrojm Mar 11, 2022
c796033
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 11, 2022
b5438e1
Fix revert and pre-commit
munrojm Mar 11, 2022
131e2c8
[pre-commit.ci] auto fixes from pre-commit.com hooks
pre-commit-ci[bot] Mar 11, 2022
a01a6aa
Include imports with pylint
munrojm Mar 11, 2022
4d37690
Merge branch 'orjson_integration' of https://github.com/munrojm/monty…
munrojm Mar 11, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions .pre-commit-config.yaml
Expand Up @@ -38,6 +38,7 @@ repos:
rev: 5.10.1
hooks:
- id: isort
args: ["--profile", "black"]

- repo: https://github.com/psf/black
rev: 22.1.0
Expand Down
15 changes: 8 additions & 7 deletions docs_rst/_themes/flask_theme_support.py
@@ -1,7 +1,8 @@
# flasky extensions. flasky pygments style based on tango style
from pygments.style import Style
from pygments.token import Keyword, Name, Comment, String, Error, \
Number, Operator, Generic, Whitespace, Punctuation, Other, Literal
from pygments.token import (Comment, Error, Generic, Keyword, Literal, Name,
Number, Operator, Other, Punctuation, String,
Whitespace)


class FlaskyStyle(Style):
Expand All @@ -10,12 +11,12 @@ class FlaskyStyle(Style):

styles = {
# No corresponding class for the following:
#Text: "", # class: ''
# Text: "", # class: ''
Whitespace: "underline #f8f8f8", # class: 'w'
Error: "#a40000 border:#ef2929", # class: 'err'
Error: "#a40000 border:#ef2929", # class: 'err'
Other: "#000000", # class 'x'

Comment: "italic #8f5902", # class: 'c'
Comment: "italic #8f5902", # class: 'c'
Comment.Preproc: "noitalic", # class: 'cp'

Keyword: "bold #004461", # class: 'k'
Expand Down Expand Up @@ -62,7 +63,7 @@ class FlaskyStyle(Style):
String: "#4e9a06", # class: 's'
String.Backtick: "#4e9a06", # class: 'sb'
String.Char: "#4e9a06", # class: 'sc'
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Doc: "italic #8f5902", # class: 'sd' - like a comment
String.Double: "#4e9a06", # class: 's2'
String.Escape: "#4e9a06", # class: 'se'
String.Heredoc: "#4e9a06", # class: 'sh'
Expand All @@ -74,7 +75,7 @@ class FlaskyStyle(Style):

Generic: "#000000", # class: 'g'
Generic.Deleted: "#a40000", # class: 'gd'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Emph: "italic #000000", # class: 'ge'
Generic.Error: "#ef2929", # class: 'gr'
Generic.Heading: "bold #000080", # class: 'gh'
Generic.Inserted: "#00A000", # class: 'gi'
Expand Down
6 changes: 3 additions & 3 deletions docs_rst/conf.py
Expand Up @@ -11,8 +11,8 @@
# All configuration values have a default; values that are commented out
# serve to show the default.

import sys
import os
import sys

# If extensions (or modules to document with autodoc) are in another directory,
# add these directories to sys.path here. If the directory is relative to the
Expand All @@ -22,7 +22,7 @@
sys.path.insert(0, os.path.dirname('../monty'))
sys.path.insert(0, os.path.dirname('../..'))

from monty import __version__, __author__
from monty import __author__, __version__

# -- General configuration -----------------------------------------------------

Expand Down Expand Up @@ -181,7 +181,7 @@

# -- Options for LaTeX output ------------------------------------------------

latex_elements = {
latex_elements = { # type: ignore
# The paper size ('letterpaper' or 'a4paper').
#'papersize': 'letterpaper',

Expand Down
1 change: 0 additions & 1 deletion monty/design_patterns.py
Expand Up @@ -116,4 +116,3 @@ def write(*args): # pylint: disable=E0211
Does nothing...
:param args:
"""
pass
14 changes: 9 additions & 5 deletions monty/json.py
Expand Up @@ -38,6 +38,11 @@
except ImportError:
YAML = None # type: ignore

try:
import orjson
except ImportError:
orjson = None # type: ignore

__version__ = "3.0.0"


Expand Down Expand Up @@ -248,9 +253,7 @@ class MontyEncoder(json.JSONEncoder):
"""
A Json Encoder which supports the MSONable API, plus adds support for
numpy arrays, datetime objects, bson ObjectIds (requires bson).

Usage::

# Add it as a *cls* keyword when using json.dump
json.dumps(object, cls=MontyEncoder)
"""
Expand All @@ -262,10 +265,8 @@ def default(self, o) -> dict: # pylint: disable=E0202
output. (b) If the @module and @class keys are not in the to_dict,
add them to the output automatically. If the object has no to_dict
property, the default Python json encoder default method is called.

Args:
o: Python object.

Return:
Python dict representation.
"""
Expand Down Expand Up @@ -443,7 +444,10 @@ def decode(self, s):
:param s: string
:return: Object.
"""
d = json.JSONDecoder.decode(self, s)
if orjson is not None:
d = orjson.loads(s) # pylint: disable=E1101
else:
d = json.loads(s)
return self.process_decoded(d)


Expand Down
2 changes: 1 addition & 1 deletion pylintrc
Expand Up @@ -368,7 +368,7 @@ ignore-comments=yes
ignore-docstrings=yes

# Ignore imports when computing similarities.
ignore-imports=yes
ignore-imports=no

# Minimum lines number of a similarity.
min-similarity-lines=4
Expand Down
5 changes: 4 additions & 1 deletion requirements-ci.txt
Expand Up @@ -14,4 +14,7 @@ pydantic==1.9.0
flake8==4.0.1
pandas==1.4.1
black==22.1.0
pylint==2.12.2
pylint==2.12.2
orjson==3.6.7
types-orjson==3.6.2
types-requests==2.27.11
4 changes: 2 additions & 2 deletions setup.py
@@ -1,6 +1,6 @@
import os
from setuptools import setup, find_packages
import io

from setuptools import find_packages, setup

current_dir = os.path.dirname(os.path.abspath(__file__))

Expand Down
8 changes: 4 additions & 4 deletions tasks.py
Expand Up @@ -5,17 +5,17 @@
"""


import datetime
import glob
import requests
import json
import os
import datetime
import re

import requests
from invoke import task
from monty.os import cd
from monty import __version__ as ver

from monty import __version__ as ver
from monty.os import cd

__author__ = "Shyue Ping Ong"
__copyright__ = "Copyright 2012, The Materials Project"
Expand Down
2 changes: 1 addition & 1 deletion tests/test_bisect.py
@@ -1,6 +1,6 @@
import unittest

from monty.bisect import index, find_lt, find_le, find_gt, find_ge
from monty.bisect import find_ge, find_gt, find_le, find_lt, index


class FuncTestCase(unittest.TestCase):
Expand Down
4 changes: 2 additions & 2 deletions tests/test_collections.py
@@ -1,7 +1,7 @@
import unittest
import os
import unittest

from monty.collections import frozendict, Namespace, AttrDict, FrozenAttrDict, tree
from monty.collections import AttrDict, FrozenAttrDict, Namespace, frozendict, tree

test_dir = os.path.join(os.path.dirname(__file__), "test_files")

Expand Down
3 changes: 1 addition & 2 deletions tests/test_design_patterns.py
@@ -1,7 +1,6 @@
import unittest
import pickle

from monty.design_patterns import singleton, cached_class
from monty.design_patterns import cached_class, singleton


class SingletonTest(unittest.TestCase):
Expand Down
5 changes: 3 additions & 2 deletions tests/test_dev.py
@@ -1,7 +1,8 @@
import multiprocessing
import unittest
import warnings
import multiprocessing
from monty.dev import deprecated, requires, get_ncpus, install_excepthook

from monty.dev import deprecated, get_ncpus, install_excepthook, requires


class A:
Expand Down
2 changes: 1 addition & 1 deletion tests/test_fractions.py
@@ -1,6 +1,6 @@
import unittest

from monty.fractions import gcd, lcm, gcd_float
from monty.fractions import gcd, gcd_float, lcm


class FuncTestCase(unittest.TestCase):
Expand Down
11 changes: 6 additions & 5 deletions tests/test_functools.py
@@ -1,15 +1,16 @@
import unittest
import sys
import platform
import sys
import time
import unittest

from monty.functools import (
lru_cache,
TimeoutError,
lazy_property,
lru_cache,
prof_main,
return_if_raise,
return_none_if_raise,
timeout,
TimeoutError,
prof_main,
)


Expand Down
2 changes: 1 addition & 1 deletion tests/test_inspect.py
@@ -1,6 +1,6 @@
import unittest

from monty.inspect import *
from monty.inspect import all_subclasses, caller_name, find_top_pyfile


class LittleCatA:
Expand Down
6 changes: 3 additions & 3 deletions tests/test_io.py
@@ -1,17 +1,17 @@
import unittest
import os
import unittest

try:
from pathlib import Path
except ImportError:
Path = None # type: ignore

from monty.io import (
reverse_readline,
zopen,
FileLock,
FileLockException,
reverse_readfile,
reverse_readline,
zopen,
)

test_dir = os.path.join(os.path.dirname(__file__), "test_files")
Expand Down
48 changes: 41 additions & 7 deletions tests/test_json.py
@@ -1,17 +1,18 @@
__version__ = "0.1"

import datetime
import json
import os
import unittest
from enum import Enum

import numpy as np
import json
import datetime
import pandas as pd
from bson.objectid import ObjectId
from enum import Enum

from monty.json import MontyDecoder, MontyEncoder, MSONable, _load_redirect, jsanitize

from . import __version__ as tests_version
from monty.json import MSONable, MontyEncoder, MontyDecoder, jsanitize
from monty.json import _load_redirect

test_dir = os.path.join(os.path.dirname(os.path.abspath(__file__)), "test_files")

Expand Down Expand Up @@ -101,6 +102,11 @@ def __init__(self, s):
self.s = s


class ClassContainingNumpyArray(MSONable):
def __init__(self, np_a):
self.np_a = np_a


class MSONableTest(unittest.TestCase):
def setUp(self):
self.good_cls = GoodMSONClass
Expand Down Expand Up @@ -146,7 +152,7 @@ def test_to_from_dict(self):
self.assertRaises(NotImplementedError, obj.as_dict)
obj = self.auto_mson(2, 3)
d = obj.as_dict()
objd = self.auto_mson.from_dict(d)
self.auto_mson.from_dict(d)

def test_unsafe_hash(self):
GMC = GoodMSONClass
Expand Down Expand Up @@ -288,7 +294,7 @@ def test_datetime(self):
self.assertEqual(type(d["dt"]), datetime.datetime)

def test_uuid(self):
from uuid import uuid4, UUID
from uuid import UUID, uuid4

uuid = uuid4()
jsonstr = json.dumps(uuid, cls=MontyEncoder)
Expand Down Expand Up @@ -343,6 +349,22 @@ def test_numpy(self):
d = jsanitize(x, strict=True)
assert type(d["energies"][0]) == float

# Test data nested in a class
x = np.array([[1 + 1j, 2 + 1j], [3 + 1j, 4 + 1j]], dtype="complex64")
cls = ClassContainingNumpyArray(np_a={"a": [{"b": x}]})

d = json.loads(json.dumps(cls, cls=MontyEncoder))

self.assertEqual(d["np_a"]["a"][0]["b"]["@module"], "numpy")
self.assertEqual(d["np_a"]["a"][0]["b"]["@class"], "array")
self.assertEqual(d["np_a"]["a"][0]["b"]["data"], [[[1.0, 2.0], [3.0, 4.0]], [[1.0, 1.0], [1.0, 1.0]]])
self.assertEqual(d["np_a"]["a"][0]["b"]["dtype"], "complex64")

obj = ClassContainingNumpyArray.from_dict(d)
self.assertIsInstance(obj, ClassContainingNumpyArray)
self.assertIsInstance(obj.np_a["a"][0]["b"], np.ndarray)
self.assertEqual(obj.np_a["a"][0]["b"][0][1], 2 + 1j)

def test_pandas(self):

cls = ClassContainingDataFrame(df=pd.DataFrame([{"a": 1, "b": 1}, {"a": 1, "b": 2}]))
Expand All @@ -369,6 +391,18 @@ def test_pandas(self):
self.assertIsInstance(obj.s, pd.Series)
self.assertEqual(list(obj.s.a), [1, 2, 3])

cls = ClassContainingSeries(s={"df": [pd.Series({"a": [1, 2, 3], "b": [4, 5, 6]})]})

d = json.loads(MontyEncoder().encode(cls))

self.assertEqual(d["s"]["df"][0]["@module"], "pandas")
self.assertEqual(d["s"]["df"][0]["@class"], "Series")

obj = ClassContainingSeries.from_dict(d)
self.assertIsInstance(obj, ClassContainingSeries)
self.assertIsInstance(obj.s["df"][0], pd.Series)
self.assertEqual(list(obj.s["df"][0].a), [1, 2, 3])

def test_callable(self):
instance = MethodSerializationClass(a=1)
for function in [
Expand Down
2 changes: 1 addition & 1 deletion tests/test_logging.py
@@ -1,7 +1,7 @@
import logging
import unittest
from io import StringIO

import logging
from monty.logging import logged


Expand Down
2 changes: 1 addition & 1 deletion tests/test_multiprocessing.py
@@ -1,7 +1,7 @@
import unittest
from math import sqrt

from monty.multiprocessing import imap_tqdm
from math import sqrt


class FuncCase(unittest.TestCase):
Expand Down
1 change: 1 addition & 0 deletions tests/test_operator.py
@@ -1,4 +1,5 @@
import unittest

from monty.operator import operator_from_str


Expand Down