Easily parse arbitrary arguments from the command line without dependencies
Project description
minydra ๐ฆ
Minimal Python command-line parser inspired by Facebook's Hydra + dot-accessible dictionary.
Easily parse arbitrary arguments from the command line without dependencies:
pip install minydra
minydra
is tested on Python 3.7
, 3.8
and 3.9
.
Usage
from minydra.parser import Parser
if __name__ == "__main__":
parser = Parser(
verbose=0, # print received args
allow_overwrites=False, # allow repeating args in the command-line
warn_overwrites=True, # warn repeating args if they are allowed
parse_env=True, # get environment variable
warn_env=True, # warn if an environment variable is specified but not found
)
args = parser.args.pretty_print().resolve().pretty_print() # notice .resolve() transforms dotted.keys into nested dicts
from minydra import resolved_args
if __name__ == "__main__":
args = resolved_args()
args.pretty_print()
examples/demo.py
examples/demo.json
from minydra import resolved_args, MinyDict
if __name__ == "__main__":
# parse arbitrary args in 1 line
args = resolved_args()
# override default conf
if args.default:
args = MinyDict.from_json(args.default).update(args)
# print the args in a nice orderly fashion
args.pretty_print()
# access args with dot/attribute access
print(f'Using project "{args.log.project}" in {args.log.outdir}')
import minydra
from minydra.dict import MinyDict
@minydra.parse_args(verbose=0, allow_overwrites=False) # Parser's init args work here
def main(args: MinyDict) -> None:
args.resolve().pretty_print()
if __name__ == "__main__":
main()
Parsing
- Simple strings are parsed to
float
andint
automatically. - A single keyword will be interpreted as a positive flag.
- A single keyword starting with
-
will be interpreted as a negative flag. - If
parse_env
isTrue
, environment variables are evaluated.
$ python decorator.py outdir=$HOME/project save -log learning_rate=1e-4 batch_size=64
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ batch_size : 64 โ
โ learning_rate : 0.0001 โ
โ log : False โ
โ outdir : /Users/victor/project โ
โ save : True โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
- dotted keys will be resolved to nested dictionary keys:
$ python decorator.py server.conf.port=8000
โญโโโโโโโโโโโโโโโโโโโโโโฎ
โ server โ
โ conf โ
โ port : 8000 โ
โฐโโโโโโโโโโโโโโโโโโโโโโฏ
- Using
ast.literal_eval(value)
,minydra
will try and parse more complex values for arguments as lists or dicts. Those should be specified as strings:
$ python examples/decorator.py layers="[1, 2, 3]" norms="{'conv': 'batch', 'epsilon': 1e-3}"
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ layers : [1, 2, 3] โ
โ norms : {'conv': 'batch', 'epsilon': 0.001} โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Forcing types
Adding ___<type>
to a key will force this type to the value. Notice how 01
is parsed to an integer 1
but 04
is parsed to a string (as specified) "04"
, and hello
is parsed to a list
, not kept as a string
$ python examples/decorator.py n_jobs___str=04 job=01 chips___list=hello
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ chips : ['h', 'e', 'l', 'l', 'o'] โ
โ job : 1 โ
โ n_jobs : 04 โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Known types are defined in Parser.known_types
and the separator (___
) in Parser.type_separator
In [1]: from minydra import Parser
In [2]: Parser.known_types
Out[2]: {'bool', 'dict', 'float', 'int', 'list', 'set', 'str'}
MinyDict
Minydra's args are a custom lightweight wrapper around native dict
which allows for dot access (args.key
), resolving dotted keys into nested dicts and pretty printing sorted keys in a box with nested dicts indented. If a key does not exist, it will not fail, rather return None (as dict.get(key, None)
).
a MinyDict
inherits from dict
so usual methods work .keys()
, .items()
etc.
In [1]: from minydra.dict import MinyDict
In [2]: args = MinyDict({"foo": "bar", "yes.no.maybe": "idontknow"}).pretty_print(); args
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ foo : bar โ
โ yes.no.maybe : idontknow โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Out[2]: {'foo': 'bar', 'yes.no.maybe': 'idontknow'}
In [3]: args.resolve().pretty_print(); args
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ foo : bar โ
โ yes โ
โ no โ
โ maybe : idontknow โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Out[3]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}
In [4]: args.yes.no.maybe
Out[4]: "idontknow"
In [5]: "foo" in args
Out[5]: True
In [6]: "rick" in args
Out[6]: False
In [7]: args.morty is None
Out[7]: True
In [8]: args.items()
Out[8]: dict_items([('foo', 'bar'), ('yes', {'no': {'maybe': 'idontknow'}})])
Dumping/Loading
You can save and read MinyDict
to/from disk in two formats: json
and pickle
.
Both to_pickle
and to_json
have 3 arguments:
file_path
as astr
orpathlib.Path
which is resolved:- expand env variable (
$MYDIR
for instance) - expand user (
~
) - make absolute
- expand env variable (
return_path
which defaults toTrue
. IfTrue
to_json
andto_pickle
return the path of the created objectallow_overwrites
which defaults toTrue
. IfFalse
andpath
exists, aFileExistsError
will be raised. Otherwise creates/overwrites the file atfile_path
verbose
which defaults to0
. If>0
prints the path of the created object
json
Warning: the json
standard does not accept ints as keys in dictionaries so {3: 2}
would be dumped -- and therefore loaded -- as {"3": 2}
.
In [1]: from minydra.dict import MinyDict
In [2]: args = MinyDict({"foo": "bar", "yes.no.maybe": "idontknow"}).resolve(); args
Out[2]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}
In [3]: args.to_json("./opts.json")
In [4]: args.to_json("./opts.json", verbose=1)
Json dumped to: /Users/victor/Documents/Github/vict0rsch/minydra/opts.json
In [5]: MinyDict.from_json("opts.json")
Out[5]: {'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}
pickle
In [1]: from minydra.dict import MinyDict
In [2]: args = MinyDict({"foo": "bar", "yes.no.maybe": "idontknow"}).resolve()
In [3]: print(args)
{'foo': 'bar', 'yes': {'no': {'maybe': 'idontknow'}}}
In [4]: assert args == MinyDict.from_pickle(args.to_pickle("opts.pkl"))
python examples/dumps.py path="./myargs.pkl" format=pickle cleanup
โญโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฎ
โ cleanup : True โ
โ format : pickle โ
โ path : ./myargs.pkl โ
โฐโโโโโโโโโโโโโโโโโโโโโโโโโโโโโฏ
Dumped args to /Users/victor/Documents/Github/vict0rsch/minydra/myargs.pkl
Cleaning up
pretty_print
Prints the Minydict
in a box, with dicts properly indented. A few arguments:
indents
, which defaults to2
: the amount of indentation for nested dictionariessort_keys
, which defaults toTrue
: whether or not to alphabetically sort the keys before printing
to_dict
To produce a native Python dict
, use args.to_dict()
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
.
Try with examples/protected.py
:
python examples/protected.py server.conf.port=8000 get=3
โญโโโโโโโโโโโโโโโโโโโโโโฎ
โ get : 3 โ
โ server โ
โ conf โ
โ port : 8000 โ
โฐโโโโโโโโโโโโโโโโโโโโโโฏ
<built-in method get of MinyDict object at 0x100ccd4a0>
3
dict_items([('get', 3), ('server', {'conf': {'port': 8000}})])
{'conf': {'port': 8000}}
Project details
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distributions
Built Distribution
File details
Details for the file minydra-0.1.3-py3-none-any.whl
.
File metadata
- Download URL: minydra-0.1.3-py3-none-any.whl
- Upload date:
- Size: 13.0 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/3.4.1 importlib_metadata/4.5.0 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.61.0 CPython/3.9.2
File hashes
Algorithm | Hash digest | |
---|---|---|
SHA256 | a03bae650b49d4c7917d3e996c66f5d09d4c1adba379d80758d8a14c30bd2581 |
|
MD5 | 1ee7b6ceb7beef8212a5f7170b1de18e |
|
BLAKE2b-256 | d8a6fc3b072b9b932a032aa14ffe8b74e550230a651bcfc726a8edf67814c5f3 |