Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
1 change: 0 additions & 1 deletion cirq-core/cirq/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -604,7 +604,6 @@
JsonResolver,
json_cirq_type,
json_namespace,
json_serializable_dataclass,
dataclass_json_dict,
kraus,
LabelEntity,
Expand Down
1 change: 0 additions & 1 deletion cirq-core/cirq/protocols/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,6 @@
DEFAULT_RESOLVERS,
HasJSONNamespace,
JsonResolver,
json_serializable_dataclass,
json_cirq_type,
json_namespace,
to_json_gzip,
Expand Down
103 changes: 5 additions & 98 deletions cirq-core/cirq/protocols/json_serialization.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,6 @@
import sympy
from typing_extensions import Protocol

from cirq._compat import deprecated, deprecated_parameter
from cirq._doc import doc_private
from cirq.type_workarounds import NotImplementedType

Expand Down Expand Up @@ -148,23 +147,7 @@ def _json_namespace_(cls) -> str:
pass


# TODO: remove once deprecated parameter goes away.
def _obj_to_dict_helper_helper(obj: Any, attribute_names: Iterable[str]) -> Dict[str, Any]:
d = {}
for attr_name in attribute_names:
d[attr_name] = getattr(obj, attr_name)
return d


@deprecated_parameter(
deadline='v0.15',
fix='Define obj._json_namespace_ to return namespace instead.',
parameter_desc='namespace',
match=lambda args, kwargs: 'namespace' in kwargs,
)
def obj_to_dict_helper(
obj: Any, attribute_names: Iterable[str], namespace: Optional[str] = None
) -> Dict[str, Any]:
def obj_to_dict_helper(obj: Any, attribute_names: Iterable[str]) -> Dict[str, Any]:
"""Construct a dictionary containing attributes from obj

This is useful as a helper function in objects implementing the
Expand All @@ -178,88 +161,15 @@ def obj_to_dict_helper(
obj: A python object with attributes to be placed in the dictionary.
attribute_names: The names of attributes to serve as keys in the
resultant dictionary. The values will be the attribute values.
namespace: An optional prefix to the value associated with the
key "cirq_type". The namespace name will be joined with the
class name via a dot (.)
"""
d = {}
if namespace is not None:
d['cirq_type'] = f'{namespace}.' + obj.__class__.__name__
d.update(_obj_to_dict_helper_helper(obj, attribute_names))
for attr_name in attribute_names:
d[attr_name] = getattr(obj, attr_name)
return d


# Copying the Python API, whose usage of `repr` annoys pylint.
# pylint: disable=redefined-builtin
@deprecated(deadline='v0.15', fix='Implement _json_dict_ using cirq.dataclass_json_dict()')
def json_serializable_dataclass(
_cls: Optional[Type] = None,
*,
namespace: Optional[str] = None,
init: bool = True,
repr: bool = True,
eq: bool = True,
order: bool = False,
unsafe_hash: bool = False,
frozen: bool = False,
):
"""Create a dataclass that supports JSON serialization.

This function defers to the ordinary ``dataclass`` decorator but appends
the ``_json_dict_`` protocol method which automatically determines
the appropriate fields from the dataclass.

Dataclasses are implemented with somewhat complex metaprogramming, and
tooling (PyCharm, mypy) have special cases for dealing with classes
decorated with @dataclass. There is very little support (and no plans for
support) for decorators that wrap @dataclass like this. Consider explicitly
defining `_json_dict_` on your dataclasses which simply
`return dataclass_json_dict(self)`.

Args:
_cls: The class to add JSON serializatin to.
namespace: An optional prefix to the value associated with the
key "cirq_type". The namespace name will be joined with the
class name via a dot (.)
init: Forwarded to the `dataclass` constructor.
repr: Forwarded to the `dataclass` constructor.
eq: Forwarded to the `dataclass` constructor.
order: Forwarded to the `dataclass` constructor.
unsafe_hash: Forwarded to the `dataclass` constructor.
frozen: Forwarded to the `dataclass` constructor.
"""

def wrap(cls):
cls = dataclasses.dataclass(
cls, init=init, repr=repr, eq=eq, order=order, unsafe_hash=unsafe_hash, frozen=frozen
)

cls._json_namespace_ = lambda: namespace

cls._json_dict_ = lambda obj: obj_to_dict_helper(
obj, [f.name for f in dataclasses.fields(cls)]
)

return cls

# _cls is used to deduce if we're being called as
# @json_serializable_dataclass or @json_serializable_dataclass().
if _cls is None:
# We're called with parens.
return wrap

# We're called as @dataclass without parens.
return wrap(_cls)


# pylint: enable=redefined-builtin
@deprecated_parameter(
deadline='v0.15',
fix='Define obj._json_namespace_ to return namespace instead.',
parameter_desc='namespace',
match=lambda args, kwargs: 'namespace' in kwargs,
)
def dataclass_json_dict(obj: Any, namespace: str = None) -> Dict[str, Any]:
def dataclass_json_dict(obj: Any) -> Dict[str, Any]:
"""Return a dictionary suitable for _json_dict_ from a dataclass.

Dataclasses keep track of their relevant fields, so we can automatically generate these.
Expand All @@ -272,10 +182,7 @@ def dataclass_json_dict(obj: Any, namespace: str = None) -> Dict[str, Any]:
dataclasses which simply `return dataclass_json_dict(self)`.
"""
attribute_names = [f.name for f in dataclasses.fields(obj)]
if namespace is not None:
return obj_to_dict_helper(obj, attribute_names, namespace=namespace)
else:
return _obj_to_dict_helper_helper(obj, attribute_names)
return obj_to_dict_helper(obj, attribute_names)


def _json_dict_with_cirq_type(obj: Any):
Expand Down
137 changes: 1 addition & 136 deletions cirq-core/cirq/protocols/json_serialization_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
import pathlib
import sys
import warnings
from typing import ClassVar, Dict, List, Optional, Tuple, Type
from typing import Dict, List, Optional, Tuple, Type
from unittest import mock

import networkx as nx
Expand Down Expand Up @@ -103,68 +103,6 @@ def custom_resolver(name):
assert_json_roundtrip_works(HasOldJsonDict(), resolvers=test_resolvers)


def test_deprecated_obj_to_dict_helper_namespace():
class HasOldJsonDict:
# Required for testing serialization of non-cirq objects.
__module__ = 'test.noncirq.namespace' # type: ignore

def __init__(self, x):
self.x = x

def __eq__(self, other):
return isinstance(other, HasOldJsonDict) and other.x == self.x

def _json_dict_(self):
return json_serialization.obj_to_dict_helper(
self, ['x'], namespace='test.noncirq.namespace'
)

@classmethod
def _from_json_dict_(cls, x, **kwargs):
return cls(x)

with pytest.raises(ValueError, match='not a Cirq type'):
_ = cirq.json_cirq_type(HasOldJsonDict)

def custom_resolver(name):
if name == 'test.noncirq.namespace.HasOldJsonDict':
return HasOldJsonDict

test_resolvers = [custom_resolver] + cirq.DEFAULT_RESOLVERS
with cirq.testing.assert_deprecated(
"Found 'cirq_type'", 'Define obj._json_namespace_', deadline='v0.15', count=3
):
assert_json_roundtrip_works(HasOldJsonDict(1), resolvers=test_resolvers)


def test_deprecated_dataclass_json_dict_namespace():
@dataclasses.dataclass
class HasOldJsonDict:
# Required for testing serialization of non-cirq objects.
__module__: ClassVar = 'test.noncirq.namespace' # type: ignore
x: int

def _json_dict_(self):
return json_serialization.dataclass_json_dict(self, namespace='test.noncirq.namespace')

@classmethod
def _from_json_dict_(cls, x, **kwargs):
return cls(x)

with pytest.raises(ValueError, match='not a Cirq type'):
_ = cirq.json_cirq_type(HasOldJsonDict)

def custom_resolver(name):
if name == 'test.noncirq.namespace.HasOldJsonDict':
return HasOldJsonDict

test_resolvers = [custom_resolver] + cirq.DEFAULT_RESOLVERS
with cirq.testing.assert_deprecated(
"Found 'cirq_type'", 'Define obj._json_namespace_', deadline='v0.15', count=5
):
assert_json_roundtrip_works(HasOldJsonDict(1), resolvers=test_resolvers)


def test_line_qubit_roundtrip():
q1 = cirq.LineQubit(12)
assert_json_roundtrip_works(
Expand Down Expand Up @@ -829,59 +767,6 @@ def test_pathlib_paths(tmpdir):
assert cirq.read_json_gzip(gzip_path) == cirq.X


def test_json_serializable_dataclass():
with cirq.testing.assert_deprecated(
"Implement _json_dict_ using cirq.dataclass_json_dict()", deadline="v0.15"
):

@cirq.json_serializable_dataclass
class MyDC:
q: cirq.LineQubit
desc: str

my_dc = MyDC(cirq.LineQubit(4), 'hi mom')

def custom_resolver(name):
if name == 'MyDC':
return MyDC

assert_json_roundtrip_works(
my_dc,
text_should_be="\n".join(
[
'{',
' "cirq_type": "MyDC",',
' "q": {',
' "cirq_type": "LineQubit",',
' "x": 4',
' },',
' "desc": "hi mom"',
'}',
]
),
resolvers=[custom_resolver] + cirq.DEFAULT_RESOLVERS,
)


def test_json_serializable_dataclass_parenthesis():
with cirq.testing.assert_deprecated(
"Implement _json_dict_ using cirq.dataclass_json_dict()", deadline="v0.15"
):

@cirq.json_serializable_dataclass()
class MyDC:
q: cirq.LineQubit
desc: str

def custom_resolver(name):
if name == 'MyDC':
return MyDC

my_dc = MyDC(cirq.LineQubit(4), 'hi mom')

assert_json_roundtrip_works(my_dc, resolvers=[custom_resolver] + cirq.DEFAULT_RESOLVERS)


def test_dataclass_json_dict():
@dataclasses.dataclass(frozen=True)
class MyDC:
Expand All @@ -900,26 +785,6 @@ def custom_resolver(name):
assert_json_roundtrip_works(my_dc, resolvers=[custom_resolver, *cirq.DEFAULT_RESOLVERS])


def test_json_serializable_dataclass_namespace():
with cirq.testing.assert_deprecated(
"Implement _json_dict_ using cirq.dataclass_json_dict()", deadline="v0.15"
):

@cirq.json_serializable_dataclass(namespace='cirq.experiments')
class QuantumVolumeParams:
width: int
depth: int
circuit_i: int

qvp = QuantumVolumeParams(width=5, depth=5, circuit_i=0)

def custom_resolver(name):
if name == 'cirq.experiments.QuantumVolumeParams':
return QuantumVolumeParams

assert_json_roundtrip_works(qvp, resolvers=[custom_resolver] + cirq.DEFAULT_RESOLVERS)


def test_numpy_values():
assert (
cirq.to_json({'value': np.array(1)})
Expand Down