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

Milestone to v1 #164

Merged
merged 51 commits into from
Dec 17, 2024
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
a69d542
apply `@dataclass` decorator
rnag Dec 1, 2024
05c860a
add `pprint` for `__str__()`
rnag Dec 1, 2024
19771ff
Lotta Changes to `Load`, it's Supa Fast !!!
rnag Dec 2, 2024
054e71a
Fixing a bug
rnag Dec 2, 2024
0506731
move to `v1`
rnag Dec 2, 2024
1eaa45c
ive got deser for defaultdict, enum, uuid, decimal, and path working!
rnag Dec 2, 2024
c75ec73
minor changes
rnag Dec 2, 2024
7fef942
add support for time, date, datetime, and timedelta
rnag Dec 2, 2024
dad21ec
add support for namedtuple, NamedTuple
rnag Dec 4, 2024
5a6ae32
add support for TypedDict
rnag Dec 4, 2024
deb839e
add support for ReadOnly, Literal, Annotated, LiteralString
rnag Dec 4, 2024
862e5c0
fix for nested dataclasses
rnag Dec 6, 2024
68b3312
minor refactor and changes
rnag Dec 7, 2024
e2b8e88
feat: deserialize `Union` types
rnag Dec 7, 2024
4a5c281
feat: add `v1` and `v1_unsafe_parse_dataclass_in_union`
rnag Dec 8, 2024
a873b2b
feat: add `v1` and `v1_unsafe_parse_dataclass_in_union`
rnag Dec 8, 2024
7a27227
fix tests and disable auto `@dataclass` application
rnag Dec 8, 2024
813eba8
i think that fix it
rnag Dec 8, 2024
2eade44
i think that fix it
rnag Dec 8, 2024
989416b
Moar Updates
rnag Dec 8, 2024
accb219
minor changes and tests need be added
rnag Dec 8, 2024
56685f3
optimize missing fields
rnag Dec 9, 2024
bfff459
add support for key casing
rnag Dec 9, 2024
f970288
add support for aliases
rnag Dec 10, 2024
3a91837
partially add `raise_on_unknown_key` support
rnag Dec 10, 2024
2c6868d
fully add `raise_on_unknown_key` support
rnag Dec 11, 2024
dde2d83
add support for `CatchAll`
rnag Dec 11, 2024
9a7725a
fully support `CatchAll`
rnag Dec 11, 2024
1b5f8fe
add more test cases for coverage
rnag Dec 12, 2024
6477ad3
add benchmarks for catch all
rnag Dec 12, 2024
dba8877
update to use `pytest-benchmark`
rnag Dec 13, 2024
aaf2077
checkin changes so far
rnag Dec 17, 2024
492f84c
fix logic so its working
rnag Dec 17, 2024
9044407
completely fix `AliasPath`
rnag Dec 17, 2024
3f3287a
fix tests on CI
rnag Dec 17, 2024
4a0adc6
fix tests on CI
rnag Dec 17, 2024
b0f6bb8
fix tests on CI
rnag Dec 17, 2024
f7f3ef8
Merge branch 'main' into milestone-to-V1
rnag Dec 17, 2024
543f51a
try fix tests on CI
rnag Dec 17, 2024
2a901eb
try fix tests on CI
rnag Dec 17, 2024
5649da1
thats not the issue, ima have to try debug it locally
rnag Dec 17, 2024
03d3936
Im stupid, we need to use `is_subclass_safe` after all :o
rnag Dec 17, 2024
34dea81
I tire of this
rnag Dec 17, 2024
bd52f88
Update badges and docs
rnag Dec 17, 2024
48c6ade
Update docs to mention v1` opt-in
rnag Dec 17, 2024
9857586
update description
rnag Dec 17, 2024
b6671b9
one last change, think its good to go!
rnag Dec 17, 2024
9147fe0
add section on perf in v1
rnag Dec 17, 2024
58a1ad3
clean up comments
rnag Dec 17, 2024
ce60349
Update HISTORY.rst
rnag Dec 17, 2024
b75f9e5
Update HISTORY.rst and add Dark Mode for Docs!
rnag Dec 17, 2024
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
22 changes: 13 additions & 9 deletions benchmarks/complex.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@
import attr
import mashumaro

from dataclass_wizard import JSONWizard
from dataclass_wizard import JSONWizard, LoadMeta
from dataclass_wizard.class_helper import create_new_class
from dataclass_wizard.utils.string_conv import to_snake_case
from dataclass_wizard.utils.type_conv import as_datetime
Expand Down Expand Up @@ -135,6 +135,10 @@ class PersonDJ:
attr_dict=vars(MyClass).copy())


# Enable experimental `v1` mode for optimized de/serialization
LoadMeta(v1=True).bind_to(MyClassWizard)


@pytest.fixture(scope='session')
def data():
return {
Expand Down Expand Up @@ -214,14 +218,14 @@ def test_load(request, data, data_2, data_dacite, n):
"""
[ RESULTS ON MAC OS X ]

benchmarks.complex.complex - [INFO] dataclass-wizard 0.800521
benchmarks.complex.complex - [INFO] dataclass-factory 0.827150
benchmarks.complex.complex - [INFO] dataclasses-json 37.087781
benchmarks.complex.complex - [INFO] dacite 9.421210
benchmarks.complex.complex - [INFO] mashumaro 0.608496
benchmarks.complex.complex - [INFO] pydantic 1.039472
benchmarks.complex.complex - [INFO] jsons 39.677698
benchmarks.complex.complex - [INFO] jsons (strict) 41.592585
benchmarks.complex.complex - [INFO] dataclass-wizard 0.373847
benchmarks.complex.complex - [INFO] dataclass-factory 0.777164
benchmarks.complex.complex - [INFO] dataclasses-json 28.177022
benchmarks.complex.complex - [INFO] dacite 6.619898
benchmarks.complex.complex - [INFO] mashumaro 0.351623
benchmarks.complex.complex - [INFO] pydantic 0.563395
benchmarks.complex.complex - [INFO] jsons 30.564242
benchmarks.complex.complex - [INFO] jsons (strict) 35.122489
"""
g = globals().copy()
g.update(locals())
Expand Down
29 changes: 20 additions & 9 deletions benchmarks/nested.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
from pydantic import BaseModel
import mashumaro

from dataclass_wizard import JSONWizard
from dataclass_wizard import JSONWizard, LoadMeta
from dataclass_wizard.class_helper import create_new_class
from dataclass_wizard.utils.string_conv import to_snake_case
from dataclass_wizard.utils.type_conv import as_datetime, as_date
Expand Down Expand Up @@ -141,6 +141,8 @@ class Data2DJ:
JsonsType = TypeVar('JsonsType', Data1, JsonSerializable)
# Model for `dataclasses-json`
DJType = TypeVar('DJType', Data1, DataClassJsonMixin)
# Model for `mashumaro`
MashumaroType = TypeVar('MashumaroType', Data1, mashumaro.DataClassDictMixin)
# Factory for `dataclass-factory`
factory = dataclass_factory.Factory()

Expand All @@ -150,12 +152,19 @@ class Data2DJ:
MyClassJsons: JsonsType = create_new_class(
Data1, (Data1, JsonSerializable), 'Jsons',
attr_dict=vars(Data1).copy())
MyClassMashumaroModel: MashumaroType = create_new_class(
Data1, (Data1, mashumaro.DataClassDictMixin), 'Mashumaro',
attr_dict=vars(Data1).copy())

# Pydantic Model for Benchmarking
MyClassPydanticModel = MyClassPydantic

# Mashumaro Model for Benchmarking
MyClassMashumaroModel = MyClassMashumaro
# MyClassMashumaroModel = MyClassMashumaro


# Enable experimental `v1` mode for optimized de/serialization
LoadMeta(v1=True).bind_to(MyClassWizard)


@pytest.fixture(scope='session')
Expand Down Expand Up @@ -205,18 +214,20 @@ def test_load(request, data, n):
"""
[ RESULTS ON MAC OS X ]

benchmarks.nested.nested - [INFO] dataclass-wizard 0.397123
benchmarks.nested.nested - [INFO] dataclass-factory 0.418530
benchmarks.nested.nested - [INFO] dataclasses-json 11.443072
benchmarks.nested.nested - [INFO] mashumaro 0.158189
benchmarks.nested.nested - [INFO] pydantic 0.346031
benchmarks.nested.nested - [INFO] jsons 28.124958
benchmarks.nested.nested - [INFO] jsons (strict) 28.816675
benchmarks.nested.nested - [INFO] dataclass-wizard 0.135700
benchmarks.nested.nested - [INFO] dataclass-factory 0.412265
benchmarks.nested.nested - [INFO] dataclasses-json 11.448704
benchmarks.nested.nested - [INFO] mashumaro 0.150680
benchmarks.nested.nested - [INFO] pydantic 0.328947
benchmarks.nested.nested - [INFO] jsons 25.052287
benchmarks.nested.nested - [INFO] jsons (strict) 43.233567

"""
g = globals().copy()
g.update(locals())

MyClassWizard.from_dict(data)

log.info('dataclass-wizard %f',
timeit('MyClassWizard.from_dict(data)', globals=g, number=n))

Expand Down
8 changes: 6 additions & 2 deletions benchmarks/simple.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@
import attr
import mashumaro

from dataclass_wizard import JSONWizard
from dataclass_wizard import JSONWizard, LoadMeta
from dataclass_wizard.class_helper import create_new_class
from dataclass_wizard.utils.string_conv import to_snake_case

Expand Down Expand Up @@ -65,6 +65,10 @@ class MyClassMashumaro(mashumaro.DataClassDictMixin):
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 {
Expand All @@ -77,7 +81,7 @@ def test_load(data, n):
"""
[ RESULTS ON MAC OS X ]

benchmarks.simple.simple - [INFO] dataclass-wizard 0.076336
benchmarks.simple.simple - [INFO] dataclass-wizard 0.033917
benchmarks.simple.simple - [INFO] dataclass-factory 0.103837
benchmarks.simple.simple - [INFO] dataclasses-json 3.941902
benchmarks.simple.simple - [INFO] jsons 5.636863
Expand Down
7 changes: 6 additions & 1 deletion dataclass_wizard/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,18 +114,23 @@
'IS_NOT',
'IS_TRUTHY',
'IS_FALSY',
# V1
'Alias',
'AliasPath',
]

import logging

from .bases_meta import LoadMeta, DumpMeta, EnvMeta
from .dumpers import DumpMixin, setup_default_dumper, asdict
from .loaders import LoadMixin, setup_default_loader, fromlist, fromdict
from .loaders import LoadMixin, setup_default_loader
from .loader_selection import fromlist, fromdict
from .models import (env_field, json_field, json_key, path_field, skip_if_field,
KeyPath, Container,
Pattern, DatePattern, TimePattern, DateTimePattern,
CatchAll, SkipIf, SkipIfNone,
EQ, NE, LT, LE, GT, GE, IS, IS_NOT, IS_TRUTHY, IS_FALSY)
from .v1.models import Alias, AliasPath
from .environ.wizard import EnvWizard
from .property_wizard import property_wizard
from .serial_json import JSONWizard, JSONPyWizard, JSONSerializable
Expand Down
220 changes: 220 additions & 0 deletions dataclass_wizard/abstractions.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@

from .bases import META
from .models import Extras
from .v1.models import TypeInfo
from .type_def import T, TT


Expand Down Expand Up @@ -252,3 +253,222 @@ def get_parser_for_annotation(cls, ann_type,

class AbstractDumper(ABC):
__slots__ = ()


class AbstractLoaderGenerator(ABC):
"""
Abstract code generator which defines helper methods to generate the
code for deserializing an object `o` of a given annotated type into
the corresponding dataclass field during dynamic function construction.
"""
__slots__ = ()

@staticmethod
@abstractmethod
def transform_json_field(string: str) -> str:
"""
Transform a JSON field name (which will typically be camel-cased)
into the conventional format for a dataclass field name
(which will ideally be snake-cased).
"""

@staticmethod
@abstractmethod
def default_load_to(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code for the default load function if no other types match.
Generally, this will be a stub load method.
"""

@staticmethod
@abstractmethod
def load_after_type_check(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load an object after confirming its type.

:param type_str: The type annotation of the field as a string.
:param i: Index of the value being processed.
:param extras: Additional context or dependencies for code generation.
:raises ParseError: If the object type is not as expected.
"""

@staticmethod
@abstractmethod
def load_to_str(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a string field.
"""

@staticmethod
@abstractmethod
def load_to_int(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into an integer field.
"""

@staticmethod
@abstractmethod
def load_to_float(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a float field.
"""

@staticmethod
@abstractmethod
def load_to_bool(_: str, extras: Extras) -> str:
"""
Generate code to load a value into a boolean field.
Adds a helper function `as_bool` to the local context.
"""

@staticmethod
@abstractmethod
def load_to_bytes(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a bytes field.
"""

@staticmethod
@abstractmethod
def load_to_bytearray(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a bytearray field.
"""

@staticmethod
@abstractmethod
def load_to_none(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a None.
"""

@staticmethod
@abstractmethod
def load_to_literal(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to confirm a value is equivalent to one
of the provided literals.
"""

@classmethod
@abstractmethod
def load_to_union(cls, tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a `Union[X, Y, ...]` (one of [X, Y, ...] possible types)
"""

@staticmethod
@abstractmethod
def load_to_enum(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into an Enum field.
"""

@staticmethod
@abstractmethod
def load_to_uuid(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a UUID field.
"""

@staticmethod
@abstractmethod
def load_to_iterable(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into an iterable field (list, set, etc.).
"""

@staticmethod
@abstractmethod
def load_to_tuple(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a tuple field.
"""

@staticmethod
@abstractmethod
def load_to_named_tuple(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a named tuple field.
"""

@classmethod
@abstractmethod
def load_to_named_tuple_untyped(cls, tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into an untyped named tuple.
"""

@staticmethod
@abstractmethod
def load_to_dict(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a dictionary field.
"""

@staticmethod
@abstractmethod
def load_to_defaultdict(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a defaultdict field.
"""

@staticmethod
@abstractmethod
def load_to_typed_dict(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a typed dictionary field.
"""

@staticmethod
@abstractmethod
def load_to_decimal(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a Decimal field.
"""

@staticmethod
@abstractmethod
def load_to_datetime(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a datetime field.
"""

@staticmethod
@abstractmethod
def load_to_time(tp: TypeInfo, extras: Extras) -> str:
"""
Generate code to load a value into a time field.
"""

@staticmethod
@abstractmethod
def load_to_date(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a date field.
"""

@staticmethod
@abstractmethod
def load_to_timedelta(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a timedelta field.
"""

@staticmethod
def load_to_dataclass(tp: TypeInfo, extras: Extras) -> 'str | TypeInfo':
"""
Generate code to load a value into a `dataclass` type field.
"""

@classmethod
@abstractmethod
def get_string_for_annotation(cls,
tp: TypeInfo,
extras: Extras) -> 'str | TypeInfo':
"""
Generate code to get the parser (dispatcher) for a given annotation type.

`base_cls` is the original class object, useful when the annotated
type is a :class:`typing.ForwardRef` object.
"""
Loading
Loading