# Marsh
(Un)marshaling library for objects in Python 3.8+.
Tested on Ubuntu, Windows and Mac OSX for Python 3.8, 3.9 and 3.10.
Relies heavily on type-hint reflection. All (un)marshaling is performed recursively which allows for support of nested types.
Influenced by the work of [Omry Yadan](https://github.com/omry) on [hydra](hydra.cc).
The [documentation](https://marsh.readthedocs.io) is hosted on ReadTheDocs.
## Getting Started
### Install
marsh is available through pip
```shell
pip install marsh
```
### Entry point
Using the unmarshaling capabilities of ``marsh`` we can
create entry points for python code.
These entry points allow for arguments to be given on the command
line and/or through config files which are then validated, converted to the correct types and passed to the entry point function.
Most python types are supported (primitives, type aliases, dataclasses, namedtuple e.t.c.)
#### Create
Creating an entry point is as simple as decorating a function
and calling it without arguments.
```python
# app.py
import marsh
from typing import Sequence, Union
@marsh.main
def run(
a: int,
b: Union[float, Sequence[int]],
c: dict[str, bool],
) -> None:
"""Example of an entry point.
Arguments:
a: An integer argument.
b: A floating point value or a sequence of ints.
c: A dictionary with string keys and
bool values. If this was python 3.8
we would instead use typing.Dict[str, bool] as
type hint as the builtin dict did not support
type annotations.
"""
print(a, type(a))
print(b, type(b))
print(c, type(c))
if __name__ == '__main__':
run()
```
#### Run
When running the application we can use the positional arguments
on the command line to pass values to our function.
```shell
$ python app.py a=1 b=5e-1 c.key1=true c.key2=false
1 <class 'int'>
0.5 <class 'float'>
{'key1': True, 'key2': False} <class 'dict'>
```
#### Argument validation
When giving invalid values or when required arguments are missing an error message is printed and the application exits.
```shell
$ python app.py a=1.5 b=0 c.some_key=true
failed to unmarshal config: int: could not convert: 1.5
path: a
```
```shell
$ python app.py b=0 c.some_key=true
failed to unmarshal config: MissingValueError
path: a
```
#### Help
Using --help we can also get a help message for the arguments. Here the output was piped to `tail` to truncate the output into displaying only the arguments of our entry point.
```shell
$ python app.py --help | tail -n 11
fields:
a: <int> An integer argument.
b: <float> | [<int>, ...]
A floating point value or a sequence of ints.
c: {<str>: <bool>, ...}
A dictionary with string keys and bool values. If this
was python 3.8 we would instead use typing.Dict[str,
bool] as type hint as the builtin dict did not support
type annotations.
```
### Marshal
Marshaling values simply means taking a python object and turning it into JSON-like data.
```python
# marshal.py
import dataclasses
import marsh
@dataclasses.dataclass
class Config:
a: int
b: float
config = Config(1, 5e-1)
print(marsh.marshal(config))
```
```shell
$ python marshal.py
{'a': 1, 'b': 0.5}
```
### Unmarshal
Unmarshaling is the opposite of marshaling. A type is instantiated using JSON-like data.
```python
# unmarshal.py
import dataclasses
import typing
import marsh
class Range(typing.NamedTuple):
start: typing.Optional[int]
stop: int
@dataclasses.dataclass
class Config:
a: int
b: float
c: Range
config = marsh.unmarshal(
Config,
{
'a': 1,
'b': 1.5,
'c': {
'start': None,
'stop': 5,
},
}
)
print(config)
```
```shell
$ python umarshal.py
Config(a=1, b=1.5, c=Range(start=None, stop=5))
```
## License
[MIT License](LICENSE).
Raw data
{
"_id": null,
"home_page": "https://github.com/adriansahlman/marsh",
"name": "marsh",
"maintainer": "",
"docs_url": null,
"requires_python": ">=3.8",
"maintainer_email": "",
"keywords": "marshal unmarshal configuration",
"author": "Adrian Sahlman",
"author_email": "adrian.sahlman@gmail.com",
"download_url": "https://files.pythonhosted.org/packages/6b/53/cd3ff0229f34f5e485b9c9378aaa1df599cb72a4f6c3e669fb0c3fa05bf4/marsh-0.2.6.tar.gz",
"platform": null,
"description": "\n# Marsh\n\n(Un)marshaling library for objects in Python 3.8+.\n\nTested on Ubuntu, Windows and Mac OSX for Python 3.8, 3.9 and 3.10.\n\nRelies heavily on type-hint reflection. All (un)marshaling is performed recursively which allows for support of nested types.\n\nInfluenced by the work of [Omry Yadan](https://github.com/omry) on [hydra](hydra.cc).\n\nThe [documentation](https://marsh.readthedocs.io) is hosted on ReadTheDocs.\n\n## Getting Started\n\n### Install\n\nmarsh is available through pip\n```shell\npip install marsh\n```\n\n### Entry point\n\nUsing the unmarshaling capabilities of ``marsh`` we can\ncreate entry points for python code.\n\nThese entry points allow for arguments to be given on the command\nline and/or through config files which are then validated, converted to the correct types and passed to the entry point function.\n\nMost python types are supported (primitives, type aliases, dataclasses, namedtuple e.t.c.)\n\n#### Create\n\nCreating an entry point is as simple as decorating a function\nand calling it without arguments.\n\n```python\n# app.py\nimport marsh\nfrom typing import Sequence, Union\n\n\n@marsh.main\ndef run(\n a: int,\n b: Union[float, Sequence[int]],\n c: dict[str, bool],\n) -> None:\n \"\"\"Example of an entry point.\n\n Arguments:\n a: An integer argument.\n b: A floating point value or a sequence of ints.\n c: A dictionary with string keys and\n bool values. If this was python 3.8\n we would instead use typing.Dict[str, bool] as\n type hint as the builtin dict did not support\n type annotations.\n \"\"\"\n print(a, type(a))\n print(b, type(b))\n print(c, type(c))\n\n\nif __name__ == '__main__':\n run()\n```\n\n#### Run\n\nWhen running the application we can use the positional arguments\non the command line to pass values to our function.\n\n```shell\n$ python app.py a=1 b=5e-1 c.key1=true c.key2=false\n1 <class 'int'>\n0.5 <class 'float'>\n{'key1': True, 'key2': False} <class 'dict'>\n```\n\n#### Argument validation\n\nWhen giving invalid values or when required arguments are missing an error message is printed and the application exits.\n\n```shell\n$ python app.py a=1.5 b=0 c.some_key=true\nfailed to unmarshal config: int: could not convert: 1.5\n\tpath: a\n```\n```shell\n$ python app.py b=0 c.some_key=true\nfailed to unmarshal config: MissingValueError\n\tpath: a\n```\n\n#### Help\n\nUsing --help we can also get a help message for the arguments. Here the output was piped to `tail` to truncate the output into displaying only the arguments of our entry point.\n```shell\n$ python app.py --help | tail -n 11\nfields:\n a: <int> An integer argument.\n\n b: <float> | [<int>, ...]\n A floating point value or a sequence of ints.\n\n c: {<str>: <bool>, ...}\n A dictionary with string keys and bool values. If this\n was python 3.8 we would instead use typing.Dict[str,\n bool] as type hint as the builtin dict did not support\n type annotations.\n```\n\n\n\n### Marshal\nMarshaling values simply means taking a python object and turning it into JSON-like data.\n\n```python\n# marshal.py\nimport dataclasses\nimport marsh\n\n\n@dataclasses.dataclass\nclass Config:\n a: int\n b: float\n\n\nconfig = Config(1, 5e-1)\nprint(marsh.marshal(config))\n```\n\n```shell\n$ python marshal.py\n{'a': 1, 'b': 0.5}\n```\n\n### Unmarshal\nUnmarshaling is the opposite of marshaling. A type is instantiated using JSON-like data.\n\n```python\n# unmarshal.py\nimport dataclasses\nimport typing\nimport marsh\n\n\nclass Range(typing.NamedTuple):\n start: typing.Optional[int]\n stop: int\n\n\n@dataclasses.dataclass\nclass Config:\n a: int\n b: float\n c: Range\n\n\nconfig = marsh.unmarshal(\n Config,\n {\n 'a': 1,\n 'b': 1.5,\n 'c': {\n 'start': None,\n 'stop': 5,\n },\n }\n)\nprint(config)\n```\n\n```shell\n$ python umarshal.py\nConfig(a=1, b=1.5, c=Range(start=None, stop=5))\n```\n\n## License\n[MIT License](LICENSE).\n\n\n",
"bugtrack_url": null,
"license": "MIT",
"summary": "(Un)marshaling framework.",
"version": "0.2.6",
"split_keywords": [
"marshal",
"unmarshal",
"configuration"
],
"urls": [
{
"comment_text": "",
"digests": {
"blake2b_256": "68d18f9dc2a4df06590f481642420df5da6aecd82770f2623da63c8b48412e94",
"md5": "205a54a40bbd99d76e0b77b5e797ee31",
"sha256": "9b7999d78be426a2b617ce9a87fe04ddf22017db4c07c03666c57b222095abf5"
},
"downloads": -1,
"filename": "marsh-0.2.6-py3-none-any.whl",
"has_sig": false,
"md5_digest": "205a54a40bbd99d76e0b77b5e797ee31",
"packagetype": "bdist_wheel",
"python_version": "py3",
"requires_python": ">=3.8",
"size": 86410,
"upload_time": "2023-01-02T20:53:17",
"upload_time_iso_8601": "2023-01-02T20:53:17.290508Z",
"url": "https://files.pythonhosted.org/packages/68/d1/8f9dc2a4df06590f481642420df5da6aecd82770f2623da63c8b48412e94/marsh-0.2.6-py3-none-any.whl",
"yanked": false,
"yanked_reason": null
},
{
"comment_text": "",
"digests": {
"blake2b_256": "6b53cd3ff0229f34f5e485b9c9378aaa1df599cb72a4f6c3e669fb0c3fa05bf4",
"md5": "2e284672c1a425d7c64c29621e0c3cca",
"sha256": "14c359d12b2a5e5d66cba2ebad460adf84215f22db016354867ccf682bf65e54"
},
"downloads": -1,
"filename": "marsh-0.2.6.tar.gz",
"has_sig": false,
"md5_digest": "2e284672c1a425d7c64c29621e0c3cca",
"packagetype": "sdist",
"python_version": "source",
"requires_python": ">=3.8",
"size": 64193,
"upload_time": "2023-01-02T20:53:18",
"upload_time_iso_8601": "2023-01-02T20:53:18.623986Z",
"url": "https://files.pythonhosted.org/packages/6b/53/cd3ff0229f34f5e485b9c9378aaa1df599cb72a4f6c3e669fb0c3fa05bf4/marsh-0.2.6.tar.gz",
"yanked": false,
"yanked_reason": null
}
],
"upload_time": "2023-01-02 20:53:18",
"github": true,
"gitlab": false,
"bitbucket": false,
"github_user": "adriansahlman",
"github_project": "marsh",
"travis_ci": false,
"coveralls": false,
"github_actions": true,
"requirements": [
{
"name": "typing-extensions",
"specs": [
[
">=",
"4.2"
]
]
},
{
"name": "omegaconf",
"specs": [
[
">=",
"2.1.1"
]
]
},
{
"name": "docstring-parser",
"specs": [
[
">=",
"0.7.3"
]
]
},
{
"name": "dateparser",
"specs": [
[
">=",
"1.1.1"
]
]
},
{
"name": "mypy",
"specs": [
[
"==",
"0.982"
]
]
},
{
"name": "flake8",
"specs": [
[
">=",
"3.8"
]
]
},
{
"name": "add-trailing-comma",
"specs": [
[
">=",
"2.2.2"
]
]
},
{
"name": "pytest",
"specs": [
[
">=",
"6.1.2"
]
]
},
{
"name": "types-PyYAML",
"specs": []
},
{
"name": "types-setuptools",
"specs": []
},
{
"name": "types-typing-extensions",
"specs": [
[
">=",
"3.7.3"
]
]
},
{
"name": "types-dateparser",
"specs": []
}
],
"lcname": "marsh"
}