Skip to content

Commit

Permalink
adding tests with toml-test (#2)
Browse files Browse the repository at this point in the history
* adding tests with toml-test

* fix linting

* more tests

* remove debug

* fix linting

* tweak docs

* uprev
  • Loading branch information
samuelcolvin authored Jan 9, 2020
1 parent 5743c16 commit 17c7bfa
Show file tree
Hide file tree
Showing 14 changed files with 325 additions and 54 deletions.
10 changes: 10 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,16 @@ jobs:
coverage xml
ls -alh
- name: install go
uses: actions/setup-go@v1
with:
go-version: 1.13.x

- name: run toml-test
run: |
go get github.com/BurntSushi/toml-test
~/go/bin/toml-test ./tests/toml_test.py
- uses: codecov/[email protected]
with:
token: ${{ secrets.CODECOV_TOKEN }}
Expand Down
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "rtoml"
version = "0.1.1"
version = "0.2.0"
authors = ["Samuel Colvin <[email protected]>"]
edition = "2018"

Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ test:
pytest --cov=rtoml

.PHONY: testcov
testcov: test
testcov: build test
@echo "building coverage html"
@coverage html

Expand All @@ -48,6 +48,9 @@ clean:
rm -f `find . -type f -name '*.py[co]' `
rm -f `find . -type f -name '*~' `
rm -f `find . -type f -name '.*~' `
rm -rf dist
rm -rf build
rm -rf target
rm -rf .cache
rm -rf .pytest_cache
rm -rf .mypy_cache
Expand Down
63 changes: 55 additions & 8 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,35 +14,82 @@ pip install rtoml

#### load
```python
load(toml: Union[str, Path, TextIO]) -> Any
def load(toml: Union[str, Path, TextIO]) -> Any: ...
```

Parse TOML via a string or file and return a python object. The `toml` argument by be a `str`,
Parse TOML via a string or file and return a python object. The `toml` argument may be a `str`,
`Path` or file object from `open()`.

#### loads
```python
loads(toml: str) -> Any
def loads(toml: str) -> Any: ...
```

Parse a TOML string and return a python object.
Parse a TOML string and return a python object. (provided to match the interface of `json` and similar libraries)

#### dumps
```python
dumps(obj: Any) -> str
def dumps(obj: Any) -> str: ...
```

Serialize a python object to TOML.

#### dump
```python
dump(obj: Any, file: Union[Path, TextIO]) -> int
def dump(obj: Any, file: Union[Path, TextIO]) -> int: ...
```

Serialize a python object to TOML and write it to a file. `file` maybe a `Path` or file object from `open()`.
Serialize a python object to TOML and write it to a file. `file` may be a `Path` or file object from `open()`.

### Example

```py
TODO
from datetime import datetime, timezone, timedelta
import rtoml

obj = {
'title': 'TOML Example',
'owner': {
'dob': datetime(1979, 5, 27, 7, 32, tzinfo=timezone(timedelta(hours=-8))),
'name': 'Tom Preston-Werner',
},
'database': {
'connection_max': 5000,
'enabled': True,
'ports': [8001, 8001, 8002],
'server': '192.168.1.1',
},
}

loaded_obj = rtoml.load("""\
# This is a TOML document.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # First class dates
[database]
server = "192.168.1.1"
ports = [8001, 8001, 8002]
connection_max = 5000
enabled = true
""")

assert loaded_obj == obj

assert rtoml.dumps(obj) == """\
title = "TOML Example"
[owner]
dob = 1979-05-27T07:32:00-08:00
name = "Tom Preston-Werner"
[database]
connection_max = 5000
enabled = true
server = "192.168.1.1"
ports = [8001, 8001, 8002]
"""
```
48 changes: 48 additions & 0 deletions example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
from datetime import datetime, timezone, timedelta
import rtoml

obj = {
'title': 'TOML Example',
'owner': {
'dob': datetime(1979, 5, 27, 7, 32, tzinfo=timezone(timedelta(hours=-8))),
'name': 'Tom Preston-Werner',
},
'database': {
'connection_max': 5000,
'enabled': True,
'ports': [8001, 8001, 8002],
'server': '192.168.1.1',
},
}

loaded_obj = rtoml.load("""\
# This is a TOML document.
title = "TOML Example"
[owner]
name = "Tom Preston-Werner"
dob = 1979-05-27T07:32:00-08:00 # First class dates
[database]
server = "192.168.1.1"
ports = [8001, 8001, 8002]
connection_max = 5000
enabled = true
""")

assert loaded_obj == obj

assert rtoml.dumps(obj) == """\
title = "TOML Example"
[owner]
dob = 1979-05-27T07:32:00-08:00
name = "Tom Preston-Werner"
[database]
connection_max = 5000
enabled = true
server = "192.168.1.1"
ports = [8001, 8001, 8002]
"""
38 changes: 23 additions & 15 deletions rtoml/__init__.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,21 @@
from datetime import datetime, timezone
from datetime import date, datetime, time, timezone
from io import TextIOBase
from pathlib import Path
from typing import Any, TextIO, Union

from . import _rtoml

__all__ = 'VERSION', 'TomlError', 'load', 'loads', 'dumps', 'dump'
__all__ = 'VERSION', 'TomlParsingError', 'TomlSerializationError', 'load', 'loads', 'dumps', 'dump'

# VERSION is set in Cargo.toml
VERSION = _rtoml.VERSION
TomlError = _rtoml.TomlError
TomlParsingError = _rtoml.TomlParsingError
TomlSerializationError = _rtoml.TomlSerializationError


def load(toml: Union[str, Path, TextIO]) -> Any:
"""
Parse TOML via a string or file and return a python object. The `toml` argument by be a `str`,
Parse TOML via a string or file and return a python object. The `toml` argument may be a `str`,
`Path` or file object from `open()`.
"""
if isinstance(toml, Path):
Expand All @@ -27,7 +28,7 @@ def load(toml: Union[str, Path, TextIO]) -> Any:

def loads(toml: str) -> Any:
"""
Parse a TOML string and return a python object.
Parse a TOML string and return a python object. (provided to match the interface of `json` and similar libraries)
"""
if not isinstance(toml, str):
raise TypeError(f'invalid toml input, must be str not {type(toml)}')
Expand All @@ -43,7 +44,7 @@ def dumps(obj: Any) -> str:

def dump(obj: Any, file: Union[Path, TextIO]) -> int:
"""
Serialize a python object to TOML and write it to a file. `file` maybe a `Path` or file object from `open()`.
Serialize a python object to TOML and write it to a file. `file` may be a `Path` or file object from `open()`.
"""
s = dumps(obj)
if isinstance(file, Path):
Expand All @@ -52,12 +53,19 @@ def dump(obj: Any, file: Union[Path, TextIO]) -> int:
return file.write(s)


def parse_datetime(v: str) -> datetime:
tz = None
if v.endswith(('z', 'Z')):
tz = timezone.utc
v = v[:-1]
dt = datetime.fromisoformat(v)
if tz:
dt = dt.replace(tzinfo=tz)
return dt
def parse_datetime(v: str) -> Union[date, time]:
try:
return date.fromisoformat(v)
except ValueError:
tz = None
if v.endswith(('z', 'Z')):
tz = timezone.utc
v = v[:-1]
try:
dt = datetime.fromisoformat(v)
except ValueError:
return time.fromisoformat(v)
else:
if tz:
dt = dt.replace(tzinfo=tz)
return dt
9 changes: 5 additions & 4 deletions rtoml/_rtoml.pyi
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
from datetime import datetime
from typing import Any, Callable
from datetime import date, time
from typing import Any, Callable, Union

VERSION: str

def deserialize(toml: str, parse_datetime: Callable[[str], datetime]) -> Any: ...
def deserialize(toml: str, parse_datetime: Callable[[str], Union[date, time]]) -> Any: ...
def serialize(obj: Any) -> str: ...

class TomlError(ValueError): ...
class TomlParsingError(ValueError): ...
class TomlSerializationError(ValueError): ...
1 change: 1 addition & 0 deletions rustfmt.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
max_width = 120
5 changes: 1 addition & 4 deletions setup.cfg
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,6 @@ max-line-length = 120
max-complexity = 14
inline-quotes = '
multiline-quotes = """
ignore = E203, W503
[bdist_wheel]
python-tag = py36.py37.py38
[coverage:run]
source = rtoml
Expand All @@ -33,6 +29,7 @@ multi_line_output=3
include_trailing_comma=True
force_grid_wrap=0
combine_as_imports=True
skip=tests/toml_test.py
[mypy]
follow_imports = silent
Expand Down
28 changes: 12 additions & 16 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@ use toml::Value::{Array, Boolean, Datetime, Float, Integer, String as TomlString
use toml::{to_string as to_toml_string, Value};

const VERSION: &str = env!("CARGO_PKG_VERSION");
create_exception!(_rtoml, TomlError, ValueError);
create_exception!(_rtoml, TomlParsingError, ValueError);
create_exception!(_rtoml, TomlSerializationError, ValueError);

fn convert_value(t: &Value, py: Python, parse_datetime: &PyObject) -> PyResult<PyObject> {
match t {
Expand All @@ -24,8 +25,8 @@ fn convert_value(t: &Value, py: Python, parse_datetime: &PyObject) -> PyResult<P

Array(array) => {
let mut list: Vec<PyObject> = Vec::with_capacity(array.len());
for (i, value) in array.iter().enumerate() {
list[i] = convert_value(value, py, parse_datetime)?;
for value in array {
list.push(convert_value(value, py, parse_datetime)?)
}
Ok(list.to_object(py))
}
Expand All @@ -41,7 +42,7 @@ fn convert_value(t: &Value, py: Python, parse_datetime: &PyObject) -> PyResult<P
fn deserialize(py: Python, toml: String, parse_datetime: PyObject) -> PyResult<PyObject> {
match toml.parse::<Value>() {
Ok(v) => convert_value(&v, py, &parse_datetime),
Err(e) => Err(TomlError::py_err(e.to_string())),
Err(e) => Err(TomlParsingError::py_err(e.to_string())),
}
}

Expand Down Expand Up @@ -146,12 +147,9 @@ impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> {
to_seq!(&PyTuple);

cast!(|x: &PyDateTime| {
let raw_str: &str = x
.str()
.map_err(debug_py_err)?
.extract()
.map_err(debug_py_err)?;
match toml::value::Datetime::from_str(raw_str) {
let dt_str: &str = x.str().map_err(debug_py_err)?.extract().map_err(debug_py_err)?;
let iso_str = dt_str.replacen("+00:00", "Z", 1);
match toml::value::Datetime::from_str(&iso_str) {
Ok(dt) => dt.serialize(serializer),
Err(e) => Err(ser::Error::custom(format_args!(
"unable to convert datetime string to toml datetime object {:?}",
Expand All @@ -177,10 +175,7 @@ impl<'p, 'a> Serialize for SerializePyObject<'p, 'a> {
"{} is not serializable to TOML: {}",
name, repr,
))),
Err(_) => Err(ser::Error::custom(format_args!(
"{} is not serializable to TOML",
name,
))),
Err(_) => Err(ser::Error::custom(format_args!("{} is not serializable to TOML", name))),
}
}
}
Expand All @@ -193,13 +188,14 @@ fn serialize(py: Python, obj: PyObject) -> PyResult<String> {
};
match to_toml_string(&s) {
Ok(s) => Ok(s),
Err(e) => Err(TomlError::py_err(e.to_string())),
Err(e) => Err(TomlSerializationError::py_err(e.to_string())),
}
}

#[pymodule]
fn _rtoml(py: Python, m: &PyModule) -> PyResult<()> {
m.add("TomlError", py.get_type::<TomlError>())?;
m.add("TomlParsingError", py.get_type::<TomlParsingError>())?;
m.add("TomlSerializationError", py.get_type::<TomlSerializationError>())?;
m.add("VERSION", VERSION)?;
m.add_wrapped(wrap_pyfunction!(deserialize))?;
m.add_wrapped(wrap_pyfunction!(serialize))?;
Expand Down
Loading

0 comments on commit 17c7bfa

Please sign in to comment.