From 39a1d5652d5fab8c307ded56e7e355b4f22e811c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Nov 2021 14:21:37 +0300 Subject: [PATCH 01/18] Add .env folder to gitignore --- .gitignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.gitignore b/.gitignore index c746b8b..622f55f 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,4 @@ __pycache__/ .coverage .auto-format /toml-test/ +/.env/ From 9ff9816ee0d41c2e79be67e5198137205537fecf Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Nov 2021 14:21:56 +0300 Subject: [PATCH 02/18] Dont serialize map entity whereas valus is None --- src/lib.rs | 35 ++++++++++++++++++++--------------- 1 file changed, 20 insertions(+), 15 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index c2272f0..50856c0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -83,23 +83,28 @@ impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> { macro_rules! add_to_map { ($map:ident, $key:ident, $value:ident) => { - if $key.is_none() { - $map.serialize_key("null")?; - } else if let Ok(key) = $key.extract::() { - $map.serialize_key(if key { "true" } else { "false" })?; - } else if let Ok(key) = $key.str() { - let key = key.to_string(); - $map.serialize_key(&key)?; + if $value.is_none() { + // in case of none we don't need to serialize anything + // we just ingore this field. } else { - return Err(ser::Error::custom(format_args!( - "Dictionary key is not a string: {:?}", - $key - ))); + if $key.is_none() { + $map.serialize_key("null")?; + } else if let Ok(key) = $key.extract::() { + $map.serialize_key(if key { "true" } else { "false" })?; + } else if let Ok(key) = $key.str() { + let key = key.to_string(); + $map.serialize_key(&key)?; + } else { + return Err(ser::Error::custom(format_args!( + "Dictionary key is not a string: {:?}", + $key + ))); + } + $map.serialize_value(&SerializePyObject { + py: self.py, + obj: $value, + })?; } - $map.serialize_value(&SerializePyObject { - py: self.py, - obj: $value, - })?; }; } From fc35a3cc66b76e19f944e99972112017dac440b6 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Nov 2021 14:22:52 +0300 Subject: [PATCH 03/18] Add tests for None value in maps --- tests/test_dump.py | 3 +++ tests/test_load.py | 3 +++ 2 files changed, 6 insertions(+) diff --git a/tests/test_dump.py b/tests/test_dump.py index 25e24c6..b3d8b1f 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -61,3 +61,6 @@ def test_dump_file(tmp_path): def test_varied_list(): assert rtoml.dumps({'test': [1, '2']}) == 'test = [1, "2"]\n' + +def test_none_map_value(): + assert rtoml.dumps({'key': None}) == '' diff --git a/tests/test_load.py b/tests/test_load.py index 28517bc..b23e993 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -222,3 +222,6 @@ def test_subtable(): smooth = true """ assert rtoml.loads(s) == ... + +def test_empty_toml(): + assert rtoml.loads('') == {} From da4497c301835893076edf71857f5d8e91382539 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Nov 2021 14:32:00 +0300 Subject: [PATCH 04/18] Add more tests for None value in the map --- tests/test_dump.py | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/tests/test_dump.py b/tests/test_dump.py index b3d8b1f..5352034 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -62,5 +62,14 @@ def test_dump_file(tmp_path): def test_varied_list(): assert rtoml.dumps({'test': [1, '2']}) == 'test = [1, "2"]\n' -def test_none_map_value(): - assert rtoml.dumps({'key': None}) == '' +@pytest.mark.parametrize( + 'input_obj,output_toml', + [ + ({'key': None}, ''), + ({'foo': 'bar', 'key1': None}, 'foo = "bar"\n'), + ({'key1': None, 'foo': 'bar'}, 'foo = "bar"\n'), + ({'key1': None, 'foo': 'bar', 'key2': None}, 'foo = "bar"\n'), + ], +) +def test_none_map_value(input_obj,output_toml): + assert rtoml.dumps(input_obj) == output_toml From 04624561e119fb09904305ea04f4ec252636fadd Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Nov 2021 14:41:46 +0300 Subject: [PATCH 05/18] Fix lint warnings --- tests/test_dump.py | 3 ++- tests/test_load.py | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/tests/test_dump.py b/tests/test_dump.py index 5352034..31fe0b6 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -62,6 +62,7 @@ def test_dump_file(tmp_path): def test_varied_list(): assert rtoml.dumps({'test': [1, '2']}) == 'test = [1, "2"]\n' + @pytest.mark.parametrize( 'input_obj,output_toml', [ @@ -71,5 +72,5 @@ def test_varied_list(): ({'key1': None, 'foo': 'bar', 'key2': None}, 'foo = "bar"\n'), ], ) -def test_none_map_value(input_obj,output_toml): +def test_none_map_value(input_obj, output_toml): assert rtoml.dumps(input_obj) == output_toml diff --git a/tests/test_load.py b/tests/test_load.py index b23e993..0fc647a 100644 --- a/tests/test_load.py +++ b/tests/test_load.py @@ -223,5 +223,6 @@ def test_subtable(): """ assert rtoml.loads(s) == ... + def test_empty_toml(): assert rtoml.loads('') == {} From 67ab23d8b7d94096920b58adce89b6fb0c74a992 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Nov 2021 15:02:08 +0300 Subject: [PATCH 06/18] Ommit None values in sequences --- src/lib.rs | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 50856c0..195a975 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -140,6 +140,11 @@ impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> { cast!(|x: $type| { let mut seq = serializer.serialize_seq(Some(x.len()))?; for element in x { + if element.is_none() { + // None values must be omitted + continue + } + seq.serialize_element(&SerializePyObject { py: self.py, obj: element, From d7eb3eef6699733f98b42e5b11a197c67b86192f Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sat, 6 Nov 2021 15:02:31 +0300 Subject: [PATCH 07/18] Add test for None in sequences --- tests/test_dump.py | 32 ++++++++++++++++++++++++++++++++ 1 file changed, 32 insertions(+) diff --git a/tests/test_dump.py b/tests/test_dump.py index 31fe0b6..8d849ee 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -74,3 +74,35 @@ def test_varied_list(): ) def test_none_map_value(input_obj, output_toml): assert rtoml.dumps(input_obj) == output_toml + +@pytest.mark.parametrize( + 'input_obj,output_toml', + [ + ([None], '[]'), + (["a", None], '["a"]'), + ([None, "b"], '["b"]'), + (["a", None, "b"], '["a", "b"]'), + ({'foo': 'bar', 'list': [None]}, 'foo = "bar"\nlist = []\n'), + ({'foo': 'bar', 'list': [None, "b"]}, 'foo = "bar"\nlist = ["b"]\n'), + ({'foo': 'bar', 'list': ["a", None]}, 'foo = "bar"\nlist = ["a"]\n'), + ({'foo': 'bar', 'list': ["a", None, "b"]}, 'foo = "bar"\nlist = ["a", "b"]\n'), + ], +) +def test_none_values_inside_list(input_obj, output_toml): + assert rtoml.dumps(input_obj) == output_toml + +@pytest.mark.parametrize( + 'input_obj,output_toml', + [ + ((None), '"null"'), + (("a", None), '["a"]'), + ((None, "b"), '["b"]'), + (("a", None, "b"), '["a", "b"]'), + ({'foo': 'bar', 'list': (None)}, 'foo = "bar"\n'), + ({'foo': 'bar', 'list': (None, "b")}, 'foo = "bar"\nlist = ["b"]\n'), + ({'foo': 'bar', 'list': ("a", None)}, 'foo = "bar"\nlist = ["a"]\n'), + ({'foo': 'bar', 'list': ("a", None, "b")}, 'foo = "bar"\nlist = ["a", "b"]\n'), + ], +) +def test_none_values_inside_tuple(input_obj, output_toml): + assert rtoml.dumps(input_obj) == output_toml From 1d55b166b6222d535d4977088947e919db3ede0a Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sun, 5 Dec 2021 17:45:57 +0300 Subject: [PATCH 08/18] Add omitNone argument --- rtoml/__init__.py | 4 ++-- rtoml/_rtoml.pyi | 4 ++-- src/lib.rs | 42 ++++++++++++++++++++++++++---------------- 3 files changed, 30 insertions(+), 20 deletions(-) diff --git a/rtoml/__init__.py b/rtoml/__init__.py index 95123fe..058d2ba 100644 --- a/rtoml/__init__.py +++ b/rtoml/__init__.py @@ -34,7 +34,7 @@ def loads(toml: str) -> Dict[str, Any]: return _rtoml.deserialize(toml) -def dumps(obj: Any, *, pretty: bool = False) -> str: +def dumps(obj: Any, *, pretty: bool = False, omitNone: bool = False) -> str: """ Serialize a python object to TOML. @@ -45,7 +45,7 @@ def dumps(obj: Any, *, pretty: bool = False) -> str: else: serialize = _rtoml.serialize - return serialize(obj) + return serialize(obj, omitNone); def dump(obj: Any, file: Union[Path, TextIO], *, pretty: bool = False) -> int: diff --git a/rtoml/_rtoml.pyi b/rtoml/_rtoml.pyi index 8cb658f..07627df 100644 --- a/rtoml/_rtoml.pyi +++ b/rtoml/_rtoml.pyi @@ -4,8 +4,8 @@ from typing import Any, Callable, Union VERSION: str def deserialize(toml: str) -> Any: ... -def serialize(obj: Any) -> str: ... -def serialize_pretty(obj: Any) -> str: ... +def serialize(obj: Any, omitNone: bool) -> str: ... +def serialize_pretty(obj: Any, omitNone: bool) -> str: ... class TomlParsingError(ValueError): ... class TomlSerializationError(ValueError): ... diff --git a/src/lib.rs b/src/lib.rs index 195a975..bd98917 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ extern crate pyo3; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::types::{PyAny, PyDateTime, PyDict, PyFloat, PyList, PyTuple}; +use pyo3::types::{PyAny, PyDateTime, PyDict, PyFloat, PyList, PyTuple, PyBool}; use pyo3::{create_exception, wrap_pyfunction, PyErrArguments}; use serde::ser::{self, Serialize, SerializeMap, SerializeSeq, Serializer}; use std::str::FromStr; @@ -49,12 +49,26 @@ fn deserialize(py: Python, toml: String) -> PyResult { } // taken from https://github.com/mre/hyperjson/blob/10d31608584ef4499d6b6b10b6dc9455b358fe3d/src/lib.rs#L287-L402 -struct SerializePyObject<'p, 'a> { +struct SerializePyObject<'p> { py: Python<'p>, - obj: &'a PyAny, + obj: &'p PyAny, + omit_none: bool, } -impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> { +impl<'p> SerializePyObject<'p> { + fn new(py: Python<'p>, obj: &'p PyObject, omit_none: &PyObject) -> PyResult { + let omit_none = omit_none.extract::<&PyBool>(py)?.is_true(); + let obj = obj.extract(py)?; + + Ok(Self { + py, + omit_none, + obj, + }) + } +} + +impl<'p> Serialize for SerializePyObject<'p> { fn serialize(&self, serializer: S) -> Result where S: Serializer, @@ -103,6 +117,7 @@ impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> { $map.serialize_value(&SerializePyObject { py: self.py, obj: $value, + omit_none: self.omit_none, })?; } }; @@ -148,6 +163,7 @@ impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> { seq.serialize_element(&SerializePyObject { py: self.py, obj: element, + omit_none: self.omit_none, })? } return seq.end(); @@ -193,24 +209,18 @@ impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> { } #[pyfunction] -fn serialize(py: Python, obj: PyObject) -> PyResult { - let s = SerializePyObject { - py, - obj: obj.extract(py)?, - }; - match to_toml_string(&s) { +fn serialize(py: Python, obj: PyObject, omit_none: PyObject) -> PyResult { + let serializer = SerializePyObject::new(py, &obj, &omit_none)?; + match to_toml_string(&serializer) { Ok(s) => Ok(s), Err(e) => Err(TomlSerializationError::new_err(e.to_string())), } } #[pyfunction] -fn serialize_pretty(py: Python, obj: PyObject) -> PyResult { - let s = SerializePyObject { - py, - obj: obj.extract(py)?, - }; - match to_toml_string_pretty(&s) { +fn serialize_pretty(py: Python, obj: PyObject, omit_none: PyObject) -> PyResult { + let serializer = SerializePyObject::new(py, &obj, &omit_none)?; + match to_toml_string_pretty(&serializer) { Ok(s) => Ok(s), Err(e) => Err(TomlSerializationError::new_err(e.to_string())), } From f81d05115375e2648fabe5464d13884da4a20a8e Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sun, 5 Dec 2021 17:50:20 +0300 Subject: [PATCH 09/18] Use omitNone flag --- src/lib.rs | 4 ++-- tests/test_dump.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index bd98917..8c26564 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -97,7 +97,7 @@ impl<'p> Serialize for SerializePyObject<'p> { macro_rules! add_to_map { ($map:ident, $key:ident, $value:ident) => { - if $value.is_none() { + if self.omit_none && $value.is_none() { // in case of none we don't need to serialize anything // we just ingore this field. } else { @@ -155,7 +155,7 @@ impl<'p> Serialize for SerializePyObject<'p> { cast!(|x: $type| { let mut seq = serializer.serialize_seq(Some(x.len()))?; for element in x { - if element.is_none() { + if self.omit_none && element.is_none() { // None values must be omitted continue } diff --git a/tests/test_dump.py b/tests/test_dump.py index 8d849ee..23d6d70 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -73,7 +73,7 @@ def test_varied_list(): ], ) def test_none_map_value(input_obj, output_toml): - assert rtoml.dumps(input_obj) == output_toml + assert rtoml.dumps(input_obj, omitNone=True) == output_toml @pytest.mark.parametrize( 'input_obj,output_toml', @@ -89,7 +89,7 @@ def test_none_map_value(input_obj, output_toml): ], ) def test_none_values_inside_list(input_obj, output_toml): - assert rtoml.dumps(input_obj) == output_toml + assert rtoml.dumps(input_obj, omitNone=True) == output_toml @pytest.mark.parametrize( 'input_obj,output_toml', @@ -105,4 +105,4 @@ def test_none_values_inside_list(input_obj, output_toml): ], ) def test_none_values_inside_tuple(input_obj, output_toml): - assert rtoml.dumps(input_obj) == output_toml + assert rtoml.dumps(input_obj, omitNone=True) == output_toml From 772abe493612969b4eba42ab9c19fcca1637d487 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Sun, 5 Dec 2021 17:51:54 +0300 Subject: [PATCH 10/18] Rename omitNone to omit_none --- rtoml/__init__.py | 4 ++-- tests/test_dump.py | 6 +++--- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/rtoml/__init__.py b/rtoml/__init__.py index 058d2ba..cdcce95 100644 --- a/rtoml/__init__.py +++ b/rtoml/__init__.py @@ -34,7 +34,7 @@ def loads(toml: str) -> Dict[str, Any]: return _rtoml.deserialize(toml) -def dumps(obj: Any, *, pretty: bool = False, omitNone: bool = False) -> str: +def dumps(obj: Any, *, pretty: bool = False, omit_none: bool = False) -> str: """ Serialize a python object to TOML. @@ -45,7 +45,7 @@ def dumps(obj: Any, *, pretty: bool = False, omitNone: bool = False) -> str: else: serialize = _rtoml.serialize - return serialize(obj, omitNone); + return serialize(obj, omit_none); def dump(obj: Any, file: Union[Path, TextIO], *, pretty: bool = False) -> int: diff --git a/tests/test_dump.py b/tests/test_dump.py index 23d6d70..017f6c5 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -73,7 +73,7 @@ def test_varied_list(): ], ) def test_none_map_value(input_obj, output_toml): - assert rtoml.dumps(input_obj, omitNone=True) == output_toml + assert rtoml.dumps(input_obj, omit_none=True) == output_toml @pytest.mark.parametrize( 'input_obj,output_toml', @@ -89,7 +89,7 @@ def test_none_map_value(input_obj, output_toml): ], ) def test_none_values_inside_list(input_obj, output_toml): - assert rtoml.dumps(input_obj, omitNone=True) == output_toml + assert rtoml.dumps(input_obj, omit_none=True) == output_toml @pytest.mark.parametrize( 'input_obj,output_toml', @@ -105,4 +105,4 @@ def test_none_values_inside_list(input_obj, output_toml): ], ) def test_none_values_inside_tuple(input_obj, output_toml): - assert rtoml.dumps(input_obj, omitNone=True) == output_toml + assert rtoml.dumps(input_obj, omit_none=True) == output_toml From 1732cd6ec8118d36bda562fc5f75d28cb3db38cb Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Wed, 8 Dec 2021 20:44:58 +0300 Subject: [PATCH 11/18] Address PR issues --- rtoml/__init__.py | 8 ++++---- rtoml/_rtoml.pyi | 4 ++-- src/lib.rs | 32 ++++++++++++++------------------ tests/test_dump.py | 7 ++++--- 4 files changed, 24 insertions(+), 27 deletions(-) diff --git a/rtoml/__init__.py b/rtoml/__init__.py index cdcce95..3c1fc8a 100644 --- a/rtoml/__init__.py +++ b/rtoml/__init__.py @@ -34,7 +34,7 @@ def loads(toml: str) -> Dict[str, Any]: return _rtoml.deserialize(toml) -def dumps(obj: Any, *, pretty: bool = False, omit_none: bool = False) -> str: +def dumps(obj: Any, *, pretty: bool = False, include_none: bool = True) -> str: """ Serialize a python object to TOML. @@ -45,16 +45,16 @@ def dumps(obj: Any, *, pretty: bool = False, omit_none: bool = False) -> str: else: serialize = _rtoml.serialize - return serialize(obj, omit_none); + return serialize(obj, include_none); -def dump(obj: Any, file: Union[Path, TextIO], *, pretty: bool = False) -> int: +def dump(obj: Any, file: Union[Path, TextIO], *, pretty: bool = False, include_none: bool = True) -> int: """ Serialize a python object to TOML and write it to a file. `file` may be a `Path` or file object from `open()`. If `pretty` is true, output has a more "pretty" format. """ - s = dumps(obj, pretty=pretty) + s = dumps(obj, pretty=pretty, include_none=include_none) if isinstance(file, Path): return file.write_text(s, encoding='UTF-8') else: diff --git a/rtoml/_rtoml.pyi b/rtoml/_rtoml.pyi index 07627df..44137ae 100644 --- a/rtoml/_rtoml.pyi +++ b/rtoml/_rtoml.pyi @@ -4,8 +4,8 @@ from typing import Any, Callable, Union VERSION: str def deserialize(toml: str) -> Any: ... -def serialize(obj: Any, omitNone: bool) -> str: ... -def serialize_pretty(obj: Any, omitNone: bool) -> str: ... +def serialize(obj: Any, include_none: bool) -> str: ... +def serialize_pretty(obj: Any, include_none: bool) -> str: ... class TomlParsingError(ValueError): ... class TomlSerializationError(ValueError): ... diff --git a/src/lib.rs b/src/lib.rs index 8c26564..673c8aa 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ extern crate pyo3; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::types::{PyAny, PyDateTime, PyDict, PyFloat, PyList, PyTuple, PyBool}; +use pyo3::types::{PyAny, PyBool, PyDateTime, PyDict, PyFloat, PyList, PyTuple}; use pyo3::{create_exception, wrap_pyfunction, PyErrArguments}; use serde::ser::{self, Serialize, SerializeMap, SerializeSeq, Serializer}; use std::str::FromStr; @@ -52,19 +52,15 @@ fn deserialize(py: Python, toml: String) -> PyResult { struct SerializePyObject<'p> { py: Python<'p>, obj: &'p PyAny, - omit_none: bool, + include_none: bool, } impl<'p> SerializePyObject<'p> { - fn new(py: Python<'p>, obj: &'p PyObject, omit_none: &PyObject) -> PyResult { - let omit_none = omit_none.extract::<&PyBool>(py)?.is_true(); + fn new(py: Python<'p>, obj: &'p PyObject, include_none: &PyObject) -> PyResult { + let include_none = include_none.extract::<&PyBool>(py)?.is_true(); let obj = obj.extract(py)?; - Ok(Self { - py, - omit_none, - obj, - }) + Ok(Self { py, include_none, obj }) } } @@ -97,7 +93,7 @@ impl<'p> Serialize for SerializePyObject<'p> { macro_rules! add_to_map { ($map:ident, $key:ident, $value:ident) => { - if self.omit_none && $value.is_none() { + if !self.include_none && ($value.is_none() || $key.is_none()) { // in case of none we don't need to serialize anything // we just ingore this field. } else { @@ -117,7 +113,7 @@ impl<'p> Serialize for SerializePyObject<'p> { $map.serialize_value(&SerializePyObject { py: self.py, obj: $value, - omit_none: self.omit_none, + include_none: self.include_none, })?; } }; @@ -155,15 +151,15 @@ impl<'p> Serialize for SerializePyObject<'p> { cast!(|x: $type| { let mut seq = serializer.serialize_seq(Some(x.len()))?; for element in x { - if self.omit_none && element.is_none() { + if !self.include_none && element.is_none() { // None values must be omitted - continue + continue; } seq.serialize_element(&SerializePyObject { py: self.py, obj: element, - omit_none: self.omit_none, + include_none: self.include_none, })? } return seq.end(); @@ -209,8 +205,8 @@ impl<'p> Serialize for SerializePyObject<'p> { } #[pyfunction] -fn serialize(py: Python, obj: PyObject, omit_none: PyObject) -> PyResult { - let serializer = SerializePyObject::new(py, &obj, &omit_none)?; +fn serialize(py: Python, obj: PyObject, include_none: PyObject) -> PyResult { + let serializer = SerializePyObject::new(py, &obj, &include_none)?; match to_toml_string(&serializer) { Ok(s) => Ok(s), Err(e) => Err(TomlSerializationError::new_err(e.to_string())), @@ -218,8 +214,8 @@ fn serialize(py: Python, obj: PyObject, omit_none: PyObject) -> PyResult } #[pyfunction] -fn serialize_pretty(py: Python, obj: PyObject, omit_none: PyObject) -> PyResult { - let serializer = SerializePyObject::new(py, &obj, &omit_none)?; +fn serialize_pretty(py: Python, obj: PyObject, include_none: PyObject) -> PyResult { + let serializer = SerializePyObject::new(py, &obj, &include_none)?; match to_toml_string_pretty(&serializer) { Ok(s) => Ok(s), Err(e) => Err(TomlSerializationError::new_err(e.to_string())), diff --git a/tests/test_dump.py b/tests/test_dump.py index 017f6c5..c0efae7 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -66,6 +66,7 @@ def test_varied_list(): @pytest.mark.parametrize( 'input_obj,output_toml', [ + ({None: 1}, ''), ({'key': None}, ''), ({'foo': 'bar', 'key1': None}, 'foo = "bar"\n'), ({'key1': None, 'foo': 'bar'}, 'foo = "bar"\n'), @@ -73,7 +74,7 @@ def test_varied_list(): ], ) def test_none_map_value(input_obj, output_toml): - assert rtoml.dumps(input_obj, omit_none=True) == output_toml + assert rtoml.dumps(input_obj, include_none=False) == output_toml @pytest.mark.parametrize( 'input_obj,output_toml', @@ -89,7 +90,7 @@ def test_none_map_value(input_obj, output_toml): ], ) def test_none_values_inside_list(input_obj, output_toml): - assert rtoml.dumps(input_obj, omit_none=True) == output_toml + assert rtoml.dumps(input_obj, include_none=False) == output_toml @pytest.mark.parametrize( 'input_obj,output_toml', @@ -105,4 +106,4 @@ def test_none_values_inside_list(input_obj, output_toml): ], ) def test_none_values_inside_tuple(input_obj, output_toml): - assert rtoml.dumps(input_obj, omit_none=True) == output_toml + assert rtoml.dumps(input_obj, include_none=False) == output_toml From 2aebd0c88fdbde3e2904a9905fa5faf943f1383b Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Wed, 8 Dec 2021 20:49:57 +0300 Subject: [PATCH 12/18] Update README.md --- README.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index e9bf3d9..5701858 100644 --- a/README.md +++ b/README.md @@ -47,22 +47,26 @@ Parse a TOML string and return a python dictionary. (provided to match the inter #### dumps ```python -def dumps(obj: Any, *, pretty: bool = False) -> str: ... +def dumps(obj: Any, *, pretty: bool = False, include_none = True) -> str: ... ``` Serialize a python object to TOML. If `pretty` is true, output has a more "pretty" format. +If `include_none` is false, none objectes are ommited (in dictionaries, tuples and lists as well). + #### dump ```python -def dump(obj: Any, file: Union[Path, TextIO], *, pretty: bool = False) -> int: ... +def dump(obj: Any, file: Union[Path, TextIO], *, pretty: bool = False, include_none = True) -> int: ... ``` Serialize a python object to TOML and write it to a file. `file` may be a `Path` or file object from `open()`. If `pretty` is true, output has a more "pretty" format. +If `include_none` is false, none objectes are ommited (in dictionaries, tuples and lists as well). + ### Example ```py From e7b8dddac60b9e5c02dc0504a3f2294c414631fe Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Wed, 8 Dec 2021 21:22:42 +0300 Subject: [PATCH 13/18] Refactoring --- src/lib.rs | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 673c8aa..7937985 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -93,10 +93,8 @@ impl<'p> Serialize for SerializePyObject<'p> { macro_rules! add_to_map { ($map:ident, $key:ident, $value:ident) => { - if !self.include_none && ($value.is_none() || $key.is_none()) { - // in case of none we don't need to serialize anything - // we just ingore this field. - } else { + let ignore_none_entry = !self.include_none && ($value.is_none() || $key.is_none()); + if !ignore_none_entry { if $key.is_none() { $map.serialize_key("null")?; } else if let Ok(key) = $key.extract::() { @@ -110,6 +108,7 @@ impl<'p> Serialize for SerializePyObject<'p> { $key ))); } + $map.serialize_value(&SerializePyObject { py: self.py, obj: $value, From bf7d47c1547083ef755ecfbc5282bf3b0033a8e4 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 13 May 2022 14:57:43 +0300 Subject: [PATCH 14/18] Update README.md Co-authored-by: Samuel Colvin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 5701858..e0394bc 100644 --- a/README.md +++ b/README.md @@ -54,7 +54,7 @@ Serialize a python object to TOML. If `pretty` is true, output has a more "pretty" format. -If `include_none` is false, none objectes are ommited (in dictionaries, tuples and lists as well). +If `include_none` is `False`, `None` values are omitted from the generated toml string. #### dump ```python From 8402fad4907066c17aacdcba70b1c71e2e293d56 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 13 May 2022 14:58:14 +0300 Subject: [PATCH 15/18] Update rtoml/__init__.py Co-authored-by: Samuel Colvin --- rtoml/__init__.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/rtoml/__init__.py b/rtoml/__init__.py index 3c1fc8a..664b0dc 100644 --- a/rtoml/__init__.py +++ b/rtoml/__init__.py @@ -45,7 +45,7 @@ def dumps(obj: Any, *, pretty: bool = False, include_none: bool = True) -> str: else: serialize = _rtoml.serialize - return serialize(obj, include_none); + return serialize(obj, include_none) def dump(obj: Any, file: Union[Path, TextIO], *, pretty: bool = False, include_none: bool = True) -> int: From c1b81234227aca84f0db7e9e914bd76779143588 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 13 May 2022 15:30:36 +0300 Subject: [PATCH 16/18] Refactoring lib.rs Signed-off-by: Maxim Zhiburt Co-authored-by: Samuel Colvin --- src/lib.rs | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 7937985..03017da 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -2,7 +2,7 @@ extern crate pyo3; use pyo3::exceptions::PyValueError; use pyo3::prelude::*; -use pyo3::types::{PyAny, PyBool, PyDateTime, PyDict, PyFloat, PyList, PyTuple}; +use pyo3::types::{PyAny, PyDateTime, PyDict, PyFloat, PyList, PyTuple}; use pyo3::{create_exception, wrap_pyfunction, PyErrArguments}; use serde::ser::{self, Serialize, SerializeMap, SerializeSeq, Serializer}; use std::str::FromStr; @@ -56,8 +56,7 @@ struct SerializePyObject<'p> { } impl<'p> SerializePyObject<'p> { - fn new(py: Python<'p>, obj: &'p PyObject, include_none: &PyObject) -> PyResult { - let include_none = include_none.extract::<&PyBool>(py)?.is_true(); + fn new(py: Python<'p>, obj: &'p PyObject, include_none: bool) -> PyResult { let obj = obj.extract(py)?; Ok(Self { py, include_none, obj }) @@ -204,8 +203,8 @@ impl<'p> Serialize for SerializePyObject<'p> { } #[pyfunction] -fn serialize(py: Python, obj: PyObject, include_none: PyObject) -> PyResult { - let serializer = SerializePyObject::new(py, &obj, &include_none)?; +fn serialize(py: Python, obj: PyObject, include_none: bool) -> PyResult { + let serializer = SerializePyObject::new(py, &obj, include_none)?; match to_toml_string(&serializer) { Ok(s) => Ok(s), Err(e) => Err(TomlSerializationError::new_err(e.to_string())), @@ -213,8 +212,8 @@ fn serialize(py: Python, obj: PyObject, include_none: PyObject) -> PyResult PyResult { - let serializer = SerializePyObject::new(py, &obj, &include_none)?; +fn serialize_pretty(py: Python, obj: PyObject, include_none: bool) -> PyResult { + let serializer = SerializePyObject::new(py, &obj, include_none)?; match to_toml_string_pretty(&serializer) { Ok(s) => Ok(s), Err(e) => Err(TomlSerializationError::new_err(e.to_string())), From f37a0aa03709e0a15dcf35152943cee62694e011 Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 13 May 2022 15:31:48 +0300 Subject: [PATCH 17/18] Update README.md Signed-off-by: Maxim Zhiburt Co-authored-by: Samuel Colvin --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index e0394bc..305027f 100644 --- a/README.md +++ b/README.md @@ -65,7 +65,7 @@ Serialize a python object to TOML and write it to a file. `file` may be a `Path` If `pretty` is true, output has a more "pretty" format. -If `include_none` is false, none objectes are ommited (in dictionaries, tuples and lists as well). +If `include_none` is `False`, `None` values are omitted from the generated toml string. ### Example From 0dfed8594088490c8a18d9491bd2c090c959022c Mon Sep 17 00:00:00 2001 From: Maxim Zhiburt Date: Fri, 13 May 2022 15:40:23 +0300 Subject: [PATCH 18/18] Fix lint warnings Signed-off-by: Maxim Zhiburt --- tests/test_dump.py | 26 ++++++++++++++------------ 1 file changed, 14 insertions(+), 12 deletions(-) diff --git a/tests/test_dump.py b/tests/test_dump.py index c0efae7..b2336a9 100644 --- a/tests/test_dump.py +++ b/tests/test_dump.py @@ -76,33 +76,35 @@ def test_varied_list(): def test_none_map_value(input_obj, output_toml): assert rtoml.dumps(input_obj, include_none=False) == output_toml + @pytest.mark.parametrize( 'input_obj,output_toml', [ ([None], '[]'), - (["a", None], '["a"]'), - ([None, "b"], '["b"]'), - (["a", None, "b"], '["a", "b"]'), + (['a', None], '["a"]'), + ([None, 'b'], '["b"]'), + (['a', None, 'b'], '["a", "b"]'), ({'foo': 'bar', 'list': [None]}, 'foo = "bar"\nlist = []\n'), - ({'foo': 'bar', 'list': [None, "b"]}, 'foo = "bar"\nlist = ["b"]\n'), - ({'foo': 'bar', 'list': ["a", None]}, 'foo = "bar"\nlist = ["a"]\n'), - ({'foo': 'bar', 'list': ["a", None, "b"]}, 'foo = "bar"\nlist = ["a", "b"]\n'), + ({'foo': 'bar', 'list': [None, 'b']}, 'foo = "bar"\nlist = ["b"]\n'), + ({'foo': 'bar', 'list': ['a', None]}, 'foo = "bar"\nlist = ["a"]\n'), + ({'foo': 'bar', 'list': ['a', None, 'b']}, 'foo = "bar"\nlist = ["a", "b"]\n'), ], ) def test_none_values_inside_list(input_obj, output_toml): assert rtoml.dumps(input_obj, include_none=False) == output_toml + @pytest.mark.parametrize( 'input_obj,output_toml', [ ((None), '"null"'), - (("a", None), '["a"]'), - ((None, "b"), '["b"]'), - (("a", None, "b"), '["a", "b"]'), + (('a', None), '["a"]'), + ((None, 'b'), '["b"]'), + (('a', None, 'b'), '["a", "b"]'), ({'foo': 'bar', 'list': (None)}, 'foo = "bar"\n'), - ({'foo': 'bar', 'list': (None, "b")}, 'foo = "bar"\nlist = ["b"]\n'), - ({'foo': 'bar', 'list': ("a", None)}, 'foo = "bar"\nlist = ["a"]\n'), - ({'foo': 'bar', 'list': ("a", None, "b")}, 'foo = "bar"\nlist = ["a", "b"]\n'), + ({'foo': 'bar', 'list': (None, 'b')}, 'foo = "bar"\nlist = ["b"]\n'), + ({'foo': 'bar', 'list': ('a', None)}, 'foo = "bar"\nlist = ["a"]\n'), + ({'foo': 'bar', 'list': ('a', None, 'b')}, 'foo = "bar"\nlist = ["a", "b"]\n'), ], ) def test_none_values_inside_tuple(input_obj, output_toml):