Skip to content

Commit

Permalink
Support for Counter objects (#471)
Browse files Browse the repository at this point in the history
  • Loading branch information
matt035343 authored Aug 18, 2023
1 parent dcb9372 commit 89578cb
Show file tree
Hide file tree
Showing 4 changed files with 31 additions and 4 deletions.
6 changes: 4 additions & 2 deletions dataclasses_json/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
_is_collection, _is_mapping, _is_new_type,
_is_optional, _isinstance_safe,
_get_type_arg_param,
_get_type_args,
_get_type_args, _is_counter,
_NO_ARGS,
_issubclass_safe, _is_tuple)

Expand Down Expand Up @@ -271,7 +271,7 @@ def _decode_generic(type_, value, infer_missing):
res = type_(value)
# FIXME this is a hack to fix a deeper underlying issue. A refactor is due.
elif _is_collection(type_):
if _is_mapping(type_):
if _is_mapping(type_) and not _is_counter(type_):
k_type, v_type = _get_type_args(type_, (Any, Any))
# a mapping type has `.keys()` and `.values()`
# (see collections.abc)
Expand All @@ -284,6 +284,8 @@ def _decode_generic(type_, value, infer_missing):
xs = _decode_items(types[0], value, infer_missing)
else:
xs = _decode_items(_get_type_args(type_) or _NO_ARGS, value, infer_missing)
elif _is_counter(type_):
xs = dict(zip(_decode_items(_get_type_arg_param(type_, 0), value.keys(), infer_missing), value.values()))
else:
xs = _decode_items(_get_type_arg_param(type_, 0), value, infer_missing)

Expand Down
5 changes: 5 additions & 0 deletions dataclasses_json/utils.py
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import inspect
import sys
from datetime import datetime, timezone
from collections import Counter
from typing import (Collection, Mapping, Optional, TypeVar, Any, Type, Tuple,
Union, cast)

Expand Down Expand Up @@ -142,6 +143,10 @@ def _is_optional(type_):
type_ is Any)


def _is_counter(type_):
return _issubclass_safe(_get_type_origin(type_), Counter)


def _is_mapping(type_):
return _issubclass_safe(_get_type_origin(type_), Mapping)

Expand Down
11 changes: 11 additions & 0 deletions tests/entities.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
import sys
from collections import deque
from dataclasses import dataclass, field
from datetime import datetime
Expand All @@ -15,6 +16,10 @@
Union,
Any)
from uuid import UUID
if sys.version_info >= (3, 9):
from collections import Counter
else:
from typing import Counter

from marshmallow import fields

Expand Down Expand Up @@ -370,3 +375,9 @@ class DataClassWithNestedOptional:
@dataclass
class DataClassWithNestedDictWithTupleKeys:
a: Dict[Tuple[int], int]


@dataclass_json
@dataclass
class DataClassWithCounter:
c: Counter[str]
13 changes: 11 additions & 2 deletions tests/test_collections.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
from collections import deque
from collections import Counter, deque

from tests.entities import (DataClassIntImmutableDefault,
DataClassMutableDefaultDict,
Expand All @@ -19,7 +19,8 @@
DataClassWithFrozenSetUnbound,
DataClassWithDequeCollections,
DataClassWithTuple, DataClassWithTupleUnbound,
DataClassWithUnionIntNone, MyCollection)
DataClassWithUnionIntNone, MyCollection,
DataClassWithCounter)


class TestEncoder:
Expand Down Expand Up @@ -112,6 +113,10 @@ def test_mutable_default_list(self):
def test_mutable_default_dict(self):
assert DataClassMutableDefaultDict().to_json() == '{"xs": {}}'

def test_counter(self):
assert DataClassWithCounter(
c=Counter('foo')).to_json() == '{"c": {"f": 1, "o": 2}}'


class TestDecoder:
def test_list(self):
Expand Down Expand Up @@ -235,3 +240,7 @@ def test_mutable_default_dict(self):
== DataClassMutableDefaultDict())
assert (DataClassMutableDefaultDict.from_json('{}', infer_missing=True)
== DataClassMutableDefaultDict())

def test_counter(self):
assert DataClassWithCounter.from_json('{"c": {"f": 1, "o": 2}}') == \
DataClassWithCounter(c=Counter('foo'))

6 comments on commit 89578cb

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2371096%38–41, 51, 64, 66, 81, 83, 169, 197
   mm.py2023085%33–36, 42–45, 53–56, 62–65, 88, 161–162, 167, 171, 175, 180, 184, 188, 196, 202, 207, 216, 221, 226, 235, 244–251
   stringcase.py25388%59, 76, 97
   undefined.py143299%24, 38
   utils.py1313673%12–25, 45–50, 61–65, 75, 100–101, 109–110, 125–133, 163, 182, 207
tests
   entities.py234399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142497%88, 99, 139–140
   test_str_subclass.py22195%9
   test_union.py981090%87–94, 108–115
TOTAL250314594% 

Tests Skipped Failures Errors Time
294 3 💤 0 ❌ 0 🔥 3.366s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2371096%38–41, 51, 64, 66, 81, 83, 169, 197
   mm.py2023085%33–36, 42–45, 53–56, 62–65, 88, 161–162, 167, 171, 175, 180, 184, 188, 196, 202, 207, 216, 221, 226, 235, 244–251
   stringcase.py25388%59, 76, 97
   undefined.py143299%24, 38
   utils.py1313673%12–25, 45–50, 61–65, 75, 100–101, 109–110, 125–133, 163, 182, 207
tests
   entities.py234399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142497%88, 99, 139–140
   test_str_subclass.py22195%9
   test_union.py981090%87–94, 108–115
TOTAL250314594% 

Tests Skipped Failures Errors Time
294 3 💤 0 ❌ 0 🔥 3.810s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py237996%38–41, 51, 64, 66, 81, 83, 169
   mm.py2012986%33–36, 42–45, 53–56, 62–65, 88, 161–162, 167, 171, 175, 180, 184, 188, 196, 202, 207, 216, 221, 226, 244–251
   stringcase.py25388%59, 76, 97
   undefined.py141299%24, 38
   utils.py1312978%12–25, 45–50, 61–65, 75, 100–101, 109–110, 163, 182, 207
tests
   entities.py213399%20, 234, 240
   test_annotations.py804248%50–67, 78–102, 106–122
   test_api.py140299%139–140
   test_str_subclass.py22195%9
TOTAL238812495% 

Tests Skipped Failures Errors Time
294 1 💤 0 ❌ 0 🔥 3.871s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py236996%38–41, 51, 64, 66, 81, 83, 169
   mm.py2012986%33–36, 42–45, 53–56, 62–65, 88, 161–162, 167, 171, 175, 180, 184, 188, 196, 202, 207, 216, 221, 226, 244–251
   stringcase.py25388%59, 76, 97
   undefined.py143299%24, 38
   utils.py1312978%12–25, 45–50, 61–65, 75, 100–101, 109–110, 163, 182, 207
tests
   entities.py234399%20, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142299%139–140
   test_str_subclass.py22195%9
TOTAL250112495% 

Tests Skipped Failures Errors Time
294 1 💤 0 ❌ 0 🔥 4.283s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py236996%38–41, 51, 64, 66, 81, 83, 169
   mm.py2012986%33–36, 42–45, 53–56, 62–65, 88, 161–162, 167, 171, 175, 180, 184, 188, 196, 202, 207, 216, 221, 226, 244–251
   stringcase.py25388%59, 76, 97
   undefined.py143299%24, 38
   utils.py1312978%12–25, 45–50, 61–65, 75, 100–101, 109–110, 163, 182, 207
tests
   entities.py234399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142199%139
   test_str_subclass.py22195%9
TOTAL250112395% 

Tests Skipped Failures Errors Time
294 1 💤 0 ❌ 0 🔥 4.104s ⏱️

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Coverage

Coverage Report
FileStmtsMissCoverMissing
dataclasses_json
   cfg.py51492%80, 84–86
   core.py2371096%38–41, 51, 64, 66, 81, 83, 169, 197
   mm.py2023085%33–36, 42–45, 53–56, 62–65, 88, 161–162, 167, 171, 175, 180, 184, 188, 196, 202, 207, 216, 221, 226, 235, 244–251
   stringcase.py25388%59, 76, 97
   undefined.py143299%24, 38
   utils.py1313673%12–25, 45–50, 61–65, 75, 100–101, 109–110, 125–133, 163, 182, 207
tests
   entities.py234399%22, 234, 240
   test_annotations.py814248%50–67, 78–102, 106–122
   test_api.py142497%88, 99, 139–140
   test_str_subclass.py22195%9
   test_union.py981090%87–94, 108–115
TOTAL250314594% 

Tests Skipped Failures Errors Time
294 3 💤 0 ❌ 0 🔥 6.983s ⏱️

Please sign in to comment.