Skip to content

Commit 1dbcd8d

Browse files
Add json serialization support that is compatible with Cirq (#51)
2 parents 5158d08 + 20440f6 commit 1dbcd8d

5 files changed

Lines changed: 70 additions & 2 deletions

File tree

.github/workflows/ci.yml

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -79,5 +79,10 @@ jobs:
7979
- name: pytest
8080
run: ci/pytest_unit.sh
8181

82-
- name: perf
82+
- name: test performance
8383
run: ci/pytest_perf.sh
84+
85+
- name: test compatibility with Cirq
86+
run: |
87+
pip install cirq-core
88+
pytest test/* -m cirq

pyproject.toml

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,4 +11,8 @@ requires = ["setuptools", "wheel", "Cython"]
1111
testpaths = [
1212
"test",
1313
"test_perf",
14-
]
14+
]
15+
markers = [
16+
"cirq: mark a test as a compatibility test with Cirq.",
17+
]
18+
addopts = '-m "not cirq"'

test/cirq_compatibility.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2025 The TUnits Authors
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
import pytest
16+
import tunits as tu
17+
import cirq # type: ignore
18+
19+
20+
@pytest.mark.cirq
21+
def test_to_json() -> None:
22+
assert (
23+
cirq.to_json(tu.ns * 3)
24+
== """{
25+
"cirq_type": "tunits.Value",
26+
"value": 3,
27+
"unit": "ns"
28+
}"""
29+
)
30+
31+
assert (
32+
cirq.to_json(tu.GHz * [1, 2, 3, -1])
33+
== """{
34+
"cirq_type": "tunits.ValueArray",
35+
"value": [
36+
1.0,
37+
2.0,
38+
3.0,
39+
-1.0
40+
],
41+
"unit": "GHz"
42+
}"""
43+
)

tunits/core/__init__.pyi

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,6 +214,11 @@ class WithUnit:
214214
def __divmod__(self, other: Any) -> tuple[_NUMERICAL_TYPE_OR_ARRAY, 'WithUnit']: ...
215215
def _value_class(self) -> type['Value']: ...
216216
def _array_class(self) -> type['ValueArray']: ...
217+
def _json_dict_(self) -> dict[str, Any]: ...
218+
@classmethod
219+
def _json_namespace_(cls) -> str: ...
220+
@classmethod
221+
def _from_json_dict_(cls: type[T], **kwargs: Any) -> T: ...
217222

218223
class Value(Generic[NumericalT], WithUnit, np.generic, SupportsIndex):
219224
"""A floating-point value with associated units."""

tunits/core/cython/with_unit.pyx

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -674,6 +674,17 @@ cdef class WithUnit:
674674
raise ValueError(f'{self} is not dimensionless')
675675
return self.value_in_base_units()
676676

677+
def _json_dict_(self) -> dict[str, Any]:
678+
return {"value": self.value, "unit": self.units}
679+
680+
@classmethod
681+
def _json_namespace_(cls) -> str:
682+
return "tunits"
683+
684+
@classmethod
685+
def _from_json_dict_(cls, **kwargs):
686+
return cls(kwargs["value"], kwargs["unit"])
687+
677688
_try_interpret_as_with_unit = None
678689
_is_value_consistent_with_default_unit_database = None
679690
def init_base_unit_functions(

0 commit comments

Comments
 (0)