1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196
|
import logging
from dataclasses import dataclass, asdict
from timeit import timeit
from typing import Optional, TypeVar
import dataclass_factory
import pytest
from dataclasses_json import DataClassJsonMixin
from jsons import JsonSerializable
from dacite import from_dict as dacite_from_dict
from pydantic import BaseModel
import marshmallow
import attr
import mashumaro
from dataclass_wizard import JSONWizard, LoadMeta
from dataclass_wizard.class_helper import create_new_class
from dataclass_wizard.constants import PY314_OR_ABOVE
from dataclass_wizard.utils.string_conv import to_snake_case
log = logging.getLogger(__name__)
# Dataclass for the test
@dataclass
class MyClass:
my_str: str
my_int: int
my_bool: Optional[bool]
# Add Pydantic Model
class MyClassPydantic(BaseModel):
my_str: str
my_int: int
my_bool: Optional[bool]
# Marshmallow Schema
class MyClassSchema(marshmallow.Schema):
my_str = marshmallow.fields.Str()
my_int = marshmallow.fields.Int()
my_bool = marshmallow.fields.Bool()
# attrs Class
@attr.s
class MyClassAttrs:
my_str = attr.ib(type=str)
my_int = attr.ib(type=int)
my_bool = attr.ib(type=Optional[bool])
# Mashumaro Model
@dataclass
class MyClassMashumaro(mashumaro.DataClassDictMixin):
my_str: str
my_int: int
my_bool: Optional[bool]
# Model for `dataclass-wizard`
WizType = TypeVar("WizType", MyClass, JSONWizard)
# Model for `jsons`
JsonsType = TypeVar("JsonsType", MyClass, JsonSerializable)
# Model for `dataclasses-json`
DJType = TypeVar("DJType", MyClass, DataClassJsonMixin)
# Factory for `dataclass-factory`
factory = dataclass_factory.Factory()
MyClassWizard: WizType = create_new_class(MyClass, (MyClass, JSONWizard), "Wizard")
MyClassDJ: DJType = create_new_class(MyClass, (MyClass, DataClassJsonMixin), "DJ")
MyClassJsons: JsonsType = create_new_class(MyClass, (MyClass, JsonSerializable), "Jsons")
# Enable experimental `v1` mode for optimized de/serialization
LoadMeta(v1=True).bind_to(MyClassWizard)
@pytest.fixture(scope="session")
def data():
return {
"my_str": "hello world!",
"my_int": 21,
"my_bool": True,
}
def test_load(data, n):
"""
[ RESULTS]
platform darwin -- Python 3.13.11, pytest-8.3.4, pluggy-1.6.0
benchmarks.simple.simple - [INFO] dataclass-wizard 0.029298
benchmarks.simple.simple - [INFO] dataclass-factory 0.100123
benchmarks.simple.simple - [INFO] dataclasses-json 3.530623
benchmarks.simple.simple - [INFO] jsons 4.920980
benchmarks.simple.simple - [INFO] dacite 0.612328
benchmarks.simple.simple - [INFO] pydantic 0.063677
benchmarks.simple.simple - [INFO] marshmallow 2.197097
benchmarks.simple.simple - [INFO] attrs 0.020192
benchmarks.simple.simple - [INFO] mashumaro 0.040619
"""
g = globals().copy()
g.update(locals())
# Add dacite and pydantic benchmarks
log.info("dataclass-wizard %f",
timeit("MyClassWizard.from_dict(data)", globals=g, number=n))
if not PY314_OR_ABOVE: # breaks on Python 3.14+
log.info("dataclass-factory %f",
timeit("factory.load(data, MyClass)", globals=g, number=n))
log.info("dataclasses-json %f",
timeit("MyClassDJ.from_dict(data)", globals=g, number=n))
log.info("jsons %f",
timeit("MyClassJsons.load(data)", globals=g, number=n))
log.info("dacite %f",
timeit("dacite_from_dict(MyClass, data)", globals=g, number=n))
log.info("pydantic %f",
timeit("MyClassPydantic(**data)", globals=g, number=n))
log.info("marshmallow %f",
timeit("MyClassSchema().load(data)", globals=g, number=n))
log.info("attrs %f",
timeit("MyClassAttrs(**data)", globals=g, number=n))
log.info("mashumaro %f",
timeit("MyClassMashumaro.from_dict(data)", globals=g, number=n))
# Assert the dataclass instances have the same values for all fields.
c1 = MyClassWizard.from_dict(data)
if not PY314_OR_ABOVE: # breaks on Python 3.14+
c2 = factory.load(data, MyClass)
c3 = MyClassDJ.from_dict(data)
c4 = MyClassJsons.load(data)
c5 = dacite_from_dict(MyClass, data)
c6 = MyClassPydantic(**data)
c7 = MyClassSchema().load(data)
c8 = MyClassAttrs(**data)
c9 = MyClassMashumaro.from_dict(data)
assert c1.__dict__ == c3.__dict__ == c4.__dict__ == c5.__dict__ == c6.model_dump() == c7 == c8.__dict__ == c9.to_dict()
if not PY314_OR_ABOVE: # breaks on Python 3.14+
assert c1.__dict__ == c2.__dict__
def test_dump(data, n):
"""
[ RESULTS]
platform darwin -- Python 3.13.11, pytest-8.3.4, pluggy-1.6.0
benchmarks.simple.simple - [INFO] dataclass-wizard 0.010870
benchmarks.simple.simple - [INFO] asdict (dataclasses) 0.085224
benchmarks.simple.simple - [INFO] dataclass-factory 0.070084
benchmarks.simple.simple - [INFO] dataclasses-json 1.272380
benchmarks.simple.simple - [INFO] jsons 5.980036
benchmarks.simple.simple - [INFO] dacite (not applicable) -- skipped
benchmarks.simple.simple - [INFO] pydantic 0.079050
benchmarks.simple.simple - [INFO] marshmallow 0.000489
benchmarks.simple.simple - [INFO] attrs 0.054118
benchmarks.simple.simple - [INFO] mashumaro 0.008982
"""
c1 = MyClassWizard.from_dict(data)
if not PY314_OR_ABOVE: # breaks on Python 3.14+
c2 = factory.load(data, MyClass)
c3 = MyClassDJ.from_dict(data)
c4 = MyClassJsons.load(data)
c5 = dacite_from_dict(MyClass, data)
c6 = MyClassPydantic(**data)
c7 = MyClassSchema().load(data)
c8 = MyClassAttrs(**data)
c9 = MyClassMashumaro.from_dict(data)
g = globals().copy()
g.update(locals())
log.info("dataclass-wizard %f",
timeit("c1.to_dict()", globals=g, number=n))
log.info("asdict (dataclasses) %f",
timeit("asdict(c1)", globals=g, number=n))
if not PY314_OR_ABOVE: # breaks on Python 3.14+
log.info("dataclass-factory %f",
timeit("factory.dump(c2, MyClass)", globals=g, number=n))
log.info("dataclasses-json %f",
timeit("c3.to_dict()", globals=g, number=n))
log.info("jsons %f",
timeit("c4.dump()", globals=g, number=n))
log.info("dacite (not applicable) -- skipped")
log.info("pydantic %f",
timeit("c6.model_dump()", globals=g, number=n))
log.info("marshmallow %f",
timeit("c7", globals=g, number=n))
log.info("attrs %f",
timeit("attr.asdict(c8)", globals=g, number=n))
log.info("mashumaro %f",
timeit("c9.to_dict()", globals=g, number=n))
# Assert the dict objects which are the result of `to_dict` are all equal.
c1_dict = {to_snake_case(f): fval for f, fval in c1.to_dict().items()}
assert c1_dict == c3.to_dict() == c4.dump() == c6.model_dump() == attr.asdict(c8) == c9.to_dict()
if not PY314_OR_ABOVE: # breaks on Python 3.14+
assert c1_dict == factory.dump(c2, MyClass)
|