There are two ways of using jsonargparse. One is to build a parser step by step (see :ref:`parsers`), which is almost a drop-in replacement of argparse. But argparse is verbose and duplicates information that the code already has. The simpler and recommended way is the :func:`.auto_cli` function, which builds the parser from the signatures of the given functions and classes. For example:
.. testcode::
from jsonargparse import auto_cli
def command(name: str, prize: int = 100):
"""Prints the prize won by a person.
Args:
name: Name of winner.
prize: Amount won.
"""
print(f"{name} won {prize}€!")
if __name__ == "__main__":
auto_cli(command)
The name and prize parameters have type hints and are described in the
docstring. Both are shown in the help. In a shell:
$ python example.py --help
...
Prints the prize won by a person:
name Name of winner. (required, type: str)
--prize PRIZE Amount won. (type: int, default: 100)
$ python example.py Lucky --prize=1000
Lucky won 1000€!Note
Parsing of docstrings is optional. For the help to show the descriptions,
install jsonargparse with the docstrings extra, see :ref:`installation`.
Given a single class, the first arguments are the class init parameters, then comes a method name (methods become :ref:`sub-commands`), and then the parameters of that method:
.. testcode::
from random import randint
from jsonargparse import auto_cli
class Main:
def __init__(self, max_prize: int = 100):
"""
Args:
max_prize: Maximum prize that can be awarded.
"""
self.max_prize = max_prize
def person(self, name: str):
"""
Args:
name: Name of winner.
"""
return f"{name} won {randint(0, self.max_prize)}€!"
if __name__ == "__main__":
print(auto_cli(Main))
In a shell:
$ python example.py --max_prize=1000 person Lucky
Lucky won 632€!>>> auto_cli(Main, args=["--max_prize=1000", "person", "Lucky"]) # doctest: +ELLIPSIS
'Lucky won ...€!'If the class has no public methods, there are no subcommands and :func:`.auto_cli` returns an instance of the class:
.. testcode::
from dataclasses import dataclass
from jsonargparse import auto_cli
@dataclass
class Settings:
name: str
prize: int = 100
if __name__ == "__main__":
print(auto_cli(Settings, as_positional=False))
In a shell:
$ python example.py --name=Lucky
Settings(name='Lucky', prize=100)>>> auto_cli(Settings, as_positional=False, args=["--name=Lucky"]) # doctest: +ELLIPSIS
Settings(name='Lucky', prize=100)Note the as_positional=False, which makes required arguments non-positional.
To get an instance even when the class does have public methods, use
return_instance=True. Then only the init arguments are parsed and no method
subcommands are added.
If several functions are given, each one becomes a subcommand, i.e. example.py
function [arguments]. If several classes are given, or a mix of classes and
functions, running a method needs two levels of subcommands, i.e. example.py
class [init_arguments] method [arguments].
A dict defines subcommands with custom names and any number of levels:
.. testcode::
class Raffle:
def __init__(self, prize: int):
self.prize = prize
def __call__(self, name: str):
return f"{name} won {self.prize}€!"
components = {
"weekday": {
"_help": "Raffles for weekdays",
"tier1": Raffle(prize=100),
"tier2": Raffle(prize=50),
},
"weekend": {
"_help": "Raffles for weekends",
"tier1": Raffle(prize=300),
"tier2": Raffle(prize=75),
},
}
if __name__ == "__main__":
print(auto_cli(components))
In a shell:
$ python example.py weekend tier1 Lucky
Lucky won 300€!>>> auto_cli(components, args=["weekend", "tier1", "Lucky"])
'Lucky won 300€!'Note
These examples only use str and int type hints. jsonargparse
supports a much wider range of types, see :ref:`type-hints`. Classes can
also be used as type hints, which makes configurable dependency injection
(object composition)
easy, see :ref:`sub-classes`.
Tools created with :func:`.auto_cli` have a --config option to give settings
in a config file (see :ref:`configuration-files`). This helps when there are
many parameters. The --print_config option prints all supported settings
with their default values, which is a good starting point:
# Dump default config to have as reference
python example.py --print_config > config.yaml
# Modify the config as needed (all default settings can be removed)
nano config.yaml
# Run the tool using the adapted config
python example.py --config config.yamlA parser is created just like with Python's argparse: import the module, create a parser and add arguments to it.
.. testcode::
from jsonargparse import ArgumentParser
parser = ArgumentParser(prog="app", description="Description for my app.")
parser.add_argument("--opt1", type=int, default=0, help="Help for option 1.")
parser.add_argument("--opt2", type=float, default=1.0, help="Help for option 2.")
:meth:`parse_args <.ArgumentParser.parse_args>` returns an object with the parsed values, or the defaults, as attributes. In the examples a list of arguments is given to it, instead of taking them from the command line:
>>> cfg = parser.parse_args(["--opt2", "2.3"])
>>> cfg.opt1, type(cfg.opt1)
(0, <class 'int'>)
>>> cfg.opt2, type(cfg.opt2)
(2.3, <class 'float'>)If parsing fails, by default the usage is printed and the program exits. With
exit_on_error=False an :class:`.ArgumentError` is raised instead. When the
failure is due to a given value, the error message says where it came from, e.g.
Source: config file config.yaml:3 followed by that line of the file.
Parsed values can come from several sources: the source code, command line arguments, :ref:`configuration-files` and :ref:`environment-variables`. Later sources in the following list override earlier ones:
- Defaults defined in the source code.
- Existing default config files in the order defined in
default_config_files, e.g.~/.config/myapp.yaml. - Full config environment variable, e.g.
APP_CONFIG. - Individual key environment variables, e.g.
APP_OPT1. - Command line arguments in order left to right (might include config files).
Some of these sources might not apply, depending on the parse method used (see
:class:`.ArgumentParser`) and how the parser was built. Environment variables
must be enabled explicitly, except when using :meth:`parse_env
<.ArgumentParser.parse_env>`. Without an action="config" argument there is
no full config environment variable and no way to give a config file from the
command line.
A common pattern is a single function that builds a parser, possibly depending on some parameters, and then parses:
.. testcode::
from jsonargparse import ArgumentParser
def main_cli():
parser = ArgumentParser()
...
cfg = parser.parse_args()
...
if __name__ == "__main__":
main_cli()
Sometimes the parser object is needed without parsing. For instance sphinx-argparse includes the help of CLIs in generated documentation, and requires a function that returns the parser. :func:`.capture_parser` provides it:
.. testcode::
from jsonargparse import capture_parser
def get_parser():
return capture_parser(main_cli)
Note
For tools based on :func:`.auto_cli`, the way to get the parser is :func:`.auto_parser`, a shorthand that calls :func:`.capture_parser`.
Optional arguments can be accepted both by name, e.g. --key=val, and as
positional, e.g. val. Enable this with
set_parsing_settings(parse_optionals_as_positionals=True). Key points:
- Only optionals that take exactly one value qualify, i.e. no
nargsornargs=1. - Optionals with subclass types are excluded.
- Extra positional values are assigned after the real positionals, in the order in which the optionals were added to the parser. The usage in the help shows which optionals accept this and in which order.
- In a parser with subcommands, only the subparsers support this, after the subcommand name(s) are given.
For instance, for a parser defined as:
.. testcode::
from jsonargparse import set_parsing_settings
set_parsing_settings(parse_optionals_as_positionals=True)
parser.add_argument("p1")
parser.add_argument("--o2")
parser.add_argument("--o3")
the help shows p1 [o2 [o3]] and a note saying that the feature is enabled.
Giving values by name still works, e.g. --o2=val2 --o3=val3 val1. Also valid
are --o3=val3 val1 val2 and val1 val2 val3.
Note
Positionals take precedence. If a value is given both ways, the positional
one is used, no matter the order. With the parser above, val1 val2a
--o2=val2b gives o2=val2a.
The :class:`.ActionFail` action adds an argument that always fails when given. A use case is a feature that is only available if some package is installed:
.. testsetup:: always-fail
parser = ArgumentParser()
some_package_installed = False
.. testcode:: always-fail
from jsonargparse import ActionFail
if some_package_installed:
parser.add_argument("--module", type=SomeClass)
else:
parser.add_argument(
"--module",
action=ActionFail(message="install 'package' to enable %(option)s"),
help="Option unavailable due to missing 'package'",
)
Then giving --module=..., or a nested form like --module.child=...,
fails with the configured message. The message accepts the %(option)s and
%(value)s placeholders.
By default jsonargparse follows argparse: an argument that is not given gets the
value None. This makes it impossible to tell apart an argument that was
explicitly set to None, e.g. --opt=null, from one that was simply not
given.
set_parsing_settings(unset_sentinel=True) solves this by using the
:obj:`.Unset` sentinel as the default of the arguments that were not given a
default. An argument then has three possible states:
- :obj:`.Unset` – not given, and
add_argumentreceived nodefault. None– either explicitly set tonull, oradd_argumentreceiveddefault=None.- Any other value – the given value, or the default.
Example:
.. testcode:: unset-values
from jsonargparse import ArgumentParser, Unset, set_parsing_settings
set_parsing_settings(unset_sentinel=True)
parser = ArgumentParser()
parser.add_argument("--num", type=int | None) # no default given
parser.add_argument("--flag", type=int | None, default=None) # explicit None
cfg = parser.parse_args([])
assert cfg.num is Unset # no default → Unset
assert cfg.flag is None # explicit default=None → None
cfg = parser.parse_args(["--num=null"])
assert cfg.num is None # explicitly set to null
cfg = parser.parse_args(["--num=5"])
assert cfg.num == 5 # provided value
.. testcleanup:: unset-values
set_parsing_settings(unset_sentinel=False)
The skip_unset parameter of :meth:`dump <.ArgumentParser.dump>`, :meth:`save
<.ArgumentParser.save>` and :meth:`validate <.ArgumentParser.validate>` decides
whether :obj:`.Unset` entries are excluded, and defaults to True. From the
command line the same is done with --print_config=skip_unset.
Relation to argument_default=SUPPRESS
Argparse's argument_default=SUPPRESS, and the per-argument
default=SUPPRESS, are complementary: an argument that is not given is
completely absent from the namespace, i.e. it has no key at all. The two
features work well together and express different levels of absence.
jsonargparse supports a wide range of argument types and validates values
against them, using Python's type hint syntax. For example, an argument that
accepts None, a float in the range (0, 1), or a positive int:
.. testcode::
from jsonargparse.typing import PositiveInt, OpenUnitInterval
parser.add_argument("--op", type=PositiveInt | OpenUnitInterval | None)
The types in :py:mod:`jsonargparse.typing` are a convenience for cases that standard Python does not cover. Using them is not required.
Types can be nested with any complexity. Notes about the support:
- Nested types, i.e. child types inside
list,dict, etc., work as long as at least one child type is supported. There is no limit in nesting depth. - Supported PEPs: 563 postponed
evaluation (
from __future__ import annotations), 585 (list[<type>]instead ofList[<type>]) and 604 (<type> | <type>instead ofUnion[<type>, <type>]). - Types that use components imported inside
TYPE_CHECKINGblocks work, and so do forward references, including names defined only in the body of the class that owns the method, e.g. a nested class referred to without qualifying it. - Fully supported types are:
str/LiteralString,bool(see :ref:`boolean-arguments`),int,float,Decimal,complex,bytes/bytearray(Base64 encoding),range,list(see :ref:`list-append`),Deque,Iterable,Sequence,MutableSequence,Collection,Container,Reversible,Any/object,Union/Optional(see :ref:`union-types`),Literal,Type,Enum,PathLike,UUID,Fraction,re.Pattern(str only),datetime/date/time(ISO 8601),timedelta, the restricted types of :ref:`restricted-numbers` and :ref:`restricted-strings`, and the path and URL types of :ref:`parsing-paths` and :ref:`parsing-urls`. dict,Mapping,MutableMapping,MappingProxyType,OrderedDictandTypedDictare supported, but only withstrorintkeys, see :ref:`dict-items`.TypedDictacceptsRequiredandNotRequiredto mark single keys as required or optional,ReadOnly(PEP 705) which only marks a key as not mutable and thus changes neither its type nor its requiredness, andUnpackto type**kwargsprecisely, see PEP 692. Undeclared keys are rejected, unless givenextra_items(PEP 728). A--*.helpoption, e.g.--data.help, shows the accepted keys. It takes no value, unless theTypedDictis in a union with other types that have their own help, in which case the value is the name of the typed dict, e.g.--data.help SomeTypedDict. :meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` also accepts aTypedDict, adding one argument per key and giving the corresponding dict on :meth:`instantiate <.ArgumentParser.instantiate>`. As the argument oftype, e.g.type[SomeTypedDict], the value is an import path to a class. SinceTypedDictclasses don't supportissubclass, the given class is accepted when it is structurally compatible, as specified in PEP 589, i.e. it has all the expected keys with the same types and requiredness. A genericTypedDictworks both unsubscripted and subscripted, e.g.SomeDictandSomeDict[int], as does one that inherits from a subscripted one. Subscripting doesn't change which keys are accepted, only the types of the keys annotated with aTypeVar. A key whose type can't be validated accepts any value, see :ref:`unvalidated-types`.tuple,set,frozenset,AbstractSetandMutableSetare supported, even though on the command line, in config files and in environment variables they are all written as an array, like alist. Eachtupleposition can have its own type, which is validated as such, andtuple[type, ...]is also accepted. Asetorfrozensetof a class type is kept as a list when parsing, since subclass specs are not hashable, and becomes a set on :meth:`instantiate <.ArgumentParser.instantiate>`.NamedTupleis supported. The value is either an object with the fields as keys or an array of positional values, fields with a default can be omitted, and parsing gives an instance of the named tuple. It is always dumped as an object, so that the fields are named. A--*.helpoption shows the accepted fields, and :meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` accepts aNamedTuple, both the same as for aTypedDict. A genericNamedTupleworks unsubscripted and subscripted, e.g.SomeTuple[int]. A field of an untypedcollections.namedtupleaccepts any value.Noneis written asnull, as JSON/YAML define it. For the same reason the help showsNoneTypeasnull, e.g. a parameter with type and defaultOptional[str] = Noneis shown astype: Union[str, null], default: null.- Normal classes can be used as a type. The value is a dict with a
class_pathand optionallyinit_args, and :meth:`instantiate <.ArgumentParser.instantiate>` instantiates all classes in a config object, see :ref:`sub-classes`. Protocoltypes work the same as subclasses and don't need to beruntime_checkable. An accepted class must implement all public methods of the protocol with a compatible signature, i.e. be callable in every way that the protocol's methods can be called, like static type checkers verify. Parameter and return types must match exactly, subtypes are not accepted, except where the protocol has no annotation orAny, which accept any type. A generic protocol works both unsubscripted and subscripted, e.g.ProtoandProto[int]. Subscripting substitutes the type arguments in the protocol's methods, soProto[int]andProto[str]accept different implementations. ATypeVarthat remains, in the protocol or in the implementation, matches any type, as static type checkers do. A protocol whose only method is__call__is also implemented by a function with a compatible signature, in which case the value is the function itself, instead of a class to instantiate.dataclasses, final classes, attrs'define, pydantic'sdataclassand pydantic'sBaseModelare supported, even when nested. By default they don't accept subclasses, see :ref:`subclasses-disabled` and :ref:`enable-disable-subclasses`. A dataclass that also inherits from a normal class does accept subclasses by default. A pydantic model configured withextraas"allow"or"ignore"accepts keys not in its signature, which are forwarded to the model on instantiation.- User-defined
Generictypes are supported, see :ref:`generic-types`. Annotatedtypes are supported. If the metadata is a pydantic type, it is used for validation.pydantic.SecretStris supported and, as expected, the actual value is not serialized.jsonargparse.typing.SecretStrgives the same behavior without the pydantic dependency. Dumps only have the mask**********, and parsing this mask as a secret fails, so that a config bootstrapped with--print_configis not used with the mask as the secret. In a union these types also keep the secret from being fetched as a path, see :ref:`parsing-urls`.pydantic.FilePathandpydantic.DirectoryPathrun the corresponding pydantic validation when parsing. Arguments with these types also get file and directory tab completions, see :ref:`tab-completion`.Callableaccepts either a dot import path to a callable object, or a dict withclass_pathand optionallyinit_args. The named class must either instantiate into a callable or be a subclass of the callable's return type. :meth:`instantiate <.ArgumentParser.instantiate>` then gives the instance or a function that returns it, see :ref:`callable-type`. A function given by import path must have a return annotation, or a return type in a stub file (see :ref:`stubs-resolver`), that is the callable's return type or a subclass of it. Argument types are not validated.types.ModuleTypeaccepts the dot import path of a module, or a module object which is normalized to its import path, and oninstantiateis replaced by the imported module object.types.UnionTypeandtypes.GenericAlias, commonly found in third party libraries in unions such astype | UnionType | dict, accept a string with a type expression, e.g."int | str"or"list[int]". The expression is resolved without evaluating code, so its names must be builtins,typingnames or dot import paths.TypeAliasTypeis supported. Values are parsed as the aliased type and the help shows the alias as the argument type. This includes aliases defined with the PEP 695type X = ...statement (Python 3.12+) and aliases created withtyping_extensions.TypeAliasType, also recursive ones or with a string value. A generic alias, e.g.type X[T] = list[T], is parsed as its target with the type parameters substituted by what it is subscripted with, e.g.X[int]behaves aslist[int]. Unsubscripted, its type parameters stand for their default, constraints or bound, the same as any otherTypeVar.NewTypeis supported. Values are parsed as the supertype it stands for, including aNewTypeof aNewType, and the help shows the name given in the source code.- PEP 661 sentinels, e.g.
MISSING = Sentinel("MISSING")fromtyping_extensions, are supported as types, e.g.int | MISSING. The sentinel only accepts itself, given as its import path.
A value for an argument with a Union type is validated against each subtype,
one at a time, and the first subtype that accepts it decides the parsed value.
So the order of the subtypes matters. For example, for Union[str, int] the
command line value 2 is parsed as the str "2", since any command
line value is a valid str, whereas for Union[int, str] it is parsed as
the int 2.
Subtypes are mostly attempted in the order in which they are written. The exception are the ones that accept anything, which are moved to the end when the argument is added, so that the subtypes that do validate get a chance. From first to last attempted, the groups are:
- All types not mentioned below, in the order in which they are written.
None, which only acceptsnull. It is placed second to last so thatOptional[<type>]reads in the help as it does in the source code.Any,objectand the types that can't be validated, see :ref:`unvalidated-types`. These accept any value, so a subtype after them would never be attempted.
The sorting is stable, so subtypes in the same group keep their relative order.
Unions nested inside other types are sorted as well, e.g. the Union in
list[Union[int, Any]].
Be aware that typing considers two unions equal no matter the order of the
subtypes, and caches the types that it creates. So for a union nested in a
typing type, e.g. typing.List[Union[int, str]], the order can end up
being the one of an equal union created earlier somewhere else. PEP 585 types are not cached, so
list[Union[int, str]] always keeps the order as written.
The sorting happens when the argument is added, so the type shown in --help
is the sorted one. That is, the help always tells in which order the subtypes
are attempted. For example, an argument added as:
.. testsetup:: union
from typing import Any, Union
parser = ArgumentParser(exit_on_error=False)
.. testcode:: union
parser.add_argument("--val", type=Union[Any, int, None])
is shown in the help as (type: Union[int, null, Any], default: null) and
parses values as:
>>> parser.parse_args(["--val=2"])
Namespace(val=2)
>>> parser.parse_args(["--val=null"])
Namespace(val=None)
>>> parser.parse_args(["--val=abc"])
Namespace(val='abc')In one case the order changes while parsing instead of when the argument is
added: when appending to a list, see :ref:`list-append`, the subtypes that are a
list are moved to the front. This can only be decided when parsing, since it
depends on whether the value is appended to a previous list or replaces it. For
an argument of type Union[int, list[int]], --val=1 gives 1, while
--val+=1 gives [1].
A :ref:`signature parameter <classes-methods-functions>`, a TypedDict key or
a NamedTuple field can have a type that jsonargparse can't validate. The
argument is still added, with only the parts of the type that can't be validated
replaced by a type that accepts any value. The help shows these parts as
Unvalidated<...>, keeping the name used in the source code. For example, a
class with a parameter items: list[SomeType] = [] for which SomeType
can't be validated is shown in the help as:
--myclass.items ITEMS (type: list[Unvalidated<SomeType>], default: [])
Only these parts accept any value: in the example the value must still be a
list, and in a Union the other subtypes are still validated. A type or a
part of it can't be validated when:
- It failed to resolve, e.g. a missing import or a typo in a postponed annotation.
- It is not a type that jsonargparse supports, e.g. a
TypeVarthat stands for nothing, see :ref:`generic-types`.
The debug log gives the reason for each part, see :ref:`logging`. A parameter
without a type annotation is shown as Untyped and behaves the same, see
:ref:`classes-methods-functions`.
Since there is no type to serialize with, :meth:`dump <.ArgumentParser.dump>`
and --print_config derive a type from the value itself. A value of a type
that jsonargparse doesn't support, e.g. an arbitrary object, is serialized like
the instances given for a :ref:`subclass type <sub-classes>`: as an import path
when it can be imported back, and otherwise as a message saying that it was not
serializable, together with a warning.
Parsing a dump back has no type to validate with either, so only the values that
the config formats represent round-trip, e.g. a set is dumped and parsed
back as a list, and an Enum member as its name. A warning is raised for each
dumped value that loses its type this way. All of the above applies equally to
Any and object.
Numbers often need a limited range. For the common cases jsonargparse.typing
has the predefined types :class:`.PositiveInt`, :class:`.NonNegativeInt`,
:class:`.PositiveFloat`, :class:`.NonNegativeFloat`,
:class:`.ClosedUnitInterval` and :class:`.OpenUnitInterval`, and the
:func:`.restricted_number_type` function to define new ones:
.. testcode::
from jsonargparse.typing import PositiveInt, PositiveFloat, restricted_number_type
# float larger than zero
parser.add_argument("--op1", type=PositiveFloat)
# between 0 and 10
from_0_to_10 = restricted_number_type("from_0_to_10", int, [(">=", 0), ("<=", 10)])
parser.add_argument("--op2", type=from_0_to_10)
Likewise, :func:`.restricted_string_type` creates string types restricted to match a regular expression. The predefined ones are :class:`.Email`, which follows the normal email pattern, and :class:`.NotEmptyStr`. For example, an argument that must be exactly four uppercase letters:
.. testcode::
from jsonargparse.typing import Email, restricted_string_type
CodeType = restricted_string_type("CodeType", "^[A-Z]{4}$")
parser.add_argument("--code", type=CodeType)
parser.add_argument("--email", type=Email)
Parsing a file path often means checking that it exists and has the required access permissions, without opening the file. Also, a path in a config file can be relative to the location of that config file, and after parsing it should be easy to use without having to think about where the config file was. For this jsonargparse has the :func:`.path_type` type generator and some predefined types, e.g. :class:`.Path_fr`.
For example, suppose there is a directory with a config file app/config.yaml
and some data app/data/info.db. The YAML file contains:
# File: config.yaml
databases:
info: data/info.dbTo check that databases.info is a file that exists and is readable:
.. testsetup:: paths
cwd = os.getcwd()
tmpdir = tempfile.mkdtemp(prefix="_jsonargparse_doctest_")
os.chdir(tmpdir)
os.mkdir("app")
os.mkdir("app/data")
with open("app/config.yaml", "w") as f:
f.write("databases:\n info: data/info.db\n")
with open("app/data/info.db", "w") as f:
f.write("info\n")
.. testcleanup:: paths
os.chdir(cwd)
shutil.rmtree(tmpdir)
.. testcode:: paths
from jsonargparse import ArgumentParser
from jsonargparse.typing import Path_fr
parser = ArgumentParser()
parser.add_argument("--databases.info", type=Path_fr)
cfg = parser.parse_path("app/config.yaml")
The fr in the type name are flags standing for file and readable. After
parsing, databases.info is a :class:`.Path_fr` instance, which gives both
the original relative path from the YAML file and the absolute path:
>>> cfg.databases.info.relative
'data/info.db'
>>> cfg.databases.info.absolute # doctest: +ELLIPSIS
'/.../app/data/info.db'Directories work the same, e.g. :class:`.Path_dw` requires a directory that
exists and is writable. New path types are created with :func:`.path_type`, e.g.
Path_frw = path_type('frw') for files that must exist and be both readable
and writable. If app/config.yaml is not writable, then
Path_frw('app/config.yaml') raises a PathError (a subclass of
TypeError) saying that the file is not writable. All supported mode flags
are documented in the :class:`.Path` class.
Types created with :func:`.path_type` have :class:`.Path` as base class. This
class implements the os.PathLike protocol, using the absolute path, so for
the previous example:
>>> os.fspath(cfg.databases.info) # doctest: +ELLIPSIS
'/.../app/data/info.db'The content of the file is read with the :py:meth:`.Path.read_text` method, e.g.
info_db = cfg.databases.info.read_text().
An argument with a path type can be given nargs='+' to accept multiple
paths, i.e. --files file1 file2. To instead read a list of paths from a
plain text file or from stdin, add the argument with type list[<path_type>]
and sub_configs=True. The special string '-' means stdin:
.. testsetup:: path_list
cwd = os.getcwd()
tmpdir = tempfile.mkdtemp(prefix="_jsonargparse_doctest_")
os.chdir(tmpdir)
pathlib.Path("paths.lst").write_text("paths.lst\n")
pathlib.Path("file1").touch()
pathlib.Path("file2").touch()
parser = ArgumentParser()
stdin = sys.stdin
sys.stdin = StringIO("paths.lst\n")
.. testcleanup:: path_list
sys.stdin = stdin
os.chdir(cwd)
shutil.rmtree(tmpdir)
.. testcode:: path_list
from jsonargparse.typing import Path_fr
parser.add_argument("--list", type=list[Path_fr], sub_configs=True)
cfg = parser.parse_args(["--list", "paths.lst"]) # File with list of paths
cfg = parser.parse_args(["--list", "-"]) # List of paths from stdin
Without nargs, the argument expects a single value. So giving several paths
directly on the command line requires the JSON array syntax, i.e. --list
'["file1","file2"]', or the simpler append syntax of :ref:`list-append`, i.e.
--list+ file1 --list+ file2. Not as short as nargs='+', but with tab
completion the effort is minimal.
The same list[<path_type>] behavior applies to arguments created
automatically from type hints in signatures, i.e. with :func:`.auto_cli`,
:meth:`add_function_arguments <.ArgumentParser.add_function_arguments>`,
:meth:`add_method_arguments <.ArgumentParser.add_method_arguments>`,
:meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` and
:meth:`add_subclass_arguments <.ArgumentParser.add_subclass_arguments>`.
Note
Setting both nargs='+' and sub_configs=True for an argument of type
list[<path_type>] makes each given value produce a list of paths, which
might not be what you expect.
Note
Not all features of the :class:`.Path` class are supported on Windows.
:func:`.path_type` also supports URLs, with the 'u' flag, and fsspec file systems, with the 's' flag.
These need the requests and fsspec packages, which are installed with the
urls and fsspec extras, see :ref:`installation`.
For example, an argument that accepts either a readable file or a URL uses the
type Path_fur = path_type('fur'). If the value looks like a URL, a HEAD
request checks that it is accessible. The :py:meth:`.Path.read_text` method then
gets the content, doing a GET request for a URL, so the code does not need to
care whether the value is a local file or a URL.
set_parsing_settings(config_read_mode_urls_enabled=True) and
set_parsing_settings(config_read_mode_fsspec_enabled=True) extend this to
config files, that is to :meth:`parse_path <.ArgumentParser.parse_path>`,
:meth:`get_defaults <.ArgumentParser.get_defaults>` (default_config_files
argument), action="config", :py:meth:`.FromConfigMixin.from_config`,
:class:`.ActionJsonSchema`, :class:`.ActionJsonnet` and :class:`.ActionParser`.
So a tool that takes a config file can also get it from a URL:
my_tool.py --config http://example.com/config.yamlNote
Relative paths inside a remote path are parsed as remote. For example, for a
relative path model/state_dict.pt found inside
s3://bucket/config.yaml, its parsed absolute path becomes
s3://bucket/model/state_dict.pt.
Warning
Checking a path means accessing it, so any value in a remote config that a
type accepts as a path is requested from the remote, a secret given inline
included. To prevent this, add SecretStr to the type, e.g.
path_type('fsr') | SecretStr. Relative paths are then only resolved
locally, so just values with an explicit scheme are fetched.
Boolean arguments are very common, but argparse only supports them through
store_true and store_false. Users new to argparse often write
type=bool, which in argparse does not do what they expect.
In jsonargparse type=bool does the expected thing: the values true and
yes parse as True, and false and no as False. For example:
.. testsetup:: boolean
parser = ArgumentParser()
>>> parser.add_argument("--op1", type=bool, default=False) # doctest: +IGNORE_RESULT
>>> parser.add_argument("--op2", type=bool, default=True) # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--op1", "yes", "--op2", "false"])
Namespace(op1=True, op2=False)Two paired options, one to set True and the other to set False, are
added with :class:`.ActionYesNo`:
.. testsetup:: yes_no
parser = ArgumentParser()
.. testcode:: yes_no
from jsonargparse import ActionYesNo
# --op1 for true and --no_op1 for false.
parser.add_argument("--op1", action=ActionYesNo)
# --with-op2 for true and --without-op2 for false.
parser.add_argument("--with-op2", action=ActionYesNo(yes_prefix="with-", no_prefix="without-"))
With nargs='?' these options also accept a value of true, yes,
false or no.
String choices are another case of restricted values. Besides the usual
choices list, an Enum class can be given as type, which has the benefit
of mapping each string to a desired value:
.. testsetup:: enum
parser = ArgumentParser()
>>> import enum
>>> class MyEnum(enum.Enum):
... choice1 = -1
... choice2 = 0
... choice3 = 1
...
>>> parser.add_argument("--op", type=MyEnum) # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--op=choice1"])
Namespace(op=<MyEnum.choice1: -1>)By default a new value replaces the previous one, also for lists. So
parser.parse_args(['--list=[1]', '--list=[2, 3]']) gives [2, 3]. To
append instead of replace, add + as suffix to the argument name:
.. testsetup:: append
parser = ArgumentParser()
class MyBaseClass:
pass
>>> parser.add_argument("--list", type=list[int]) # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--list=[1]", "--list+=[2, 3]"])
Namespace(list=[1, 2, 3])
>>> parser.parse_args(["--list=[4]", "--list+=5"])
Namespace(list=[4, 5])Config files support this too. The following two files first assign a list and then append to it:
# config1.yaml
list:
- 1# config2.yaml
list+:
- 2
- 3Appending works for any element type. When the type is a union that has a list
among its subtypes, appending changes the order in which the subtypes are
attempted, see :ref:`union-types`. Lists of class types (see :ref:`sub-classes`)
also work: first append the class with the + suffix, then give its
init_args as if the type were not a list, since they apply to the last class
in the list. For example, for an argument added as:
.. testcode:: append
parser.add_argument("--list_of_instances", type=list[MyBaseClass])
Thanks to the short notation, class_path and init_args can be omitted,
so several classes are appended and configured as:
python tool.py \
--list_of_instances+={CLASS_1_PATH} \
--list_of_instances.{CLASS_1_ARG_1}=... \
--list_of_instances.{CLASS_1_ARG_2}=... \
--list_of_instances+={CLASS_2_PATH} \
--list_of_instances.{CLASS_2_ARG_1}=... \
...
--list_of_instances+={CLASS_N_PATH} \
--list_of_instances.{CLASS_N_ARG_1}=... \
...Once a new class is appended, the arguments of a previous class can no longer be changed. This limitation is intentional: it forces classes and their arguments to be given in order, which makes the command line easier to write and to read.
An argument of type dict accepts a value in JSON format:
.. testsetup:: dict_items
parser = ArgumentParser()
>>> parser.add_argument("--dict", type=dict) # doctest: +IGNORE_RESULT
>>> parser.parse_args(['--dict={"key1": "val1", "key2": "val2"}'])
Namespace(dict={'key1': 'val1', 'key2': 'val2'})As with lists, a second JSON dict replaces the previous value completely. Single items are set without replacing as:
>>> parser.parse_args(["--dict.key1=val1", "--dict.key2=val2"])
Namespace(dict={'key1': 'val1', 'key2': 'val2'})Classes that inherit from typing.Generic, i.e. user-defined generic types,
are supported. For example, a point in 2D:
.. testsetup:: generic_types
parser = ArgumentParser()
.. testcode:: generic_types
from typing import Generic, TypeVar
Number = TypeVar("Number", float, complex)
@dataclass
class Point2d(Generic[Number]):
x: Number = 0.0
y: Number = 0.0
Parsing complex-valued points:
>>> parser.add_argument("--point", type=Point2d[complex]) # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--point.x=(1+2j)"]).point
Namespace(x=(1+2j), y=0j)A TypeVar can't be used to validate, so when it is used as a type, e.g.
options: Optional[OptionsT] = None, it is replaced by what it stands for:
its PEP 696 default, its constraints or its bound, in that order. Any of
these given as a forward reference, e.g. TypeVar("OptionsT",
default="Options[int]"), is resolved with the names of the module in which the
TypeVar is defined. When the TypeVar has none of these, or the forward
reference fails to resolve, the value is accepted without validation and the
help shows it as Unvalidated<...>.
A Callable type accepts several kinds of value. The first is the import path
of a callable object:
.. testsetup:: callable
parser = ArgumentParser()
.. testcode:: callable
parser.add_argument("--callable", type=Callable)
parser.parse_args(["--callable=time.sleep"])
The second is a class whose instances are callable:
.. testcode:: callable
class OffsetSum:
def __init__(self, offset: int):
self.offset = offset
def __call__(self, value: int):
return self.offset + value
.. testcode:: callable
:hide:
doctest_mock_class_in_main(OffsetSum)
>>> value = {
... "class_path": "__main__.OffsetSum",
... "init_args": {
... "offset": 3,
... },
... }
>>> cfg = parser.parse_args(["--callable", str(value)])
>>> cfg.callable
Namespace(class_path='__main__.OffsetSum', init_args=Namespace(offset=3))
>>> init = parser.instantiate(cfg)
>>> init.callable(5)
8The third only applies when the callable returns class instances. It is a form of :ref:`dependency-injection`, explained in :ref:`instance-factories`.
:func:`.register_type` adds new types for use in parsers. If the class can be
created from a string representation, and str of an instance gives that
representation back, only the class is needed. This is how
jsonargparse.typing registers complex numbers, register_type(complex),
which is the same as register_type(complex, serializer=str,
deserializer=complex). Other classes need a serializer and/or a deserializer,
for example datetime with a format other than the default ISO 8601:
.. testcode::
from datetime import datetime
from jsonargparse import ArgumentParser
from jsonargparse.typing import register_type
def serializer(v):
return v.strftime("%d/%m/%Y %H:%M")
def deserializer(v):
return datetime.strptime(v, "%d/%m/%Y %H:%M")
register_type(datetime, serializer, deserializer)
parser = ArgumentParser()
parser.add_argument("--datetime", type=datetime)
parser.parse_args(["--datetime=03/09/2008 20:56"])
Registering an already registered type replaces the previous one, jsonargparse's
own registrations included. A debug log names the module of each, useful when
two packages register the same type. Give fail_already_registered=True to
fail instead. A generic class is registered unsubscripted, and the registration
also applies to its subscripted forms, e.g. os.PathLike[str]. The type
arguments are not validated, since the deserializer gets the complete value.
Note
Registering is only intended for simple types. By default, any class used as a type hint is treated as a subclass type (see :ref:`sub-classes`), which suits many use cases. Registering a class with :func:`.register_type` removes that option.
New types can be created and used for parsing. Even when a type is meant for a CLI, it is better to design it so that it also makes sense outside of parsing, i.e. as a type hint in functions and classes that improves the code in general. An alternative is to use pydantic types.
The simplest way is to implement a class. Take a basic type such as int as
reference. Basic types have these properties:
- Casting a string creates an instance of the type, if the value is valid, e.g.
int("1"). - Casting a string raises a
ValueError, if the value is not valid, e.g.int("a"). - Casting an instance of the type to string gives back the string representation
of the value, e.g.
str(1) == "1". - Types are idempotent, i.e. casting an instance of the type to the type gives
back the same value, e.g.
int(1) == int(int(1)).
A new type is registered with :func:`.register_type`. If it follows the
properties above, register_type(MyType) is enough. :func:`.extend_base_type`
creates and registers a type in a single call, for example for even integers:
.. testcode::
from jsonargparse.typing import extend_base_type
def is_even(class_type, value):
if int(value) % 2 != 0:
raise ValueError(f"{value} is not even")
EvenInt = extend_base_type("EvenInt", int, is_even)
Then in a parser:
>>> parser = ArgumentParser()
>>> parser.add_argument("--even_int", type=EvenInt) # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--even_int=2"])
Namespace(even_int=2)When a custom type is used as a type hint, the default must be cast to it so that static type checkers don't complain:
.. testcode::
def fn(value: EvenInt = EvenInt(2)):
...
Unlike in argparse, dot notation in the argument names defines a hierarchy of nested namespaces:
>>> parser = ArgumentParser(prog="app")
>>> parser.add_argument("--lev1.opt1", default="from default 1") # doctest: +IGNORE_RESULT
>>> parser.add_argument("--lev1.opt2", default="from default 2") # doctest: +IGNORE_RESULT
>>> cfg = parser.get_defaults()
>>> cfg.lev1.opt1
'from default 1'
>>> cfg.lev1.opt2
'from default 2'A dataclass creates a group of nested options, with the advantage that the same options can be reused in several places of a project. The analogous example is:
.. testcode::
from dataclasses import dataclass
@dataclass
class Level1Options:
"""Level 1 options
Args:
opt1: Option 1
opt2: Option 2
"""
opt1: str = "from default 1"
opt2: str = "from default 2"
parser = ArgumentParser()
parser.add_argument("--lev1", type=Level1Options, default=Level1Options())
The :class:`.Namespace` class extends the argparse one. Keys can be accessed
like in a dictionary, either one level at a time, e.g. cfg['lev1']['opt1'],
or all at once, e.g. cfg['lev1.opt1']. The :py:meth:`.Namespace.as_dict`
method gives the nested namespace as a nested dictionary.
jsonargparse can parse configuration files (config files). The dot notation
hierarchy of the arguments (see :ref:`nested-namespaces`) defines the structure
expected in these files. The default parser_mode is json_or_yaml, which
parses as JSON and, if that fails, as YAML. YAML requires the yaml extra,
see :ref:`installation`, so without it only JSON is accepted. To change the
mode, use the parser_mode parameter of the parser, e.g.
ArgumentParser(parser_mode="json").
The :py:attr:`.ArgumentParser.default_config_files` property holds patterns of
config files to search for, e.g.
ArgumentParser(default_config_files=['~/.myapp.yaml', '/etc/myapp.yaml']).
All matching files are parsed in the given order and override the defaults from
the source code. They are always parsed first, so any command line argument
overrides their values.
An argument can also be added to give a config file path explicitly. This does
not disable default_config_files. The config argument is parsed at its
position among the command line arguments, so arguments after it override the
values from that config file. It can be given several times, each one overriding
the previous. Using the example parser from :ref:`nested-namespaces`, a config
file in YAML format could be:
# File: example.yaml
lev1:
opt1: from yaml 1
opt2: from yaml 2Adding a config file argument and parsing some arguments then gives:
.. testsetup:: config
cwd = os.getcwd()
tmpdir = tempfile.mkdtemp(prefix="_jsonargparse_doctest_")
os.chdir(tmpdir)
with open("example.yaml", "w") as f:
f.write("lev1:\n opt1: from yaml 1\n opt2: from yaml 2\n")
.. testcleanup:: config
os.chdir(cwd)
shutil.rmtree(tmpdir)
set_parsing_settings(config_include_enabled=False)
>>> from jsonargparse import ArgumentParser
>>> parser = ArgumentParser()
>>> parser.add_argument("--lev1.opt1", default="from default 1") # doctest: +IGNORE_RESULT
>>> parser.add_argument("--lev1.opt2", default="from default 2") # doctest: +IGNORE_RESULT
>>> parser.add_argument("--config", action="config") # doctest: +IGNORE_RESULT
>>> cfg = parser.parse_args(["--lev1.opt1", "from arg 1", "--config", "example.yaml", "--lev1.opt2", "from arg 2"])
>>> cfg.lev1.opt1
'from yaml 1'
>>> cfg.lev1.opt2
'from arg 2'The value can also be a string with the config content, instead of a path:
>>> cfg = parser.parse_args(["--config", '{"lev1":{"opt1":"from string 1"}}'])
>>> cfg.lev1.opt1
'from string 1'The config file can also come from an environment variable, see :ref:`environment-variables`. This variable is parsed first, so any other argument given through an environment variable overrides it.
To parse a config file or a config string without parsing command line arguments, use :meth:`parse_path <.ArgumentParser.parse_path>` or :meth:`parse_string <.ArgumentParser.parse_string>`.
A config can be composed from others with an __include__ key, which is
enabled with set_parsing_settings(config_include_enabled=True). Its value is
the path of a config file or a list of them, relative to the config that has the
key, or to the working directory for a config not from a file, e.g. a command
line value or given to :meth:`parse_object <.ArgumentParser.parse_object>`. It
is accepted at any level, also in sub-config files, and must be the first key
where it is given, since the keys that follow override what the included configs
set. Included configs can include others, and since paths are relative, a group
of config files can be moved around without being modified. Unlike
:ref:`sub-config-files`, includes do not depend on sub_configs, and
:meth:`save <.ArgumentParser.save>` with multifile=True does not keep them
as separate files.
The included configs, and then the keys that follow, are merged exactly as if
they had been given one after the other, e.g. as several --config. So the
same rules apply, for instance a dict value is replaced, items are appended
when the key is given with a + suffix, see :ref:`list-append`, and changing
a class_path discards the init_args that the new class does not accept,
also for the items of a list of classes. An __include__ is not supported
within a value that is not validated, e.g. Any, since nothing merges it.
Continuing with the example above:
.. testcode:: config
set_parsing_settings(config_include_enabled=True)
# File: lev1.yaml
opt1: from lev1# File: main.yaml
__include__: example.yaml
lev1:
__include__: lev1.yaml
opt2: from main.. testsetup:: config
pathlib.Path("lev1.yaml").write_text("opt1: from lev1\n")
pathlib.Path("main.yaml").write_text(
"__include__: example.yaml\nlev1:\n __include__: lev1.yaml\n opt2: from main\n"
)
>>> cfg = parser.parse_args(["--config", "main.yaml"])
>>> cfg.lev1.opt1
'from lev1'
>>> cfg.lev1.opt2
'from main'Note
Including configs is experimental. Behavior details might change in non-major releases.
Parsers that have an action="config" argument also get a --print_config
option. It is useful for tools with many options, to create an initial config
file with all default values. The option accepts one or more flags separated by
comma, e.g. --print_config=comments,skip_default. The comments and
provenance flags require the ruamel.yaml package:
comments: add the help descriptions as YAML comments. The comments are the descriptions of the groups and arguments of the parser and, for values that correspond to a class, e.g. theinit_argsof a subclass or the fields of a dataclass, the descriptions from that class.provenance: add to each value a YAML comment saying where it came from, i.e. a default, a default config file, a config file, a config string, an environment variable or a command line argument, orimplicitand what implied it, e.g. aclass_pathnot given. For config files parsed as YAML, the comment includes the line number, e.g.# config file config.yaml:3.skip_default: skip entries whose value is the same as the default.skip_unset: skip entries that were not given a value, see :ref:`unset-values`.
From Python, a config object is serialized with the :meth:`dump
<.ArgumentParser.dump>` and :meth:`save <.ArgumentParser.save>` methods. The
supported formats are yaml, toml, json/json_indented,
json_compact and parser_mode, the default, which uses the format of the
parser, yaml for json_or_yaml when the yaml extra is installed, or
json when the parser mode has no dumper. The yaml format
dumps with a subclass of yaml.SafeDumper that writes multi-line
strings as literal blocks, i.e. |, instead of escaping the line breaks. More
formats are added with :func:`.set_dumper`, for example to dump with PyYAML's
default_flow_style:
.. testcode::
import yaml
from jsonargparse import set_dumper
def custom_yaml_dump(data):
return yaml.safe_dump(data, default_flow_style=True)
set_dumper("yaml_custom", custom_yaml_dump)
The yaml parser mode (see :py:meth:`.ArgumentParser.__init__`) requires the
yaml extra and loads with a subclass of yaml.SafeLoader that has three
differences:
- Float scientific notation is supported, e.g.
'1e-3'gives0.001, while default PyYAML gives the string'1e-3'. - Dates are kept as strings, e.g.
'2020-01-01', while default PyYAML gives adatetime.date. - Text that looks like a mapping only because of the syntax is kept as a string,
e.g.
'{text}'and'name:', while default PyYAML gives{'text': None}and{'name': None}.
The :func:`.set_loader` function replaces the yaml loader or adds a loader
as a new parser mode. For example, a custom PyYAML loader is registered and used
as:
.. testcode::
import yaml
from jsonargparse import ArgumentParser, set_loader
class CustomLoader(yaml.SafeLoader):
...
def custom_yaml_load(stream):
return yaml.load(stream, Loader=CustomLoader)
set_loader("yaml_custom", custom_yaml_load)
parser = ArgumentParser(parser_mode="yaml_custom")
When the loader is based on a library other than PyYAML, give the exceptions
that it raises on failure to :func:`.set_loader`.
Well written Python code gives type hints to its parameters and describes them in the docstrings. Making such code configurable should not duplicate the types and the descriptions. To avoid this, jsonargparse adds annotated parameters as arguments automatically, see :meth:`add_function_arguments <.ArgumentParser.add_function_arguments>`, :meth:`add_method_arguments <.ArgumentParser.add_method_arguments>`, :meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` and :meth:`add_subclass_arguments <.ArgumentParser.add_subclass_arguments>`.
Take for example a class with an init and a method with docstrings:
.. testsetup:: class_method
sys.argv = ["", "--myclass.init.foo={}", "--myclass.method.bar=0"]
class MyBaseClass:
pass
.. testcode:: class_method
class MyClass(MyBaseClass):
def __init__(self, foo: dict[str, int | list[int]], **kwargs):
"""Initializer for MyClass.
Args:
foo: Description for foo.
"""
super().__init__(**kwargs)
...
def mymethod(self, bar: float, baz: bool = False):
"""Description for mymethod.
Args:
bar: Description for bar.
baz: Description for baz.
"""
...
Both MyClass and mymethod are made configurable, the class instantiated
and the method run, as follows:
.. testcode:: class_method
from jsonargparse import ArgumentParser
parser = ArgumentParser()
parser.add_class_arguments(MyClass, "myclass.init")
parser.add_method_arguments(MyClass, "mymethod", "myclass.method")
cfg = parser.parse_args()
init = parser.instantiate(cfg)
init.myclass.method(init.myclass.init)
The :meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` call adds
myclass.init.foo, with the description from the docstring, and makes it
required since it has no default. When parsed, it is validated against its type
hint, i.e. a dict whose values are ints or lists of ints. Since the init has
**kwargs, the keyword arguments of MyBaseClass are added too. Likewise,
the :meth:`add_method_arguments <.ArgumentParser.add_method_arguments>` call
adds myclass.method.bar as a required float and myclass.method.baz as an
optional boolean with default false.
All the groups added by these methods are instantiated at once with
:meth:`instantiate <.ArgumentParser.instantiate>`. In the example above,
init.myclass.init is an instance of MyClass built from the parsed
arguments, and init.myclass.method is an :func:`operator.methodcaller` with
the arguments of mymethod bound, which is called with an instance. A function,
static or class method group becomes a :func:`functools.partial` instead. Give
instantiate=False to keep a group as parsed.
All values can be given in a single config file (see :ref:`configuration-files`). For convenience, the values of each argument group created by an add signature method can also come from its own file. For the example above, a general config file could be:
myclass:
init: myclass.yaml
method: mymethod.yamlThen myclass.yaml and mymethod.yaml hold the settings for the class
instantiation and for the method call.
A wide range of type hints is supported for signature parameters, see :ref:`type-hints`. Notes about the add signature methods:
- A parameter without a type annotation, or with a type that can only be
validated in part, is added with a type that accepts any value, see
:ref:`unvalidated-types`. Without an annotation but with a default, the type
is
Union[<type of the default>, Untyped], i.e. a value is converted to the default's type when it accepts it. fail_untypeddecides which parameters without a type annotation raise an exception instead: the required ones with the defaultTrue, all of them with"all", and none withFalse. Use"all"only for code you own, since one untyped parameter of a dependency would make its signature impossible to add.- Parameters whose name starts with
_are considered internal and skipped, unless they are required. - A
*argsis added as a list argument with its name, e.g.*files: strasfilesof typelist[str]. Withas_positional=Trueit is a positional that takes zero or more values, which argparse is unable to combine with subcommands, soas_positional=Falseis required then. When calling, positional-only parameters, and when*argshas values also the ones before it, are given positionally. Binding such values after positionals given on call, e.g. for aCallablethat returns a class, requires Python 3.14 or later. - The
skipparameter excludes arguments, e.g.parser.add_method_arguments(MyClass, 'mymethod', skip={'baz'}). In a subclass spec, a skipped parameter can still be given indict_kwargs, see :ref:`unresolved-parameters`.
Note
The signatures support is intended to be non-intrusive. By design there is no need to inherit from a class, add decorators, or use special type hints and default values. Among other advantages, this makes it possible to use classes from third party libraries, which developers can't modify.
:class:`.FromConfigMixin` adds a from_config class method, so that a class
can be instantiated directly from configuration values. It is useful for small
utilities that load constructor values from a dictionary or a config file in a
single call.
>>> from jsonargparse import FromConfigMixin
>>> class Client(FromConfigMixin):
... def __init__(self, host: str = "localhost", port: int = 80):
... self.host = host
... self.port = port
>>> client = Client.from_config({"host": "api.local", "port": 8080})
>>> (client.host, client.port)
('api.local', 8080)See :class:`.FromConfigMixin` in the API reference for the complete behavior.
Parameter descriptions in the help require the docstring-parser package, which is installed by
the docstrings extra, see :ref:`installation`.
Two options can be configured, both related to parsing speed. By default the
style is docstring_parser.DocstringStyle.AUTO, which tries all supported
styles. If the codebase uses a single style, setting it is faster:
.. testcode:: docstrings
from docstring_parser import DocstringStyle
from jsonargparse import set_parsing_settings
set_parsing_settings(docstring_parse_style=DocstringStyle.REST)
The second option is support for attribute docstrings, i.e. literal strings in the line after an attribute is defined. It is disabled by default, because enabling it makes parsing slower even for classes that have none:
.. testcode:: docstrings
from dataclasses import dataclass
from jsonargparse import set_parsing_settings
set_parsing_settings(docstring_parse_attribute_docstrings=True)
@dataclass
class Options:
"""Options for a competition winner."""
name: str
"""Name of winner."""
prize: int = 100
"""Amount won."""
Docstrings are searched in the entire class inheritance chain. So inherited
parameters and attributes are documented in the help by the base class that
declares them, and the description of a group comes from the nearest class in
the method resolution order that has a docstring. Base classes that only provide
machinery, i.e. object, abc.ABC, typing.Generic, enum.Enum,
pydantic.BaseModel and the like, are skipped, since their docstrings
describe themselves instead of the class being added to the parser.
.. testcleanup:: docstrings
set_parsing_settings(docstring_parse_style=DocstringStyle.GOOGLE)
set_parsing_settings(docstring_parse_attribute_docstrings=False)
Arguments added automatically from signatures give the developer limited control
over their behavior. To customize them, subclass the parser and override the
:meth:`add_argument <.ActionsContainer.add_argument>` method. For example,
bool arguments need a true|false value on the command line. To use
:class:`.ActionYesNo` instead, in a CLI based on :func:`.auto_cli`:
.. testcode::
from jsonargparse import ActionYesNo, ArgumentParser, auto_cli
class CustomArgumentParser(ArgumentParser):
def add_argument(self, *args, **kwargs):
if "type" in kwargs and kwargs["type"] == bool:
kwargs.pop("type")
kwargs["action"] = ActionYesNo
return super().add_argument(*args, **kwargs)
def main_function(flag: bool = False):
...
if __name__ == "__main__":
auto_cli(main_function, parser_class=CustomArgumentParser)
Some functions return an instance of a class. :func:`.class_from_function` turns such a function into a class that can be added to a parser, so that :meth:`instantiate <.ArgumentParser.instantiate>` calls the function:
.. testsetup:: class_from_function
class MyClass:
pass
def instantiate_myclass() -> MyClass:
return MyClass()
.. testcode:: class_from_function
from jsonargparse import ArgumentParser
from jsonargparse.typing import class_from_function
parser = ArgumentParser()
dynamic_class = class_from_function(instantiate_myclass)
parser.add_class_arguments(dynamic_class, "myclass.init")
Note
:func:`.class_from_function` requires the function to have a return type annotation, which must be the class that it returns.
Classes created with :func:`.class_from_function` can be selected using
class_path for :ref:`sub-classes`. For example, if
:func:`.class_from_function` is run in a module my_module as:
.. testcode:: class_from_function
class_from_function(instantiate_myclass, name="MyClass")
Then the class_path of the created class is my_module.MyClass.
There are three techniques for resolving signature parameters. The AST resolver,
which uses Python's Abstract Syntax Trees (AST) library, is tried first. The
assumptions resolver, based on assumptions about class inheritance, is the
fallback for when AST fails. The stubs resolver, which uses *.pyi stub
files, is applied on top of both.
The resolvers make a best effort to find the correct names and types that the
parser should accept. Some cases are not supported yet, and some would be
impossible to support. For these there is the special dict_kwargs key, whose
entries are not validated when parsing but are used for class instantiation. The
name comes from the use cases in which **kwargs is only used as a dict, a
purpose that it also serves.
This section is about parameters whose name the resolvers can't determine. For parameters that are resolved but have a type that can't be validated, see :ref:`unvalidated-types`.
Take for example the following parsing and instantiation:
.. testsetup:: unresolved
sys.argv = ["", "--myclass=MyClass"]
class MyClass:
def __init__(self, foo: int = 0, **kwargs):
self.kwargs = kwargs
MyClass.__module__ = "jsonargparse_tests"
jsonargparse_tests.MyClass = MyClass
.. testcode:: unresolved
from jsonargparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--myclass", type=MyClass)
cfg = parser.parse_args()
cfg_init = parser.instantiate(cfg)
Since the resolvers can't determine where the **kwargs of
MyClass.__init__ go, the following is a valid config file:
class_path: MyClass
init_args:
foo: 1
dict_kwargs:
bar: 2The value for bar is not validated, but the class is instantiated as
MyClass(foo=1, bar=2). The help of a class, e.g. --myclass.help=MyClass,
notes when it accepts extra keyword arguments through dict_kwargs.
Resolved parameters are meant to be given in init_args. When a class has no
unresolved **kwargs, a dict_kwargs key that is not one of its parameters
fails during parsing. Keys that the class does accept are moved to
init_args, so that configs keep working when an improvement of the resolvers
turns an unresolved parameter into a resolved one.
A parameter excluded with skip is still a parameter of the class, so
dict_kwargs accepts it. This is how to give a value to a parameter that had
to be skipped, e.g. an untyped mandatory one.
The assumptions resolver only considers classes. When __init__ has *args
and/or **kwargs, it assumes that these go directly to the parent class, i.e.
that __init__ has a line like super().__init__(*args, **kwargs), and
blindly collects the __init__ parameters of the parent classes. If the code
does not follow this pattern, the collected parameters are wrong. This is why it
is only a fallback for when the AST resolver fails.
The AST resolver reads the source code and works out how *args and
**kwargs are used, so as to find more accepted parameters. Since code can do
endless things, only a few specific cases are supported, illustrated below. The
code does not need to look exactly like this. What matters is how *args and
**kwargs are used, not the other parameters, the names of the variables, or
the complexity of unrelated code.
.. testsetup:: ast_resolver
class BaseClass:
pass
class SomeClass:
def __init__(self, **kwargs):
pass
class ChildClass(BaseClass):
def __init__(self, *args, **kwargs):
pass
Cases for statements in functions or methods
.. testcode:: ast_resolver
def calls_a_function(*args, **kwargs):
a_function(*args, **kwargs)
def calls_a_method(*args, **kwargs):
an_instance = SomeClass()
an_instance.a_method(*args, **kwargs)
def calls_a_static_method(*args, **kwargs):
an_instance = SomeClass()
an_instance.a_static_method(*args, **kwargs)
def calls_a_class_method(*args, **kwargs):
SomeClass.a_class_method(*args, **kwargs)
def calls_local_import(**kwargs):
import some_module
some_module.a_callable(**kwargs)
def calls_nested_module_attr(**kwargs):
import some_module
some_module.nested.a_callable(**kwargs)
def pops_from_kwargs(**kwargs):
val = kwargs.pop("name", "default")
def gets_from_kwargs(**kwargs):
val = kwargs.get("name", "default")
def constant_conditional(**kwargs):
if global_boolean_1:
first_function(**kwargs)
elif not global_boolean_2:
second_function(**kwargs)
else:
third_function(**kwargs)
Cases for classes
.. testcode:: ast_resolver
class PassThrough(BaseClass):
def __init__(self, *args, **kwargs):
super().__init__(*args, **kwargs)
class CallMethod:
def __init__(self, *args, **kwargs):
self.a_method(*args, **kwargs)
class AttributeUseInMethod:
def __init__(self, **kwargs):
self._kwargs = kwargs
def a_method(self):
a_callable(**self._kwargs)
class AttributeUseInProperty:
def __init__(self, **kwargs):
self._kwargs = kwargs
@property
def a_property(self):
return a_callable(**self._kwargs)
class DictUpdateUseInMethod:
def __init__(self, **kwargs):
self._kwargs = dict(p1=1) # Can also be: self._kwargs = {'p1': 1}
self._kwargs.update(**kwargs) # Can also be: self._kwargs = dict(p1=1, **kwargs)
def a_method(self):
a_callable(**self._kwargs)
class InstanceInClassmethod:
@classmethod
def get_instance(cls, **kwargs):
return cls(**kwargs)
class NonImmediateSuper(BaseClass):
def __init__(self, *args, **kwargs):
super(BaseClass, self).__init__(*args, **kwargs)
Cases for class instance defaults
.. testcode:: ast_resolver
# Class instance: only keyword arguments with ``ast.Constant`` value
class_instance: SomeClass = SomeClass(param=1)
# Lambda returning class instance: only keyword arguments with ``ast.Constant`` value
class_instance: Callable[[type], BaseClass] = lambda a: ChildClass(a, param=2.3)
There can be other parameters besides *args and **kwargs, so the
signatures above could be e.g. name(p1: int, k1: str = 'a', **kws). The
internal call can also have extra parameters, for example:
.. testcode::
def calls_a_function(*args, **kwargs):
a_function(*args, param=1, **kwargs)
param is excluded from the resolved parameters, because it is hard coded.
Multiple calls that use **kwargs are supported, but with caveats:
.. testcode:: ast_resolver
def conditional_calls(**kwargs):
if condition_1:
first_function(**kwargs)
elif condition_2:
second_function(**kwargs)
else:
third_function(**kwargs)
Parameters that have the same type hint and default in all calls behave
normally. When the calls disagree, the help shows the default as
Conditional<ast-resolver> {DEFAULT_1, ...}. The main difference is that
these parameters are not included in :meth:`get_defaults
<.ArgumentParser.get_defaults>` or in the output of --print_config. This is
needed because the parser does not know which call will happen at runtime, and
including them would make :meth:`instantiate <.ArgumentParser.instantiate>` fail
with unexpected keyword arguments.
A *args forwarded to calls is replaced by what the calls take positionally,
including their own *args, when all the calls agree on it. A *args that
the code uses itself, or whose use can't be resolved, is added as a list
argument, and one that is not used is left out.
Note
The resolvers log failures and unsupported cases. To see these logs, set the
environment variable JSONARGPARSE_DEBUG to true. The supported cases
are limited, so please create issues asking for new ones. Note though that a
very convoluted case can be a sign that the code needs refactoring.
The stubs resolver uses the typeshed-client package to find parameters and
their type hints in stub files *.pyi. To enable it, install jsonargparse
with the typeshed extra, see :ref:`installation`.
Most of the Python standard library has its types in stubs, for example:
>>> from random import uniform
>>> parser = ArgumentParser()
>>> parser.add_function_arguments(uniform, "uniform") # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--uniform.a=0.7", "--uniform.b=3.4"])
Namespace(uniform=Namespace(a=0.7, b=3.4))Without the stubs resolver, that :meth:`add_function_arguments
<.ArgumentParser.add_function_arguments>` call needs fail_untyped=False, and
then a and b get Untyped instead of float, so an invalid value
such as a string would not fail.
The defaults of parameters found only through stubs are not known. The help then
shows the default as Unknown<stubs-resolver>, and these parameters are not
included in :meth:`get_defaults <.ArgumentParser.get_defaults>` or in the output
of --print_config.
By default only *.pyi files are searched. To also search in *.py files,
use set_parsing_settings(stubs_resolver_allow_py_files=True).
Pydantic and attrs allow giving a field a name that is different from the
attribute name, an alias: pydantic's alias/validation_alias and attrs'
alias. The resolvers take these aliases into account, so that a parser
accepts the same names as the class itself.
When the framework accepts both names, e.g. a pydantic model with
populate_by_name, the alias is accepted as an additional option and config
key. The attribute name is the one used in the parsed namespace, in
--print_config and in dumps:
>>> from pydantic import BaseModel, ConfigDict, Field
>>> class Client(BaseModel):
... model_config = ConfigDict(populate_by_name=True)
... api_key: str = Field(default="", alias="key")
...
>>> parser = ArgumentParser()
>>> parser.add_class_arguments(Client, "client") # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--client.key=abc"])
Namespace(client=Namespace(api_key='abc'))When the framework only accepts the alias, e.g. the same model without
populate_by_name, the alias is the name used everywhere, since giving the
attribute name would not instantiate the class as expected.
Aliases don't work for a parameter whose type is a subclasses-disabled type added as a group of arguments, since then the name is a prefix of several arguments instead of a single option string, so only the attribute name is accepted. Enabling subclasses for the type, see :ref:`enable-disable-subclasses`, makes it a single argument, and then its alias is accepted too.
Dependency injection is a design pattern that separates how objects are created from how they are used, giving more loosely coupled programs, see the wikipedia article. Supporting it has been a design goal of jsonargparse.
In Python, dependency injection is done by:
- Using as type hint a class, such that the parameter accepts an instance of
this class or any subclass, e.g.
module: ModuleBaseClass. - Using as type hint a callable that returns an instance of a class, such that
the parameter accepts a function for instantiation. This could be either using
Callable, e.g.module: Callable[[int], ModuleBaseClass], or a protocol, e.g.module: ModuleFactoryProtocol.
When a class is used as a type hint, the value is a dictionary with a
class_path entry, which is the dot notation expression to import the class,
and optionally init_args to instantiate it. This dictionary is called a
subclass spec. When parsing, it is checked that the class can be imported,
that it is a subclass of the type, and that the init_args values are valid
arguments to instantiate it. The parsed config keeps the class_path and
init_args entries. :meth:`instantiate <.ArgumentParser.instantiate>` gives a
config object with all nested subclasses instantiated.
The value can also be the import path of an instance of the class, which is
kept as that same instance. A signature default that is such an instance, e.g. a
sentinel UNSET for x: int | UnsetType = UNSET, is shown and dumped as
its import path.
Besides using a class as type hint in a signature, parsers can be built with
:meth:`add_class_arguments <.ArgumentParser.add_class_arguments>` and
:meth:`add_subclass_arguments <.ArgumentParser.add_subclass_arguments>`. These
accept a skip argument to exclude parameters inside subclasses, given as a
relative destination key, i.e. param.init_args.subparam. A single argument
can also be added with a class as type, i.e. parser.add_argument("--module",
type=ModuleBase).
A simple example, with a top-level class whose parameter expects an injected
class instance, uses a config file config.yaml as:
myclass:
calendar:
class_path: calendar.Calendar
init_args:
firstweekday: 1Then in Python:
.. testsetup:: subclasses
cwd = os.getcwd()
tmpdir = tempfile.mkdtemp(prefix="_jsonargparse_doctest_")
os.chdir(tmpdir)
with open("config.yaml", "w") as f:
f.write("myclass:\n calendar:\n class_path: calendar.Calendar\n init_args:\n firstweekday: 1\n")
.. testcleanup:: subclasses
os.chdir(cwd)
shutil.rmtree(tmpdir)
>>> from calendar import Calendar
>>> class MyClass:
... def __init__(self, calendar: Calendar):
... self.calendar = calendar
...
>>> parser = ArgumentParser()
>>> parser.add_class_arguments(MyClass, "myclass") # doctest: +IGNORE_RESULT
>>> cfg = parser.parse_path("config.yaml")
>>> cfg.myclass.calendar.as_dict()
{'class_path': 'calendar.Calendar', 'init_args': {'firstweekday': 1}}
>>> cfg = parser.instantiate(cfg)
>>> isinstance(cfg.myclass, MyClass)
True
>>> isinstance(cfg.myclass.calendar, Calendar)
True
>>> cfg.myclass.calendar.getfirstweekday()
1Here the class_path points to the same class used as the type. A subclass of
Calendar, with more init parameters, would work as well.
Using :meth:`add_subclass_arguments <.ArgumentParser.add_subclass_arguments>`
instead of :meth:`add_class_arguments <.ArgumentParser.add_class_arguments>`
would also accept subclasses of MyClass, and the config would be:
myclass:
class_path: my_module.MyClass
init_args:
calendar:
class_path: calendar.TextCalendar
init_args:
firstweekday: 1Note
A parameter of type Any, object, or Untyped, accepts a dict with
class_path and init_args, and the spec is kept as is, so that the
code receiving it decides whether to instantiate it. Set
instantiate_subclass_spec_in_any=True in :func:`.set_parsing_settings`
to have instantiate build the class, though this is discouraged, since
it means that a config can instantiate any class, which is a security risk.
A value that looks like a subclass spec, i.e. has a class_path, but
can't be parsed as one, e.g. because the class fails to import, is by
default left unchanged and a debug message is logged. Set
validate_subclass_spec_in_any=True in :func:`.set_parsing_settings` to
make parsing fail instead. Besides Any, object and
Unvalidated<...>, this also applies to dicts that don't validate their
values, e.g. dict[str, Any]. For dicts the spec is only validated, since
the value stays a dict. This matters for unions such as Union[SomeClass,
dict[str, Any]], where a spec rejected by the class member would otherwise
be silently swallowed by the dict member.
Note
class_path also accepts a function whose return type is a class. The
accepted init_args are then the parameters of that function.
Note
Abstract classes, i.e. classes that have abstract methods, are not accepted
as class_path, since they can't be instantiated. For the same reason
they are not among the known subclasses shown in the help.
By default a class is instantiated as class_type(*args, **kwargs). A
different way to instantiate, e.g. within some context, can be given with the
instantiators parameter of :meth:`instantiate <.ArgumentParser.instantiate>`,
:func:`.auto_cli` and from_config. It is a list of (instantiator,
class_type, subclasses) tuples that apply only to that call, including nested
classes and a callable that returns a class, even if called afterwards. The
first entry whose class_type matches is used:
def instantiator(class_type, *args, **kwargs):
with some_context():
return class_type(*args, **kwargs)
init = parser.instantiate(cfg, instantiators=[(instantiator, MyModel, True)])With subclasses=True the instantiator also applies to subclasses of
class_type. The details of instantiator functions, e.g. getting values
applied by links, are in :func:`.add_instantiator`, which registers an
instantiator globally, for all calls. Prefer the instantiators parameter,
since a global one affects code of other libraries that use jsonargparse.
Resolving a class_path imports the named module and instantiates the named
class with the given init_args, so a config decides what code runs. When the
configs come from a trusted source, this is not a concern. When they don't, e.g.
a config uploaded by a user of a service, an import path denylist limits what a
config can reach.
Import paths that come from a value, i.e. a class_path, a Callable, a
type[...] or a types.ModuleType given in a config file, the command line
or an environment variable, are checked against a denylist before the import
happens. Paths that come from code, e.g. type annotations and defaults, are
never checked. jsonargparse denies a set of paths by default, mostly standard
library modules that give arbitrary code execution, e.g. os, subprocess,
pickle and importlib. Two settings adjust the list:
.. testsetup:: import_paths
saved_import_path_settings = dict(_common.parsing_settings)
.. testcode:: import_paths
from jsonargparse import set_parsing_settings
set_parsing_settings(
import_path_denylist=["mypackage._internal"],
import_path_allowlist=["functools.partial"],
)
An entry denies or allows a dot import path and everything under it, so os
also denies os.system. The most specific entry decides, which is why
functools.partial above is allowed even though functools is denied by
default. An entry given in both lists is allowed, so naming a default entry in
import_path_allowlist is how to stop denying it. The one exception is
jsonargparse itself, which is denied by default and not accepted in
import_path_allowlist, since a value that names it would be able to call
:func:`.set_parsing_settings` and thus change the policy that is checking it.
An object is denied by where it is defined, not only by the path used to reach
it. Modules commonly import others, e.g. import os, so without this
some.module.os.system would give the same object as the denied
os.system. This second check can only happen once the object is resolved, so
it prevents the object from being used, unlike the check on the given path,
which prevents the import from happening at all. An object that has no defining
path of its own is denied by the callable it reaches, i.e. the bound function
for a functools.partial or partialmethod and the defining class for an
instance, e.g. builtins.help is an instance of the _sitebuiltins._Helper
class.
Entries given are added to the ones denied by default, they don't replace them.
For configs that are entirely untrusted, prefer denying everything and allowing
only what the application expects. The * entry is only accepted in
import_path_denylist:
.. testcode:: import_paths
set_parsing_settings(
import_path_denylist=["*"],
import_path_allowlist=["mypackage.tools"],
)
.. testcleanup:: import_paths
_common.parsing_settings.clear()
_common.parsing_settings.update(saved_import_path_settings)
The denylist is not the only thing that limits what a config can reach. Type
hints do as well, since a class_path is only accepted where the annotation
allows one, and must name a subclass of the annotated type. The exceptions are
Any and object, which accept a subclass spec of any class, see
:ref:`sub-classes`. By default instantiate_subclass_spec_in_any is
False, so these values are kept as plain dicts, nothing is imported or
instantiated and the code that receives the dict decides what to do with it.
The denylist still applies when validate_subclass_spec_in_any=True, since
validating a spec requires importing the class it names.
Note
A denylist is a mitigation, not a sandbox. A large enough set of installed
dependencies is likely to contain something that reaches a denied capability
without naming a denied path, e.g. a class that runs a command given to it.
Only * plus a narrow allowlist gives a bound on what a config can
import.
Note
The omegaconf parser modes, see :ref:`omegaconf-interpolation`, give a
config access to OmegaConf's resolvers, which the import path denylist does
not check. The built-in oc.env resolver reads environment variables, so
a value of ${oc.env:AWS_SECRET_ACCESS_KEY} puts that variable's value
into the config, and the resolvers that the application registers are
equally reachable. Avoid these parser modes for untrusted configs.
Instead of writing a subclass spec inline, a path to a config file that holds it
can be given. This splits a large config into smaller reusable files. It
requires the argument to be added with sub_configs=True, which is the
default in :func:`.auto_cli` and is accepted by :meth:`add_argument
<.ArgumentParser.add_argument>` and the add_*_arguments methods.
This also works for the items of a list of classes and for the values of a dict of classes, useful when each component has its own config file. For example, take the following classes:
.. testcode:: sub_config_files
class Hook:
def __init__(self, verbose: bool = False):
self.verbose = verbose
class LogHook(Hook):
def __init__(self, log_file: str = "run.log", **kwargs):
super().__init__(**kwargs)
self.log_file = log_file
class CheckpointHook(Hook):
def __init__(self, every_n_steps: int = 100, **kwargs):
super().__init__(**kwargs)
self.every_n_steps = every_n_steps
.. testcode:: sub_config_files
:hide:
doctest_mock_class_in_main(LogHook)
doctest_mock_class_in_main(CheckpointHook)
And a config in which each hook is a separate file:
# File: hooks.yaml
hooks:
- log_hook.yaml
- checkpoint_hook.yaml# File: log_hook.yaml
class_path: LogHook
init_args:
log_file: train.log# File: checkpoint_hook.yaml
class_path: CheckpointHook
init_args:
every_n_steps: 500.. testsetup:: sub_config_files
cwd = os.getcwd()
tmpdir = tempfile.mkdtemp(prefix="_jsonargparse_doctest_")
os.chdir(tmpdir)
pathlib.Path("hooks.yaml").write_text("hooks:\n- log_hook.yaml\n- checkpoint_hook.yaml\n")
pathlib.Path("log_hook.yaml").write_text("class_path: LogHook\ninit_args:\n log_file: train.log\n")
pathlib.Path("checkpoint_hook.yaml").write_text("class_path: CheckpointHook\ninit_args:\n every_n_steps: 500\n")
.. testcleanup:: sub_config_files
os.chdir(cwd)
shutil.rmtree(tmpdir)
Then in Python:
>>> parser = ArgumentParser()
>>> parser.add_argument("--hooks", type=list[Hook], sub_configs=True) # doctest: +IGNORE_RESULT
>>> cfg = parser.parse_path("hooks.yaml")
>>> cfg.hooks[0].class_path
'__main__.LogHook'
>>> cfg.hooks[0].init_args.log_file
'train.log'
>>> cfg.hooks[1].init_args.every_n_steps
500
>>> init = parser.instantiate(cfg)
>>> isinstance(init.hooks[1], CheckpointHook)
TrueThe same is accepted from command line, i.e. --hooks=[log_hook.yaml,
checkpoint_hook.yaml], or appending one item at a time as explained in
:ref:`list-append`, i.e. --hooks+=log_hook.yaml
--hooks+=checkpoint_hook.yaml.
Relative paths inside a sub-config file are resolved with respect to the
directory of that file, so a group of config files can be moved around without
being modified. :meth:`save <.ArgumentParser.save>` with multifile=True
writes each sub-config back to its own file, keeping the original structure. To
override some values of a sub-config file in the same config, use
__include__ instead, see :ref:`including-configs`.
:ref:`subclasses-disabled` types also accept a sub-config file, whose content is
the fields of the type, without class_path and init_args. This only
applies when the type is not added as an argument group, i.e. when it is part of
a larger type, e.g. Optional[SomeDataclass] or list[SomeDataclass]. When
added as a group, the group's own config argument accepts the path, e.g.
--data=data.yaml, independent of sub_configs.
As mentioned in :ref:`dependency-injection`, callables that return instances of
classes, called instance factories, are the other way of doing dependency
injection. They are useful for classes that need parameters which are only
available after injection. In this case :meth:`instantiate
<.ArgumentParser.instantiate>` gives a partial function, which takes those
parameters and returns the instance. There are two options, Callable and
Protocol. For the Callable option, take the classes:
.. testcode:: callable
class Optimizer:
def __init__(self, params: Iterable):
self.params = params
class SGD(Optimizer):
def __init__(self, params: Iterable, lr: float):
super().__init__(params)
self.lr = lr
.. testcode:: callable
:hide:
doctest_mock_class_in_main(SGD)
A parser and its behavior could be:
>>> value = {
... "class_path": "SGD",
... "init_args": {
... "lr": 0.01,
... },
... }
>>> parser.add_argument("--optimizer", type=Callable[[Iterable], Optimizer]) # doctest: +IGNORE_RESULT
>>> cfg = parser.parse_args(["--optimizer", str(value)])
>>> cfg.optimizer
Namespace(class_path='__main__.SGD', init_args=Namespace(lr=0.01))
>>> init = parser.instantiate(cfg)
>>> optimizer = init.optimizer([1, 2, 3])
>>> isinstance(optimizer, SGD)
True
>>> optimizer.params, optimizer.lr
([1, 2, 3], 0.01)Note
When the Callable returns a class, the class_path can be given as
just the class name, if the class was imported before parsing, see
:ref:`sub-classes-command-line`.
When the same type above is used in a signature, a lambda can set the default:
.. testcode:: callable
class Model:
def __init__(
self,
optimizer: Callable[[Iterable], Optimizer] = lambda p: SGD(p, lr=0.05),
):
self.optimizer = optimizer
A parser then gives:
>>> parser.add_class_arguments(Model, 'model') >>> cfg = parser.get_defaults() >>> cfg.model.optimizer Namespace(class_path='__main__.SGD', init_args=Namespace(lr=0.05)) >>> init = parser.instantiate(cfg) >>> optimizer = init.model.optimizer([1, 2, 3]) >>> optimizer.params, optimizer.lr ([1, 2, 3], 0.05)
See :ref:`ast-resolver` for the limitations of lambda defaults in signatures. A
lambda default given to :meth:`add_argument <.ActionsContainer.add_argument>`
does not work, since there is no AST resolving. Use a dict with class_path
and init_args as default instead.
Several arguments after injection work the same way, e.g. Callable[[Iterable,
Iterable], Type] for two Iterable arguments, and Callable[[], Type]
for none.
Callable has an important limitation: its parameters are positional and
unnamed. The second option, a callable Protocol, avoids this. For the same
example:
.. testcode:: callable
class OptimizerFactory(Protocol):
def __call__(self, params: Iterable) -> Optimizer: ...
A parser using it behaves as:
.. testcode:: callable
:hide:
parser = ArgumentParser()
>>> value = {
... "class_path": "SGD",
... "init_args": {
... "lr": 0.02,
... },
... }
>>> parser.add_argument("--optimizer", type=OptimizerFactory) # doctest: +IGNORE_RESULT
>>> cfg = parser.parse_args(["--optimizer", str(value)])
>>> cfg.optimizer
Namespace(class_path='__main__.SGD', init_args=Namespace(lr=0.02))
>>> init = parser.instantiate(cfg)
>>> optimizer = init.optimizer(params=[6, 5])
>>> optimizer.params, optimizer.lr
([6, 5], 0.02)The difference is that init.optimizer() can now be called with keyword
arguments, i.e. params=[6, 5].
The help does not show the parameters of a class, since these depend on the chosen subclass. A help option that takes an import path gives them. For a parser defined as:
.. testcode::
from calendar import Calendar
from jsonargparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--calendar", type=Calendar)
the help of a subclass is printed with:
python tool.py --calendar.help calendar.TextCalendarA subclass can be given through several command line arguments:
python tool.py \
--calendar.class_path calendar.TextCalendar \
--calendar.init_args.firstweekday 1For convenience, .class_path and .init_args can be omitted, and the
subclass can be named instead of giving its full import path:
python tool.py --calendar TextCalendar --calendar.firstweekday 1Naming the subclass works for subclasses in modules that were imported before
parsing. Abstract classes and private classes (module or name starting with
'_') are not considered. The general help, python tool.py --help, lists
all the subclasses that can be given by name.
When the base class is not abstract, the class_path can be omitted, by
giving directly init_args, for example:
python tool.py --calendar.firstweekday 2would implicitly use calendar.Calendar as the class path.
A parameter that has a class as type can also have a default value. Take care with this: it can be considered bad practice and is best avoided in most cases. The problem is that classes are normally mutable, so depending on how the value is used, the default instance in the signature can end up modified. That is not what a default value should be, and leads to bugs that are hard to debug.
Since there are legitimate use cases, class instances in defaults are supported with a particular behavior. An example is:
.. testcode:: instance_default
class MyClass:
def __init__(
self,
calendar: Calendar = Calendar(firstweekday=1),
):
self.calendar = calendar
Adding this class to a parser works without issues. In limited cases the
:ref:`ast-resolver` figures out how the original default was instantiated, and
then the parse methods give a dict with class_path and init_args instead
of the instance. :meth:`instantiate <.ArgumentParser.instantiate>` creates a new
instance, which avoids the mutability problem.
When the :ref:`ast-resolver` does not support the case, or the source code is not available, the second approach is to instantiate the default with the :func:`.lazy_instance` function:
.. testcode:: instance_default
from jsonargparse.typing import lazy_instance
class MyClass:
def __init__(
self,
calendar: Calendar = lazy_instance(Calendar, firstweekday=1),
):
self.calendar = calendar
The parsed default is then again a dict with class_path and init_args,
avoiding the mutability risk.
:func:`.lazy_instance` is somewhat discouraged. Delaying the initialization of instances in a way that works in general is hard, and the current implementation is known to have some problems. Consider using :ref:`instance-factories` instead.
Note
For some classes and functions the import path can't be determined from the object alone. Using one of these as a default fails when serializing, since what gets saved in the config file is the import path. To solve this, give the module from which the object can be imported to :func:`.register_unresolvable_import_paths`.
Sometimes a class is used as a type hint with no intention of accepting
subclasses. For the parser this means that a subclass is not allowed, and that
serializing stores the init arguments directly, without class_path and
init_args. The standard Python way to express this is the :func:`.final`
decorator. For example:
.. testcode:: final_classes
from jsonargparse.typing import final
@final
class FinalClass:
def __init__(self, number: int = 0, accepted: bool = False):
...
parser = ArgumentParser()
parser.add_argument("--data", type=FinalClass)
cfg = parser.parse_args(["--data.number=8", "--data.accepted=true"])
for which a dump would give as output:
>>> print(parser.dump(cfg)) # doctest: +NORMALIZE_WHITESPACE
data:
number: 8
accepted: trueSometimes subclasses are not intended but the :func:`.final` decorator is not
used. For example, requiring a class_path for a simple x, y coordinates
dataclass would be needlessly cumbersome. For this reason jsonargparse early on
gave the same behavior to pure dataclasses (not mixed with normal classes),
attrs' define, pydantic's dataclass and pydantic's BaseModel. These
classes do technically support subclassing, so subclass support can be enabled
as described below. It is disabled by default to avoid breaking changes.
A type with subclasses disabled is added as an argument group when it is the
entire type of an argument, so each of its init args becomes an individual
argument, e.g. --data.number. This does not happen when the type is part of
a larger type, e.g. Optional[FinalClass] or list[FinalClass], since then
a single argument must accept the whole value. Either way the accepted values
are the same. A subclass spec is accepted, but only with the class_path of
the type itself, i.e. --data={"class_path": "FinalClass", "init_args":
{"number": 8}}. The class_path of a subclass is not accepted, unless
subclass support is enabled for the type as described next.
Abstract dataclass-like types are an exception. A class that has abstract
methods or that inherits from abc.ABC is not meant to be instantiated from
its own fields, so for these types subclass support is enabled by default, i.e.
only the class_path of an implementation is accepted.
The subclasses_disabled and subclasses_enabled parameters of
:func:`.set_parsing_settings` control which class types support subclasses.
subclasses_disabled accepts a list of types and functions. A given type and
its descendants have subclass support disabled. A function receives a type and
returns True if subclasses should be disabled for it.
subclasses_enabled accepts a list of types and function names. A given type
and its descendants have subclass support enabled, and take precedence over
subclasses_disabled. A function name must be one previously registered in
subclasses_disabled, and the effect is to unregister it. The disabling
functions registered by default are is_pure_dataclass,
is_pydantic_model, is_attrs_class and is_final_class. These are not
applied to abstract classes, see above.
Since subclasses_enabled takes precedence, subclass support can be kept
disabled for dataclasses but enabled for a specific one:
.. testsetup:: enable_disable_subclasses
selectors = _common.subclasses_disabled_selectors
_common.subclasses_disabled_selectors = selectors.copy()
@dataclass
class DataClassBaseType:
pass
.. testcleanup:: enable_disable_subclasses
_common.subclasses_disabled_selectors = selectors
.. testcode:: enable_disable_subclasses
from jsonargparse import set_parsing_settings
set_parsing_settings(subclasses_enabled=[DataClassBaseType])
To enable subclass support for all pydantic models:
.. testcode:: enable_disable_subclasses
set_parsing_settings(subclasses_enabled=["is_pydantic_model"])
To enable it for all dataclasses but disable it for a specific one:
.. testcode:: enable_disable_subclasses
set_parsing_settings(
subclasses_enabled=["is_pure_dataclass"],
subclasses_disabled=[DataClassBaseType],
)
Note
Enabling subclass support for types is experimental. The interface and behavior are expected to be stable, but fundamental issues may still require design changes, which could break things in future releases.
Some use cases add arguments from several classes, where a parameter gets its value computed from other arguments. The :meth:`link_arguments <.ArgumentParser.link_arguments>` parser method does this.
There are two types of links, apply_on='parse' and
apply_on='instantiate'. As the names say, the first are applied by the parse
methods and the second by :meth:`instantiate <.ArgumentParser.instantiate>`.
Since the value of a target comes from its link, a required init_args
parameter that is a target is not included in the parsed namespace, and a value
given for a target in a default is removed.
For parse links, the source keys can be single arguments or nested groups, and
the target key must be a single argument. Keys can be inside the init_args
of a subclass. The compute function takes as many positional arguments as there
are sources, and returns a value of a type compatible with the target. For
example:
.. testcode::
class Model:
def __init__(self, batch_size: int):
self.batch_size = batch_size
class Data:
def __init__(self, batch_size: int = 5):
self.batch_size = batch_size
parser = ArgumentParser()
parser.add_class_arguments(Model, "model")
parser.add_class_arguments(Data, "data")
parser.link_arguments("data.batch_size", "model.batch_size", apply_on="parse")
Only data.batch_size is given, on the command line or in a config file, and
its value is propagated to model.batch_size.
An example with the target inside a subclass:
.. testcode::
class Logger:
def __init__(self, save_dir: str | None = None):
self.save_dir = save_dir
class Trainer:
def __init__(
self,
save_dir: str | None = None,
logger: bool | Logger | list[Logger] = False,
):
self.logger = logger
parser = ArgumentParser()
parser.add_class_arguments(Trainer, "trainer")
parser.link_arguments("trainer.save_dir", "trainer.logger.init_args.save_dir")
The link is applied to the logger parameter when it is a single subclass,
and to all elements when it is a list of subclasses. If a subclass does not have
the targeted init_args parameter, the link is ignored.
For instantiate links, the sources can be class groups (added with
:meth:`add_class_arguments <.ArgumentParser.add_class_arguments>`) or subclass
arguments (see :ref:`sub-classes`). The source key is the instantiated object
itself or one of its attributes. The target key must be a single argument, and
can be inside the init_args of a subclass. The value set on the target is
validated against its type, except for targets inside init_args, which are
validated when the subclass is instantiated. :meth:`instantiate
<.ArgumentParser.instantiate>` determines the instantiation order from the
links, so all instantiate links together must form a directed acyclic graph. For
example:
.. testcode::
class Model:
def __init__(self, num_classes: int):
self.num_classes = num_classes
class Data:
def __init__(self):
self.num_classes = get_num_classes()
parser = ArgumentParser()
parser.add_class_arguments(Model, "model")
parser.add_class_arguments(Data, "data")
parser.link_arguments("data.num_classes", "model.num_classes", apply_on="instantiate")
This link makes :meth:`instantiate <.ArgumentParser.instantiate>` build Data
first, and then use its num_classes attribute to build Model.
One reason to add a parser mode (see :ref:`custom-loaders`) is to support
variable interpolation. Any library can be used for this. Without writing a
loader, an omegaconf parser mode is available out of the box when the
omegaconf package is installed.
For example, a YAML file:
server:
host: localhost
port: 80
client:
url: http://${server.host}:${server.port}/.. testsetup:: omegaconf
example = """
server:
host: localhost
port: 80
client:
url: http://${server.host}:${server.port}/
"""
cwd = os.getcwd()
tmpdir = tempfile.mkdtemp(prefix="_jsonargparse_doctest_")
os.chdir(tmpdir)
with open("example.yaml", "w") as f:
f.write(example)
.. testcleanup:: omegaconf
os.chdir(cwd)
shutil.rmtree(tmpdir)
It is parsed as:
>>> @dataclass
... class ServerOptions:
... host: str
... port: int
...
>>> @dataclass
... class ClientOptions:
... url: str
...
>>> parser = ArgumentParser(parser_mode="omegaconf")
>>> parser.add_argument("--server", type=ServerOptions) # doctest: +IGNORE_RESULT
>>> parser.add_argument("--client", type=ClientOptions) # doctest: +IGNORE_RESULT
>>> parser.add_argument("--config", action="config") # doctest: +IGNORE_RESULT
>>> cfg = parser.parse_args(["--config=example.yaml"])
>>> cfg.client.url
'http://localhost:80/'Note
parser_mode="omegaconf" supports OmegaConf's resolvers
within a single YAML file. Interpolation across several YAML files, or in a
single command line argument, is not possible.
The experimental omegaconf+ parser mode removes the limitations above.
Instead of resolving each YAML config on its own, resolving happens once at the
end of parsing. As a result, in nested subconfigs, node references must be
relative or absolute at the parser level. Alternatively,
set_parsing_settings(omegaconf_absolute_to_relative_paths=True) converts
absolute paths to relative ones while parsing, though this does not work in
every case.
Depending on community feedback, this mode may become the default omegaconf
mode eventually. That would be a breaking change, since absolute node references
would no longer work in nested subconfigs.
Parsers can also get values from environment variables. The name of a variable
is [PREFIX_][LEV__]*OPT: all upper case, a prefix, an underscore, and then
the argument name with each dot replaced by two underscores. The prefix is
env_prefix, or the prog without extension when env_prefix is unset,
or none when it is False. For the parser from :ref:`nested-namespaces`, the
shell variables are:
export APP_LEV1__OPT1='from env 1'
export APP_LEV1__OPT2='from env 2'The parser then uses these variables, unless the command line overrides them:
.. testsetup:: env
os.environ["APP_LEV1__OPT1"] = "from env 1"
os.environ["APP_LEV1__OPT2"] = "from env 2"
>>> parser = ArgumentParser(env_prefix="APP", default_env=True)
>>> parser.add_argument("--lev1.opt1", default="from default 1") # doctest: +IGNORE_RESULT
>>> parser.add_argument("--lev1.opt2", default="from default 2") # doctest: +IGNORE_RESULT
>>> cfg = parser.parse_args(["--lev1.opt1", "from arg 1"])
>>> cfg.lev1.opt1
'from arg 1'
>>> cfg.lev1.opt2
'from env 2'Note the default_env=True given to the parser. By default :meth:`parse_args
<.ArgumentParser.parse_args>` does not parse environment variables. Setting
JSONARGPARSE_DEFAULT_ENV to true or false in the shell overrides
default_env for all parsers, even ones given it explicitly.
The :meth:`parse_env <.ArgumentParser.parse_env>` method parses only environment variables, useful when there is no command line call.
If the parser has an action="config" argument, its environment variable is
parsed before all the others.
Subcommands are a modular way of defining parsers, like subcommands in argparse. In jsonargparse they behave somewhat differently, see :ref:`argparse-deviations`.
Add subcommands to a parser with :meth:`add_subcommands
<.ArgumentParser.add_subcommands>`, and then add an existing parser as a
subcommand with :meth:`add_subcommand <.ActionSubCommands.add_subcommand>`. In
the parsed namespace, the chosen subcommand is under the subcommand key (or
the key given by dest), and its arguments are nested under a key with the
subcommand's name. For example:
.. testcode::
from jsonargparse import ArgumentParser
...
parser_subcomm1 = ArgumentParser()
parser_subcomm1.add_argument("--op1")
...
parser_subcomm2 = ArgumentParser()
parser_subcomm2.add_argument("--op2")
...
parser = ArgumentParser(prog="app")
parser.add_argument("--op0")
subcommands = parser.add_subcommands()
subcommands.add_subcommand("subcomm1", parser_subcomm1)
subcommands.add_subcommand("subcomm2", parser_subcomm2)
Some parsing examples:
>>> parser.parse_args(["subcomm1", "--op1", "val1"]) # doctest: +IGNORE_RESULT
Namespace(op0=None, subcommand='subcomm1', subcomm1=Namespace(op1='val1'))
>>> parser.parse_args(["--op0", "val0", "subcomm2", "--op2", "val2"]) # doctest: +IGNORE_RESULT
Namespace(op0='val0', subcommand='subcomm2', subcomm2=Namespace(op2='val2'))Config files can also be parsed, with :meth:`parse_path
<.ArgumentParser.parse_path>` or :meth:`parse_string
<.ArgumentParser.parse_string>`. The config file does not need to give a value
for subcommand. For the parser above, a valid YAML is:
# File: example.yaml
op0: val0
subcomm1:
op1: val1Environment variables work like for :class:`.ActionParser`. For the parser
above, the variables of subcomm1 have the prefix APP_SUBCOMM1_ and those
of subcomm2 the prefix APP_SUBCOMM2_. The subcommand itself is chosen
with APP_SUBCOMMAND.
Several levels of subcommands are possible, with one requirement: they must be added in order of level. That is, first call :meth:`add_subcommands <.ArgumentParser.add_subcommands>` and :meth:`add_subcommand <.ActionSubCommands.add_subcommand>` for the first level, only then for the second level, and so on.
The :class:`.ActionJsonSchema` class parses and validates values with a JSON
Schema. It requires the jsonschema
package, which is not part of the minimal install. Install jsonargparse with the
jsonschema extra, see :ref:`installation`.
See the JSON Schema documentation to learn how to write a schema.
jsonargparse currently uses Draft7Validator. An example:
>>> from jsonargparse import ActionJsonSchema
>>> schema = {
... "type": "object",
... "properties": {
... "price": {"type": "number"},
... "name": {"type": "string"},
... },
... }
>>> parser = ArgumentParser()
>>> parser.add_argument("--json", action=ActionJsonSchema(schema=schema)) # doctest: +IGNORE_RESULT
>>> parser.parse_args(["--json", '{"price": 1.5, "name": "cookie"}'])
Namespace(json={'price': 1.5, 'name': 'cookie'})The value can also be a path to a JSON/YAML file, which is loaded and validated
against the schema. Default values defined in the schema initialize the config
values that are not given. In the help string, "%s" is replaced by the
schema.
Jsonnet support requires the jsonschema and jsonnet packages, which are not part of the
minimal install. Install jsonargparse with the jsonnet extra, see
:ref:`installation`.
With parser_mode='jsonnet', :meth:`parse_args <.ArgumentParser.parse_args>`,
:meth:`parse_path <.ArgumentParser.parse_path>` and :meth:`parse_string
<.ArgumentParser.parse_string>` expect Jsonnet instead:
.. testsetup:: jsonnet
cwd = os.getcwd()
tmpdir = tempfile.mkdtemp(prefix="_jsonargparse_doctest_")
os.chdir(tmpdir)
with open("example.jsonnet", "w") as f:
f.write("{}\n")
.. testcleanup:: jsonnet
os.chdir(cwd)
shutil.rmtree(tmpdir)
.. testcode:: jsonnet
from jsonargparse import ArgumentParser
parser = ArgumentParser(parser_mode="jsonnet")
parser.add_argument("--config", action="config")
cfg = parser.parse_args(["--config", "example.jsonnet"])
Jsonnet files are often parametrized and need external variables. For these, instead of changing the parser mode, use the :class:`.ActionJsonnet` class. It defines an argument that takes a Jsonnet string or a path to a Jsonnet file, plus another argument as the source of the external variables, given as a path to, or a string with, a JSON dictionary:
.. testcode:: jsonnet
from jsonargparse import ArgumentParser, ActionJsonnet
parser = ArgumentParser()
parser.add_argument("--in_ext_vars", type=dict)
parser.add_argument("--in_jsonnet", action=ActionJsonnet(ext_vars="in_ext_vars"))
For example, if a Jsonnet file required some external variable param, then
the Jsonnet and the external variable could be given as:
.. testcode:: jsonnet
cfg = parser.parse_args(["--in_ext_vars", '{"param": 123}', "--in_jsonnet", "example.jsonnet"])
The external variables argument must come before the Jsonnet path, so that the dictionary already exists when the Jsonnet is parsed.
:class:`.ActionJsonnet` also accepts a JSON Schema, and then validates the Jsonnet against it right after parsing.
An existing parser, needed standalone somewhere in the code, can be reused to parse an inner node of a larger parser. The :class:`.ActionParser` class defines such an argument:
.. testcode::
from jsonargparse import ArgumentParser, ActionParser
inner_parser = ArgumentParser(prog="app1")
inner_parser.add_argument("--op1")
...
outer_parser = ArgumentParser(prog="app2")
outer_parser.add_argument("--inner.node", title="Inner node title", action=ActionParser(parser=inner_parser))
In a config file, the value of the node can be the node itself, or the path to a
file that is loaded and parsed with the inner parser. Parsing a complete config
file with action="config" naturally parses the inner nodes correctly.
Note the title given when adding inner_parser. In the help, added
parsers are shown as independent groups starting with that title. A
description can also be given.
For environment variables, the prefix of the outer parser is used for the leaf
nodes of the inner parser. In the example above, inner_parser on its own
checks APP1_OP1 to populate option op1, while outer_parser checks
APP2_INNER__NODE__OP1 to populate inner.node.op1.
An important detail is that the parsers given to :class:`.ActionParser` are modified internally. So to use a parser both standalone and as an inner node, write a function that creates it, and call that function in each place, so that each one gets its own instance.
From a parser, jsonargparse can generate artifacts that describe what the parser accepts, so that other tools can validate and complete configs and command lines. The supported completion types are:
jsonschema: a JSON Schema that describes the config files that the parser accepts. Always available.shtab-*: a completion script for a given shell, e.g.shtab-bash. Available when the shtab package is installed.
Both are generated with the :meth:`.ArgumentParser.get_completion_script` method, or from the command line, see :ref:`print-completion-argument`.
Completion at runtime in the shell, which jsonargparse supports through the argcomplete package, is covered further down in :ref:`argcomplete`. It involves no generated artifact.
To enable generation of completion scripts via the command line, use
:func:`.set_parsing_settings` with add_print_completion_argument=True. This
adds a --print_completion argument to top-level parsers (not subparsers),
which accepts the completion types listed above.
.. testcode::
from jsonargparse import set_parsing_settings
set_parsing_settings(add_print_completion_argument=True)
Without changing Python code, the argument is also added by setting the
environment variable JSONARGPARSE_ADD_PRINT_COMPLETION_ARGUMENT=true.
The jsonschema completion type gives a JSON Schema (draft 2020-12) that describes the config files
accepted by the parser.
.. testcode::
parser = ArgumentParser(prog="example")
parser.add_argument("--bool", type=bool)
schema = parser.get_completion_script("jsonschema")
# schema now contains the JSON schema
The equivalent from the command line is:
$ example.py --print_completion=jsonschema > schema.jsonThis schema is useful as a machine-readable interface for tools. For example:
- IDE/editor assistance (autocompletion, hints, and inline validation).
- Config contract checks in CI pipelines.
- Generating documentation from parser structure.
To get validation and autocompletion for a config file in an editor such as
Visual Studio Code,
the config can point to the generated schema with a $schema key:
{
"$schema": "./schema.json",
"bool": true
}The key is accepted in any config that a parser loads, :ref:`sub-config-files`
included, and it is removed before parsing, so it never becomes part of the
parsed namespace. Accordingly, every object in the schema that describes a
config accepts the key. With config_include_enabled, these objects also
accept the __include__ key, see :ref:`including-configs`, and require none
of their keys, since a config that includes others, or is included, can be
partial.
A config that is not the root, e.g. a sub-config file or an included config,
needs the part of the schema for where it goes. Since not all editors support a
JSON pointer in $schema, the config can point to a small schema that
references that part:
{"$ref": "schema.json#/$defs/mymodule.MyModel/properties/init_args"}The schema is derived from the same information that the --help output is
based on, so it includes:
- The structure of nested keys, i.e. argument groups and subclasses-disabled types become objects, and which of their keys are required.
- The accepted types, including unions, literals, enums, containers and the
restrictions of types such as :class:`.PositiveInt` and :class:`.Email`. For
the plain argparse actions, which have no type hint, this is what the action
gives, e.g. a boolean for
store_true, an integer forcount, the possible values forstore_constand an array forappend. - The defaults of the arguments. Three kinds are left out: the required ones,
the ones whose default is
argparse.SUPPRESS, since not giving those leaves no key, and the unset ones, see :ref:`unset-values`. Withoutunset_sentinel, aNonedefault counts as unset, sonullis never described as a default. With it, an explicitdefault=Noneis described, as long as the type acceptsnull. - Descriptions taken from the docstrings of the classes and functions that the
arguments come from, or from the
helpgiven toadd_argument. - For subclass types, one entry per known subclass, each with a
class_pathfixed to that subclass and aninit_argsobject describing the accepted init parameters of that specific class. - For parsers with subcommands, one object per subcommand and a
subcommandkey. This key is optional, since a config that has a single subcommand block implies it, and when a config has several blocks the subcommand can be given as a command line argument.
Subclasses and types that are used in more than one place are added once to
$defs and referenced with $ref, which also makes recursive types work.
Each known subclass has its own definition named by its import path, e.g.
mymodule.MyModel. When an argument accepts different init parameters than
the class has, e.g. due to skip, its definition is a variant named after the
first argument that uses it, e.g. mymodule.MyModel@model.
The schema is meant to accept what the parser accepts, but for subclass types it
is stricter. A string is accepted, since it can be a class path or a path to a
sub-config file. An object is only accepted for the known subclasses, i.e. one
with class_path and init_args (required only for the subclasses that
have a required init parameter). Accepting any class_path would keep tools
from suggesting the known subclasses and from pointing out a class path that has
a typo or is not the accepted import path, and its init_args would go
undescribed. Any class_path is accepted only when a type has no known
subclass, and then its init_args are not described. Likewise,
dict_kwargs is only accepted for subclasses that have an unresolved
**kwargs, so a skipped parameter given there is rejected even though the
parser accepts it, see :ref:`unresolved-parameters`.
A union that has a subtype accepting anything, i.e. Any or an unvalidated
type, is kept as {"anyOf": [..., {}]} instead of the equivalent {}, so
that tools still have the other subschemas to describe and complete against. The
exception is when another subtype restricts the keys of an object, e.g. a
subclass, dataclass or typed dict. Then the subschemas that accept any object,
i.e. those from Any, dict and unvalidated types, are removed. This makes
the schema stricter than the parser, but in exchange mistakes in the keys are
pointed out instead of going unnoticed.
Note
The subclasses of a type that the schema includes are the ones known to Python when the schema is generated, i.e. only those whose modules happen to have been imported.
Note
The jsonschema completion type is experimental. The details of the
generated schema might change in non-major releases.
The shtab-* completion types give a shell completion script, using
shtab- followed by the shell name, e.g. shtab-bash or shtab-zsh.
For shtab there is no need to set complete/choices on the parser
actions, or to call shtab.add_argument_to. The only requirement
is to install shtab, directly or with the shtab extra, see
:ref:`installation`.
.. testcode::
parser = ArgumentParser(prog="example")
parser.add_argument("--bool", type=bool)
script = parser.get_completion_script("shtab-bash", preambles=[])
# script now contains the bash completion script
Warning
After calling :meth:`.get_completion_script` for an shtab-* completion
type, the parser instance is invalidated and cannot be used for parsing
arguments.
From the command line, for example in Linux to enable bash completions for all users, as root:
# example.py --print_completion=shtab-bash > /etc/bash_completion.d/exampleWithout installing, a script can be tested by sourcing or evaluating it:
$ eval "$(example.py --print_completion=shtab-bash)"The scripts complete when there are choices, and also print guidance for the user. Take for example the parser:
.. testsetup:: tab_completion
sys.argv = [""]
.. testcode:: tab_completion
#!/usr/bin/env python3
from jsonargparse import ArgumentParser
parser = ArgumentParser()
parser.add_argument("--bool", type=bool | None)
parser.parse_args()
The completion prints the type of the argument, how many options match, and then the matching choices. If only one option matches, the value is completed without printing guidance, unless nothing has been typed and the type accepts values other than the choices. For example:
$ example.py --bool <TAB><TAB>
Expected type: bool | None; 3/3 matched choices
true false null
$ example.py --bool f<TAB>
$ example.py --bool falseNote
The guidance requires bash 4 or newer. With older versions, e.g. bash 3.2 in
macOS, no guidance is printed, and the choices of types that accept other
values, like int | None, are only completed after typing a prefix.
For subclass types, the import paths of the known subclasses are completed, both
for the option that selects the class and for the --*.help option. The
init_args of the known subclasses are completed too, with guidance saying
which subclasses accept each one. For example:
$ example.py --cls <TAB><TAB>
Expected type: BaseClass; 3/3 matched choices
some.module.BaseClass other.module.SubclassA
other.module.SubclassB
$ example.py --cls other.module.SubclassA --cls.<TAB><TAB>
--cls.param1 --cls.param2
$ example.py --cls other.module.SubclassA --cls.param2 <TAB><TAB>
Expected type: int; Accepted by subclasses: SubclassAAnalogously, for subclasses-disabled types, TypedDict and NamedTuple,
the fields or keys are completed, as well as the values that they accept, e.g.:
$ example.py --data.<TAB><TAB>
--data.verbose --data.mode
$ example.py --data.verbose <TAB><TAB>
Expected type: bool; 2/2 matched choices
true falseFor argcomplete there is no need to implement completer functions or to call
argcomplete.autocomplete, since
:meth:`parse_args <.ArgumentParser.parse_args>` does it automatically. The only
requirement is to install argcomplete, directly or with the argcomplete
extra, see :ref:`installation`.
The shell completion can be enabled globally for all argcomplete compatible tools or for each individual tool.
Using the same bool example, activate completion and use it as follows:
$ eval "$(register-python-argcomplete example.py)"
$ example.py --bool <TAB><TAB>
false null true
$ example.py --bool f<TAB>
$ example.py --bool falseTo keep a high level of compatibility with argparse, the argparse tests from the
Python standard library are run against jsonargparse. Some are skipped because
they cover intentional deviations, are not relevant for jsonargparse, or are
still under investigation and may be enabled later. Which tests to skip is
configured in the argparse_tests_generate.py file.
The following sections describe the main intentional deviations from argparse. In addition, deprecated features in argparse are not supported.
In argparse, a parser with subcommands merges the main parser and subparser options into a single flat namespace. Since jsonargparse supports nested namespaces, subcommand options are deliberately placed in their own subnamespace, which is clearer and more convenient.
In argparse, add_subparsers needs the dest parameter for the name of the
chosen subcommand to appear in the namespace. In jsonargparse it is there by
default, without any extra parameter.
To promote modularity, jsonargparse subparsers are created independently, just like the main parser, and then added as a subcommand. This makes it possible to write functions that return a subparser, usable both standalone and as a subcommand. In argparse, subparsers are tightly coupled to the main parser and can't be defined independently. To avoid confusion with argparse, the method names for adding subcommands are intentionally different.
To migrate from argparse to jsonargparse, instead of:
.. testcode::
import argparse
parser = argparse.ArgumentParser()
subparsers = parser.add_subparsers()
subparser1 = subparsers.add_parser("foo")
subparser1.add_argument("--key")
...
the code becomes:
.. testcode::
import jsonargparse
subparser1 = jsonargparse.ArgumentParser()
subparser1.add_argument("--key")
...
parser = jsonargparse.ArgumentParser()
subcommands = parser.add_subcommands()
subcommands.add_subcommand("foo", subparser1)
Argparse has a parse_known_args method, which parses leniently by ignoring
unrecognized arguments. jsonargparse is designed for complex cases: several
subcommands, many arguments derived from signatures, class instantiation and
config files. Ignoring unrecognized arguments would make errors, such as a typo
in a config file, harder to notice. For this reason parse_known_args is
intentionally not supported.
Unlike argparse, abbreviated options, e.g. --max for --max_epochs, are
not accepted by default, since a new parameter can make an abbreviation
ambiguous or change its meaning. :ref:`tab-completion` is a robust alternative.
For argparse behavior give allow_abbrev=True to the parser, or enable it
globally, which also applies to parsers created by other code:
.. testcode::
from jsonargparse import set_parsing_settings
set_parsing_settings(allow_abbrev=True)
.. testcleanup::
set_parsing_settings(allow_abbrev=False)
Without changing Python code, the same is achieved by setting the environment
variable JSONARGPARSE_ALLOW_ABBREV=true. Neither applies to parsers given
allow_abbrev explicitly.
In argparse, the type parameter of an argument can be a user-defined
function or class. A function is supported in jsonargparse, with the extra
requirement that it must be idempotent, i.e. applying it twice or more does not
change the value. For example:
.. testcode::
# either int larger than zero or 'off' string
def int_or_off(x):
return x if x == "off" else int(x)
parser.add_argument("--int_or_off", type=int_or_off)
A class as the type conflicts with the signature and type hint support that is central to jsonargparse, so it does not work the same way as in argparse. The recommended alternative is to implement a custom type, see :ref:`custom-types`.
When a parse method fails, by default it prints a short message and exits with a
non-zero code. During development this is not enough information to find the
root of the problem. Setting the JSONARGPARSE_DEBUG environment variable to
true changes this, without touching the source code: an
:class:`.ArgumentError` is raised, the full stack trace is printed and parsers
without a logger log at debug level. Debug logs can include the raw input given
to parsers, secrets included, so only enable them for troubleshooting.
The parsers log some basic events, though this is disabled by default. To enable
it, set the logger argument when creating an :class:`.ArgumentParser`. The
intended use is to give the logger object that the whole application uses. For
convenience, logger can also be True to enable a default logger, a
string with the name of the logger, or a dictionary with the name and the level,
e.g. {"name": "myapp", "level": "ERROR"}.