Skip to content

Commit

Permalink
Merge pull request #6 from vict0rsch/requires
Browse files Browse the repository at this point in the history
  • Loading branch information
vict0rsch authored Nov 4, 2021
2 parents 0f17e3b + 98fac59 commit 7838cb3
Show file tree
Hide file tree
Showing 11 changed files with 294 additions and 34 deletions.
67 changes: 65 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ Easily parse arbitrary arguments from the command line without dependencies:
![example code](assets/code.png)
![example code](assets/run.png)

![](https://img.shields.io/badge/coverage-97%25-success)
![](https://img.shields.io/badge/version-0.1.4-informational)
![](https://img.shields.io/badge/coverage-99%25-success)
![](https://img.shields.io/badge/version-0.1.5-informational)
![](https://img.shields.io/badge/python-3.7%2B%20-orange)

```bash
Expand All @@ -25,6 +25,7 @@ pip install minydra
<a href="#minydict"><strong>MinyDict</strong></a>&nbsp;&nbsp;&nbsp;
<a href="#dumpingloading"><strong>Save config</strong></a>&nbsp;&nbsp;&nbsp;
<a href="#strict-mode"><strong>Prevent typos</strong></a>&nbsp;&nbsp;&nbsp;
<a href="#using-default-configurations"><strong>Use default configs</strong></a>&nbsp;&nbsp;&nbsp;
<a href="/examples"><strong>Examples</strong></a>
</p>

Expand Down Expand Up @@ -336,6 +337,68 @@ KeyError: 'Cannot create a non-existing key in strict mode ({"log_leveel":INFO})

<br/>

### Using default configurations

The `minydra.Parser` class takes a `defaults=` keyword argument. This can be:

* a `str` or a `pathlib.Path` to a `json` `yaml` or `pickle` file that `minydra.MinyDict` can load (`from_X`)
* a `dict` or a `minydra.MinyDict`

When `defaults` is provided, the resulting `minydra.MinyDict` serves as a reference for the arguments parsed from the command-line:

* arguments from the command-line have a higher priority but **must** be present in the `defaults` (`defaults.update(args, strict=True)` is used, see [strict mode](#strict-mode))
* arguments not present in the command-line with fallback to values in `defaults`

`defaults` can actually be a `list` and the update order is the same as the list's. For instance:

```python
In [1]: from minydra import Parser

In [2]: Parser(defaults=["./examples/demo.json", "./examples/demo2.json"]).args.pretty_print();
╭─────────────────────────────────╮
│ log │
│ │logger │
│ │ │log_level : INFO
│ │ │logger_name : minydra │
│ │outdir : /some/other/path │
│ │project : demo │
│ new_key : 3
│ verbose : False
╰─────────────────────────────────╯
```

If you need to set defaults from the command-line, there's a special `@defaults` keyword you can use:


```text
$ python examples/decorator.py @defaults=./examples/demo.json
╭──────────────────────────────────────╮
│ @defaults : ./examples/demo.json │
│ log │
│ │logger │
│ │ │log_level : DEBUG │
│ │ │logger_name : minydra │
│ │outdir : /some/path │
│ │project : demo │
│ verbose : False │
╰──────────────────────────────────────╯
$ python examples/decorator.py @defaults="['./examples/demo.json', './examples/demo2.json']"
╭───────────────────────────────────────────────────────────────────╮
│ @defaults : ['./examples/demo.json', './examples/demo2.json'] │
│ log │
│ │logger │
│ │ │log_level : INFO │
│ │ │logger_name : minydra │
│ │outdir : /some/other/path │
│ │project : demo │
│ new_key : 3 │
│ verbose : False │
╰───────────────────────────────────────────────────────────────────╯
```

<br/>

### `pretty_print`

Prints the `MinyDict` in a box, with dicts properly indented. A few arguments:
Expand Down
2 changes: 1 addition & 1 deletion assets/carbon-config.json
Original file line number Diff line number Diff line change
@@ -1 +1 @@
{"paddingVertical":"57px","paddingHorizontal":"55px","backgroundImage":null,"backgroundImageSelection":null,"backgroundMode":"color","backgroundColor":"rgba(255,255,255,1)","dropShadow":true,"dropShadowOffsetY":"21px","dropShadowBlurRadius":"45px","theme":"material","windowTheme":"none","language":"text","fontFamily":"MonoLisa","fontSize":"13.5px","lineHeight":"150%","windowControls":true,"widthAdjustment":false,"lineNumbers":false,"firstLineNumber":1,"exportSize":"4x","watermark":false,"squaredImage":false,"hiddenCharacters":false,"name":"","width":912}
{"paddingVertical":"57px","paddingHorizontal":"55px","backgroundImage":null,"backgroundImageSelection":null,"backgroundMode":"color","backgroundColor":"rgba(255,255,255,1)","dropShadow":true,"dropShadowOffsetY":"21px","dropShadowBlurRadius":"45px","theme":"material","windowTheme":"none","language":"text","fontFamily":"MonoLisa","fontSize":"13.5px","lineHeight":"150%","windowControls":true,"widthAdjustment":false,"lineNumbers":false,"firstLineNumber":1,"exportSize":"4x","watermark":false,"squaredImage":false,"hiddenCharacters":false,"name":"","width":912}
66 changes: 66 additions & 0 deletions examples/Readme.md
Original file line number Diff line number Diff line change
Expand Up @@ -297,6 +297,72 @@ In [6]: d.update({"b": {"e": 2}}).pretty_print() # default is strict=False, allo
Out[6]: {'a': 10, 'b': {'c': 0, 'e': 2}}
```

## Using default configurations

Code: [**`defaults.py`**](defaults.py)

```python
from minydra import resolved_args
if __name__ == "__main__":
args = resolved_args(defaults="demo.yaml")
args.pretty_print()
```

```text
$ python examples/defaults.py
╭──────────────────────────────╮
│ log │
│ │logger │
│ │ │log_level : DEBUG │
│ │ │logger_name : minydra │
│ │outdir : /some/path │
│ │project : demo │
│ verbose : False │
╰──────────────────────────────╯
$ python examples/defaults.py @defaults=examples/demo2.json
╭─────────────────────────────────────╮
│ @defaults : examples/demo2.json │
│ log │
│ │logger │
│ │ │log_level : INFO │
│ │ │logger_name : minydra │
│ │outdir : /some/other/path │
│ new_key : 3 │
│ verbose : False │
╰─────────────────────────────────────╯
$ python examples/defaults.py @defaults="['examples/demo.json', 'examples/demo2.json']"
╭───────────────────────────────────────────────────────────────╮
│ @defaults : ['examples/demo.json', 'examples/demo2.json'] │
│ log │
│ │logger │
│ │ │log_level : INFO │
│ │ │logger_name : minydra │
│ │outdir : /some/other/path │
│ │project : demo │
│ new_key : 3 │
│ verbose : False │
╰───────────────────────────────────────────────────────────────╯
```

```python
In [1]: from minydra import Parser
In [2]: Parser(defaults=["./examples/demo.json", "./examples/demo2.json"]).args.pretty_print();
╭─────────────────────────────────╮
│ log │
│ │logger │
│ │ │log_level : INFO │
│ │ │logger_name : minydra │
│ │outdir : /some/other/path │
│ │project : demo │
│ new_key : 3 │
│ verbose : False │
╰─────────────────────────────────╯
```
## Protected attributes

`MinyDict`'s methods (including the `dict` class's) are protected, they are read-only and you cannot therefore set _attributes_ with there names, like `args.get = 2`. If you do need to have a `get` argument, you can access it through _items_: `args["get"] = 2`.
Expand Down
8 changes: 8 additions & 0 deletions examples/defaults.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
from pathlib import Path

from minydra import resolved_args

if __name__ == "__main__":

args = resolved_args(defaults=Path(__file__).parent / "demo.yaml")
args.pretty_print()
2 changes: 1 addition & 1 deletion examples/demo.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,4 @@
}
},
"verbose": false
}
}
11 changes: 11 additions & 0 deletions examples/demo2.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
{
"log": {
"outdir": "/some/other/path",
"logger": {
"log_level": "INFO",
"logger_name": "minydra"
}
},
"verbose": false,
"new_key": 3
}
6 changes: 5 additions & 1 deletion minydra/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from .dict import MinyDict # noqa: F401
from .parser import Parser

__version__ = "0.1.4"
__version__ = "0.1.5"


def parse_args(
Expand All @@ -10,6 +10,7 @@ def parse_args(
warn_overwrites=True,
parse_env=True,
warn_env=True,
defaults=None,
):
def decorator(function):
def wrapper(*args, **kwargs):
Expand All @@ -19,6 +20,7 @@ def wrapper(*args, **kwargs):
warn_overwrites=warn_overwrites,
parse_env=parse_env,
warn_env=warn_env,
defaults=defaults,
)
result = function(parser.args)
return result
Expand All @@ -34,11 +36,13 @@ def resolved_args(
warn_overwrites=True,
parse_env=True,
warn_env=True,
defaults=None,
):
return Parser(
verbose=verbose,
allow_overwrites=allow_overwrites,
warn_overwrites=warn_overwrites,
parse_env=parse_env,
warn_env=warn_env,
defaults=defaults,
).args.resolve()
51 changes: 49 additions & 2 deletions minydra/parser.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,13 @@
import ast
import os
import pathlib
import re
import sys
from typing import Any, List, Optional
from typing import Any, List, Optional, Union

from minydra.dict import MinyDict
from minydra.exceptions import MinydraWrongArgumentException
from minydra.utils import resolve_path


class Parser:
Expand All @@ -32,6 +34,7 @@ def __init__(
warn_overwrites=True,
parse_env=True,
warn_env=True,
defaults=None,
) -> None:
"""
Create a Minydra Parser to parse arbitrary commandline argument as:
Expand All @@ -51,6 +54,9 @@ def __init__(
as key or value in the command line. Defaults to True.
warn_env (bool, optional): Wether to print a warning in case an environment
variable is parsed but no value is found. Defaults to True.
defaults (Union[str, dict, MinyDict], optional): The set of allowed keys as
a (Miny)dict or a path to a file that `minydra.MinyDict` will be able to
load (as `json`, `pickle` or `yaml`)
"""
super().__init__()

Expand All @@ -64,6 +70,48 @@ def __init__(
self._print("sys.argv:", self._argv)

self._parse_args()
if defaults is not None or self.args["@defaults"]:
default = self.load_defaults(self.args["@defaults"] or defaults)
args = self.args.deepcopy().resolve()
args_defaults = args["@defaults"]
if args["@defaults"]:
del args["@defaults"]
self.args = default.update(args, strict=True)
if args_defaults:
self.args["@defaults"] = args_defaults

@staticmethod
def load_defaults(default: Union[str, dict, MinyDict]):
"""
Set the default keys.
Args:
allow (Union[str, dict, MinyDict]): The set of allowed keys as a
(Miny)dict or a path to a file that `minydra.MinyDict` will be able to
load (as `json`, `pickle` or `yaml`)
"""
if isinstance(default, (str, pathlib.Path)):
default = resolve_path(default)
assert default.exists()
assert default.is_file()
if default.suffix not in {".json", ".yaml", ".yml", ".pickle", ".pkl"}:
raise ValueError(f"{str(default)} is not a valid file extension.")
if default.suffix in {".yaml", ".yml"}:
default = MinyDict.from_yaml(default)
elif default.suffix in {".pickle", ".pkl"}:
default = MinyDict.from_pickle(default)
else:
default = MinyDict.from_json(default)
elif isinstance(default, dict):
default = MinyDict(default).resolve()
elif isinstance(default, list):
defaults = [Parser.load_defaults(d) for d in default]
default = MinyDict()
for d in defaults:
default.update(d, strict=False)

assert isinstance(default, MinyDict)
return default

def _print(self, *args, **kwargs):
if self.verbose > 0:
Expand Down Expand Up @@ -177,7 +225,6 @@ def _force_type(value: str, type_str: str) -> Any:
return float(value)
if type_str == "str":
return str(value)
return value

@staticmethod
def _infer_arg_type(arg: Any, type_str: Optional[str] = None) -> Any:
Expand Down
2 changes: 1 addition & 1 deletion requirements-test.txt
Original file line number Diff line number Diff line change
Expand Up @@ -4,4 +4,4 @@ isort
pre-commit
pytest
pytest-cov
tox
tox
Loading

0 comments on commit 7838cb3

Please sign in to comment.