Skip to content
Permalink

Comparing changes

Choose two branches to see what’s changed or to start a new pull request. If you need to, you can also or learn more about diff comparisons.

Open a pull request

Create a new pull request by comparing changes across two branches. If you need to, you can also . Learn more about diff comparisons here.
base repository: omni-us/jsonargparse
Failed to load repositories. Confirm that selected base ref is valid, then try again.
Loading
base: v3.2.1
Choose a base ref
...
head repository: omni-us/jsonargparse
Failed to load repositories. Confirm that selected head ref is valid, then try again.
Loading
compare: v3.3.0
Choose a head ref
  • 4 commits
  • 31 files changed
  • 1 contributor

Commits on Jan 7, 2021

  1. Copy the full SHA
    f2548fc View commit details
  2. Copy the full SHA
    3f34d02 View commit details

Commits on Jan 8, 2021

  1. Copy the full SHA
    2abd760 View commit details
  2. Copy the full SHA
    b34406f View commit details
2 changes: 1 addition & 1 deletion .bumpversion.cfg
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
[bumpversion]
current_version = 3.2.1
current_version = 3.3.0
commit = True
tag = True
tag_name = v{new_version}
2 changes: 1 addition & 1 deletion .sonarcloud.properties
Original file line number Diff line number Diff line change
@@ -1,2 +1,2 @@
sonar.sources=jsonargparse
sonar.projectVersion=3.2.1
sonar.projectVersion=3.3.0
9 changes: 9 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
@@ -9,6 +9,15 @@ follow `Semantic Versioning <https://semver.org/>`_
only be introduced in major versions with advance notice in the **Deprecated**
section of releases.


v3.3.0 (2021-01-08)
-------------------

Added
^^^^^
- New add_subclass_arguments method to add as type with a specific help option.


v3.2.1 (2020-12-30)
-------------------

61 changes: 47 additions & 14 deletions README.rst
Original file line number Diff line number Diff line change
@@ -552,20 +552,20 @@ Some notes about this support are:
be shown as :code:`type: Union[str, null], default: null`.


Classes as type
===============

Using an arbitrary class as a type is also possible, though it requires a bit
explanation. In the config file or environment variable or command line
argument, a class is represented by a dictionary with a :code:`class_path` entry
indicating the dot notation expression to import the class, and optionally some
:code:`init_args` that would be used to instantiate it. When parsing it will be
checked that the class can be imported, that it is a subclass of the type and
that :code:`init_args` values correspond to valid arguments to instantiate.
After parsing, the config object will include the :code:`class_path` and
:code:`init_args` entries. To get a config object with all subclasses
instantiated, the :py:meth:`.ArgumentParser.instantiate_subclasses` method is
used.
Class type and sub-classes
==========================

It is also possible to use an arbitrary class as a type, such that the argument
accepts this class or any derived subclass. In the config file or environment
variable or command line argument, a class is represented by a dictionary with a
:code:`class_path` entry indicating the dot notation expression to import the
class, and optionally some :code:`init_args` that would be used to instantiate
it. When parsing it will be checked that the class can be imported, that it is a
subclass of the type and that :code:`init_args` values correspond to valid
arguments to instantiate. After parsing, the config object will include the
:code:`class_path` and :code:`init_args` entries. To get a config object with
all subclasses instantiated, the
:py:meth:`.ArgumentParser.instantiate_subclasses` method is used.

A simple example would be having some config file :code:`config.yaml` as:

@@ -594,6 +594,39 @@ In the example the :code:`class_path` points to the same class used for the
type. But a subclass of :code:`Calendar` with an extended list of init
parameters would also work.

When using any of the methods described in :ref:`classes-methods-functions`,
each argument with a class as the type can be given using a :code:`class_path`
and :code:`init_args` pair.

There is also another method
:py:meth:`.SignatureArguments.add_subclass_arguments` which does the same as
:code:`add_argument` in the example above, but has some added benefits: 1) the
argument is added in a new group automatically using docstrings; 2) the argument
values can be given in an independent config file by specifying a path to it; 3)
by default has useful :code:`metavar` and :code:`help` strings; and 4) a special
:code:`--*.help` argument is added that can be used to show the expected
:code:`init_args` details for a specific given :code:`class_path`. Take for
example a tool defined as:

.. code-block:: python
from calendar import Calendar
from jsonargparse import ArgumentParser
...
parser = ArgumentParser()
parser.add_subclass_arguments(Calendar, 'calendar')
...
cfg = parser.parse_args()
...
If there is some subclass of :code:`Calendar` which can be imported from
:code:`mycode.MyCalendar`, then it would be possible to see the corresponding
:code:`init_args` details by running the tool from the command line as:

.. code-block:: bash
python tool.py --calendar.help mycode.MyCalendar
.. _sub-commands:

2 changes: 1 addition & 1 deletion docs/.buildinfo
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
# Sphinx build info version 1
# This file hashes the configuration used when building these files. When it is not found, a full rebuild will be done.
config: 878f0894dcf8f9c240b49109bd5b5150
config: a43664cc7a454629e2ba7b08e921dc95
tags: 645f666f9bcd5a90fca523b33c5a78b7
47 changes: 30 additions & 17 deletions docs/CHANGELOG.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Changelog &mdash; jsonargparse 3.2.1 documentation</title>
<title>Changelog &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


@@ -87,23 +87,27 @@
<!-- Local TOC -->
<div class="local-toc"><ul>
<li><a class="reference internal" href="#">Changelog</a><ul>
<li><a class="reference internal" href="#v3-2-1-2020-12-30">v3.2.1 (2020-12-30)</a><ul>
<li><a class="reference internal" href="#v3-3-0-2021-01-08">v3.3.0 (2021-01-08)</a><ul>
<li><a class="reference internal" href="#added">Added</a></li>
</ul>
</li>
<li><a class="reference internal" href="#v3-2-1-2020-12-30">v3.2.1 (2020-12-30)</a><ul>
<li><a class="reference internal" href="#id2">Added</a></li>
<li><a class="reference internal" href="#changed">Changed</a></li>
<li><a class="reference internal" href="#fixed">Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#v3-1-0-2020-12-09">v3.1.0 (2020-12-09)</a><ul>
<li><a class="reference internal" href="#id2">Added</a></li>
<li><a class="reference internal" href="#id3">Added</a></li>
</ul>
</li>
<li><a class="reference internal" href="#v3-0-1-2020-12-02">v3.0.1 (2020-12-02)</a><ul>
<li><a class="reference internal" href="#id3">Fixed</a></li>
<li><a class="reference internal" href="#id4">Fixed</a></li>
</ul>
</li>
<li><a class="reference internal" href="#v3-0-0-2020-12-01">v3.0.0 (2020-12-01)</a><ul>
<li><a class="reference internal" href="#id4">Added</a></li>
<li><a class="reference internal" href="#id5">Changed</a></li>
<li><a class="reference internal" href="#id5">Added</a></li>
<li><a class="reference internal" href="#id6">Changed</a></li>
<li><a class="reference internal" href="#deprecated">Deprecated</a></li>
</ul>
</li>
@@ -182,11 +186,20 @@
(<code class="docutils literal notranslate"><span class="pre">&lt;major&gt;.&lt;minor&gt;.&lt;patch&gt;</span></code>). Backward incompatible (breaking) changes will
only be introduced in major versions with advance notice in the <strong>Deprecated</strong>
section of releases.</p>
<div class="section" id="v3-2-1-2020-12-30">
<h2>v3.2.1 (2020-12-30)<a class="headerlink" href="#v3-2-1-2020-12-30" title="Permalink to this headline"></a></h2>
<div class="section" id="v3-3-0-2021-01-08">
<h2>v3.3.0 (2021-01-08)<a class="headerlink" href="#v3-3-0-2021-01-08" title="Permalink to this headline"></a></h2>
<div class="section" id="added">
<h3>Added<a class="headerlink" href="#added" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>New add_subclass_arguments method to add as type with a specific help option.</p></li>
</ul>
</div>
</div>
<div class="section" id="v3-2-1-2020-12-30">
<h2>v3.2.1 (2020-12-30)<a class="headerlink" href="#v3-2-1-2020-12-30" title="Permalink to this headline"></a></h2>
<div class="section" id="id2">
<h3>Added<a class="headerlink" href="#id2" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Automatic Optional for arguments with default None #30.</p></li>
<li><p>CLI now supports running methods from classes.</p></li>
<li><p>Signature arguments can now be loaded from independent config files #32.</p></li>
@@ -214,8 +227,8 @@ <h3>Fixed<a class="headerlink" href="#fixed" title="Permalink to this headline">
</div>
<div class="section" id="v3-1-0-2020-12-09">
<h2>v3.1.0 (2020-12-09)<a class="headerlink" href="#v3-1-0-2020-12-09" title="Permalink to this headline"></a></h2>
<div class="section" id="id2">
<h3>Added<a class="headerlink" href="#id2" title="Permalink to this headline"></a></h3>
<div class="section" id="id3">
<h3>Added<a class="headerlink" href="#id3" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Support for multiple levels of subcommands #29.</p></li>
<li><p>Default description of subcommands explaining use of –help.</p></li>
@@ -224,8 +237,8 @@ <h3>Added<a class="headerlink" href="#id2" title="Permalink to this headline">¶
</div>
<div class="section" id="v3-0-1-2020-12-02">
<h2>v3.0.1 (2020-12-02)<a class="headerlink" href="#v3-0-1-2020-12-02" title="Permalink to this headline"></a></h2>
<div class="section" id="id3">
<h3>Fixed<a class="headerlink" href="#id3" title="Permalink to this headline"></a></h3>
<div class="section" id="id4">
<h3>Fixed<a class="headerlink" href="#id4" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>add_class_arguments incorrectly added arguments from <code class="code docutils literal notranslate"><span class="pre">__call__</span></code> instead
of <code class="code docutils literal notranslate"><span class="pre">__init__</span></code> for callable classes.</p></li>
@@ -234,8 +247,8 @@ <h3>Fixed<a class="headerlink" href="#id3" title="Permalink to this headline">¶
</div>
<div class="section" id="v3-0-0-2020-12-01">
<h2>v3.0.0 (2020-12-01)<a class="headerlink" href="#v3-0-0-2020-12-01" title="Permalink to this headline"></a></h2>
<div class="section" id="id4">
<h3>Added<a class="headerlink" href="#id4" title="Permalink to this headline"></a></h3>
<div class="section" id="id5">
<h3>Added<a class="headerlink" href="#id5" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>Functions to add arguments from classes, methods and functions.</p></li>
<li><p>CLI function that allows creating a line command line interface with a single
@@ -247,8 +260,8 @@ <h3>Added<a class="headerlink" href="#id4" title="Permalink to this headline">¶
<li><p>Support argcomplete for tab completion of arguments.</p></li>
</ul>
</div>
<div class="section" id="id5">
<h3>Changed<a class="headerlink" href="#id5" title="Permalink to this headline"></a></h3>
<div class="section" id="id6">
<h3>Changed<a class="headerlink" href="#id6" title="Permalink to this headline"></a></h3>
<ul class="simple">
<li><p>ArgumentParsers by default now use as error_handler the
usage_and_exit_error_handler.</p></li>
4 changes: 2 additions & 2 deletions docs/_modules/index.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Overview: module code &mdash; jsonargparse 3.2.1 documentation</title>
<title>Overview: module code &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


57 changes: 46 additions & 11 deletions docs/_modules/jsonargparse/actions.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.actions &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.actions &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


@@ -154,9 +154,10 @@ <h1>Source code for jsonargparse.actions</h1><div class="highlight"><pre>
<span class="kn">import</span> <span class="nn">re</span>
<span class="kn">import</span> <span class="nn">sys</span>
<span class="kn">import</span> <span class="nn">yaml</span>
<span class="kn">import</span> <span class="nn">inspect</span>
<span class="kn">import</span> <span class="nn">argparse</span>
<span class="kn">from</span> <span class="nn">enum</span> <span class="k">import</span> <span class="n">Enum</span>
<span class="kn">from</span> <span class="nn">argparse</span> <span class="k">import</span> <span class="n">Namespace</span><span class="p">,</span> <span class="n">Action</span><span class="p">,</span> <span class="n">SUPPRESS</span><span class="p">,</span> <span class="n">_StoreAction</span><span class="p">,</span> <span class="n">_SubParsersAction</span>
<span class="kn">from</span> <span class="nn">argparse</span> <span class="k">import</span> <span class="n">Namespace</span><span class="p">,</span> <span class="n">Action</span><span class="p">,</span> <span class="n">SUPPRESS</span><span class="p">,</span> <span class="n">_StoreAction</span><span class="p">,</span> <span class="n">_HelpAction</span><span class="p">,</span> <span class="n">_SubParsersAction</span>

<span class="kn">from</span> <span class="nn">.optionals</span> <span class="k">import</span> <span class="n">get_config_read_mode</span><span class="p">,</span> <span class="n">FilesCompleterMethod</span>
<span class="kn">from</span> <span class="nn">.typing</span> <span class="k">import</span> <span class="n">restricted_number_type</span>
@@ -166,6 +167,7 @@ <h1>Source code for jsonargparse.actions</h1><div class="highlight"><pre>
<span class="n">ParserError</span><span class="p">,</span>
<span class="n">namespace_to_dict</span><span class="p">,</span>
<span class="n">dict_to_namespace</span><span class="p">,</span>
<span class="n">import_object</span><span class="p">,</span>
<span class="n">Path</span><span class="p">,</span>
<span class="n">_load_config</span><span class="p">,</span>
<span class="n">_flat_namespace_to_dict</span><span class="p">,</span>
@@ -220,6 +222,17 @@ <h1>Source code for jsonargparse.actions</h1><div class="highlight"><pre>
<span class="k">return</span> <span class="kc">False</span>


<span class="k">def</span> <span class="nf">_remove_actions</span><span class="p">(</span><span class="n">parser</span><span class="p">,</span> <span class="n">types</span><span class="p">):</span>

<span class="k">def</span> <span class="nf">remove</span><span class="p">(</span><span class="n">actions</span><span class="p">):</span>
<span class="n">rm_actions</span> <span class="o">=</span> <span class="p">[</span><span class="n">a</span> <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="n">actions</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">types</span><span class="p">)]</span>
<span class="k">for</span> <span class="n">action</span> <span class="ow">in</span> <span class="n">rm_actions</span><span class="p">:</span>
<span class="n">actions</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">action</span><span class="p">)</span>

<span class="n">remove</span><span class="p">(</span><span class="n">parser</span><span class="o">.</span><span class="n">_actions</span><span class="p">)</span>
<span class="n">remove</span><span class="p">(</span><span class="n">parser</span><span class="o">.</span><span class="n">_action_groups</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">_group_actions</span><span class="p">)</span>


<div class="viewcode-block" id="ActionConfigFile"><a class="viewcode-back" href="../../index.html#jsonargparse.actions.ActionConfigFile">[docs]</a><span class="k">class</span> <span class="nc">ActionConfigFile</span><span class="p">(</span><span class="n">Action</span><span class="p">,</span> <span class="n">FilesCompleterMethod</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Action to indicate that an argument is a configuration file or a configuration string.&quot;&quot;&quot;</span>

@@ -304,6 +317,35 @@ <h1>Source code for jsonargparse.actions</h1><div class="highlight"><pre>
<span class="k">return</span> <span class="bp">self</span><span class="o">.</span><span class="n">_load_config</span><span class="p">(</span><span class="n">value</span><span class="p">)</span>


<span class="k">class</span> <span class="nc">_ActionHelpClassPath</span><span class="p">(</span><span class="n">Action</span><span class="p">):</span>

<span class="k">def</span> <span class="nf">__init__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="s1">&#39;baseclass&#39;</span> <span class="ow">in</span> <span class="n">kwargs</span><span class="p">:</span>
<span class="n">_check_unknown_kwargs</span><span class="p">(</span><span class="n">kwargs</span><span class="p">,</span> <span class="p">{</span><span class="s1">&#39;baseclass&#39;</span><span class="p">})</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_baseclass</span> <span class="o">=</span> <span class="n">kwargs</span><span class="p">[</span><span class="s1">&#39;baseclass&#39;</span><span class="p">]</span>
<span class="k">else</span><span class="p">:</span>
<span class="bp">self</span><span class="o">.</span><span class="n">_baseclass</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s1">&#39;_baseclass&#39;</span><span class="p">)</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">&#39;help&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;Show the help for the given class path and exit.&#39;</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">&#39;metavar&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="s1">&#39;CLASS&#39;</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">&#39;default&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="n">SUPPRESS</span>
<span class="nb">super</span><span class="p">()</span><span class="o">.</span><span class="fm">__init__</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>

<span class="k">def</span> <span class="nf">__call__</span><span class="p">(</span><span class="bp">self</span><span class="p">,</span> <span class="o">*</span><span class="n">args</span><span class="p">,</span> <span class="o">**</span><span class="n">kwargs</span><span class="p">):</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">args</span><span class="p">)</span> <span class="o">==</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">kwargs</span><span class="p">[</span><span class="s1">&#39;_baseclass&#39;</span><span class="p">]</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_baseclass</span>
<span class="k">return</span> <span class="n">_ActionHelpClassPath</span><span class="p">(</span><span class="o">**</span><span class="n">kwargs</span><span class="p">)</span>
<span class="n">val_class</span> <span class="o">=</span> <span class="n">import_object</span><span class="p">(</span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">])</span>
<span class="k">if</span> <span class="ow">not</span> <span class="n">_issubclass</span><span class="p">(</span><span class="n">val_class</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">_baseclass</span><span class="p">):</span>
<span class="k">raise</span> <span class="ne">TypeError</span><span class="p">(</span><span class="s1">&#39;Class &quot;&#39;</span><span class="o">+</span><span class="n">args</span><span class="p">[</span><span class="mi">2</span><span class="p">]</span><span class="o">+</span><span class="s1">&#39;&quot; is not a subclass of &#39;</span><span class="o">+</span><span class="bp">self</span><span class="o">.</span><span class="n">_baseclass</span><span class="o">.</span><span class="vm">__name__</span><span class="p">)</span>
<span class="n">dest</span> <span class="o">=</span> <span class="n">re</span><span class="o">.</span><span class="n">sub</span><span class="p">(</span><span class="s1">&#39;</span><span class="se">\\</span><span class="s1">.help$&#39;</span><span class="p">,</span> <span class="s1">&#39;&#39;</span><span class="p">,</span> <span class="bp">self</span><span class="o">.</span><span class="n">dest</span><span class="p">)</span> <span class="o">+</span> <span class="s1">&#39;.init_args&#39;</span>
<span class="n">ArgumentParser</span> <span class="o">=</span> <span class="n">import_object</span><span class="p">(</span><span class="s1">&#39;jsonargparse.ArgumentParser&#39;</span><span class="p">)</span>
<span class="n">tmp</span> <span class="o">=</span> <span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">tmp</span><span class="o">.</span><span class="n">add_class_arguments</span><span class="p">(</span><span class="n">val_class</span><span class="p">,</span> <span class="n">dest</span><span class="p">)</span>
<span class="n">_remove_actions</span><span class="p">(</span><span class="n">tmp</span><span class="p">,</span> <span class="p">(</span><span class="n">_HelpAction</span><span class="p">,</span> <span class="n">_ActionPrintConfig</span><span class="p">))</span>
<span class="n">tmp</span><span class="o">.</span><span class="n">print_help</span><span class="p">()</span>
<span class="n">args</span><span class="p">[</span><span class="mi">0</span><span class="p">]</span><span class="o">.</span><span class="n">exit</span><span class="p">()</span>


<div class="viewcode-block" id="ActionYesNo"><a class="viewcode-back" href="../../index.html#jsonargparse.actions.ActionYesNo">[docs]</a><span class="k">class</span> <span class="nc">ActionYesNo</span><span class="p">(</span><span class="n">Action</span><span class="p">):</span>
<span class="sd">&quot;&quot;&quot;Paired options --{yes_prefix}opt, --{no_prefix}opt to set True or False respectively.&quot;&quot;&quot;</span>

@@ -566,14 +608,7 @@ <h1>Source code for jsonargparse.actions</h1><div class="highlight"><pre>

<span class="n">parser</span><span class="o">.</span><span class="n">prog</span> <span class="o">=</span> <span class="s1">&#39;</span><span class="si">%s</span><span class="s1"> [options] </span><span class="si">%s</span><span class="s1">&#39;</span> <span class="o">%</span> <span class="p">(</span><span class="bp">self</span><span class="o">.</span><span class="n">_prog_prefix</span><span class="p">,</span> <span class="n">name</span><span class="p">)</span>
<span class="n">parser</span><span class="o">.</span><span class="n">env_prefix</span> <span class="o">=</span> <span class="bp">self</span><span class="o">.</span><span class="n">_env_prefix</span><span class="o">+</span><span class="s1">&#39;_&#39;</span><span class="o">+</span><span class="n">name</span><span class="o">+</span><span class="s1">&#39;_&#39;</span>

<span class="k">def</span> <span class="nf">remove_print_config</span><span class="p">(</span><span class="n">actions</span><span class="p">):</span>
<span class="n">print_config</span> <span class="o">=</span> <span class="p">[</span><span class="n">a</span> <span class="k">for</span> <span class="n">a</span> <span class="ow">in</span> <span class="n">actions</span> <span class="k">if</span> <span class="nb">isinstance</span><span class="p">(</span><span class="n">a</span><span class="p">,</span> <span class="n">_ActionPrintConfig</span><span class="p">)]</span>
<span class="k">if</span> <span class="nb">len</span><span class="p">(</span><span class="n">print_config</span><span class="p">)</span> <span class="o">&gt;</span> <span class="mi">0</span><span class="p">:</span>
<span class="n">actions</span><span class="o">.</span><span class="n">remove</span><span class="p">(</span><span class="n">print_config</span><span class="p">[</span><span class="mi">0</span><span class="p">])</span>

<span class="n">remove_print_config</span><span class="p">(</span><span class="n">parser</span><span class="o">.</span><span class="n">_actions</span><span class="p">)</span>
<span class="n">remove_print_config</span><span class="p">(</span><span class="n">parser</span><span class="o">.</span><span class="n">_action_groups</span><span class="p">[</span><span class="mi">1</span><span class="p">]</span><span class="o">.</span><span class="n">_group_actions</span><span class="p">)</span>
<span class="n">_remove_actions</span><span class="p">(</span><span class="n">parser</span><span class="p">,</span> <span class="n">_ActionPrintConfig</span><span class="p">)</span>

<span class="c1"># create a pseudo-action to hold the choice help</span>
<span class="n">aliases</span> <span class="o">=</span> <span class="n">kwargs</span><span class="o">.</span><span class="n">pop</span><span class="p">(</span><span class="s1">&#39;aliases&#39;</span><span class="p">,</span> <span class="p">())</span>
4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/cli.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.cli &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.cli &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/core.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.core &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.core &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/formatters.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.formatters &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.formatters &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/jsonnet.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.jsonnet &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.jsonnet &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/jsonschema.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.jsonschema &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.jsonschema &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/optionals.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.optionals &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.optionals &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


119 changes: 89 additions & 30 deletions docs/_modules/jsonargparse/signatures.html

Large diffs are not rendered by default.

4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/typing.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.typing &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.typing &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


4 changes: 2 additions & 2 deletions docs/_modules/jsonargparse/util.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse.util &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse.util &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


9 changes: 9 additions & 0 deletions docs/_sources/CHANGELOG.rst.txt
Original file line number Diff line number Diff line change
@@ -9,6 +9,15 @@ follow `Semantic Versioning <https://semver.org/>`_
only be introduced in major versions with advance notice in the **Deprecated**
section of releases.


v3.3.0 (2021-01-08)
-------------------

Added
^^^^^
- New add_subclass_arguments method to add as type with a specific help option.


v3.2.1 (2020-12-30)
-------------------

2 changes: 1 addition & 1 deletion docs/_static/documentation_options.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
var DOCUMENTATION_OPTIONS = {
URL_ROOT: document.getElementById("documentation_options").getAttribute('data-url_root'),
VERSION: '3.2.1',
VERSION: '3.3.0',
LANGUAGE: 'None',
COLLAPSE_INDEX: false,
BUILDER: 'html',
10 changes: 6 additions & 4 deletions docs/genindex.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Index &mdash; jsonargparse 3.2.1 documentation</title>
<title>Index &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


@@ -246,10 +246,10 @@ <h2 id="A">A</h2>
</li>
<li><a href="index.html#jsonargparse.actions.ActionPath">ActionPath (class in jsonargparse.actions)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#jsonargparse.actions.ActionPathList">ActionPathList (class in jsonargparse.actions)</a>
</li>
</ul></td>
<td style="width: 33%; vertical-align: top;"><ul>
<li><a href="index.html#jsonargparse.actions.ActionYesNo">ActionYesNo (class in jsonargparse.actions)</a>
</li>
<li><a href="index.html#jsonargparse.core.ArgumentParser.add_argument_group">add_argument_group() (jsonargparse.core.ArgumentParser method)</a>
@@ -259,6 +259,8 @@ <h2 id="A">A</h2>
<li><a href="index.html#jsonargparse.signatures.SignatureArguments.add_function_arguments">add_function_arguments() (jsonargparse.signatures.SignatureArguments method)</a>
</li>
<li><a href="index.html#jsonargparse.signatures.SignatureArguments.add_method_arguments">add_method_arguments() (jsonargparse.signatures.SignatureArguments method)</a>
</li>
<li><a href="index.html#jsonargparse.signatures.SignatureArguments.add_subclass_arguments">add_subclass_arguments() (jsonargparse.signatures.SignatureArguments method)</a>
</li>
<li><a href="index.html#jsonargparse.core.ArgumentParser.add_subcommands">add_subcommands() (jsonargparse.core.ArgumentParser method)</a>
</li>
89 changes: 73 additions & 16 deletions docs/index.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>jsonargparse &mdash; jsonargparse 3.2.1 documentation</title>
<title>jsonargparse &mdash; jsonargparse 3.3.0 documentation</title>



@@ -59,7 +59,7 @@


<div class="version">
3.2.1
3.3.0
</div>


@@ -96,7 +96,7 @@
<li><a class="reference internal" href="#configuration-files">Configuration files</a></li>
<li><a class="reference internal" href="#classes-methods-and-functions">Classes, methods and functions</a></li>
<li><a class="reference internal" href="#type-hints">Type hints</a></li>
<li><a class="reference internal" href="#classes-as-type">Classes as type</a></li>
<li><a class="reference internal" href="#class-type-and-sub-classes">Class type and sub-classes</a></li>
<li><a class="reference internal" href="#sub-commands">Sub-commands</a></li>
<li><a class="reference internal" href="#json-schemas">Json schemas</a></li>
<li><a class="reference internal" href="#jsonnet-files">Jsonnet files</a></li>
@@ -631,19 +631,19 @@ <h1>Parsers<a class="headerlink" href="#parsers" title="Permalink to this headli
be shown as <code class="code docutils literal notranslate"><span class="pre">type:</span> <span class="pre">Union[str,</span> <span class="pre">null],</span> <span class="pre">default:</span> <span class="pre">null</span></code>.</p></li>
</ul>
</div>
<div class="section" id="classes-as-type">
<h1>Classes as type<a class="headerlink" href="#classes-as-type" title="Permalink to this headline"></a></h1>
<p>Using an arbitrary class as a type is also possible, though it requires a bit
explanation. In the config file or environment variable or command line
argument, a class is represented by a dictionary with a <code class="code docutils literal notranslate"><span class="pre">class_path</span></code> entry
indicating the dot notation expression to import the class, and optionally some
<code class="code docutils literal notranslate"><span class="pre">init_args</span></code> that would be used to instantiate it. When parsing it will be
checked that the class can be imported, that it is a subclass of the type and
that <code class="code docutils literal notranslate"><span class="pre">init_args</span></code> values correspond to valid arguments to instantiate.
After parsing, the config object will include the <code class="code docutils literal notranslate"><span class="pre">class_path</span></code> and
<code class="code docutils literal notranslate"><span class="pre">init_args</span></code> entries. To get a config object with all subclasses
instantiated, the <a class="reference internal" href="#jsonargparse.core.ArgumentParser.instantiate_subclasses" title="jsonargparse.core.ArgumentParser.instantiate_subclasses"><code class="xref py py-meth docutils literal notranslate"><span class="pre">ArgumentParser.instantiate_subclasses()</span></code></a> method is
used.</p>
<div class="section" id="class-type-and-sub-classes">
<h1>Class type and sub-classes<a class="headerlink" href="#class-type-and-sub-classes" title="Permalink to this headline"></a></h1>
<p>It is also possible to use an arbitrary class as a type, such that the argument
accepts this class or any derived subclass. In the config file or environment
variable or command line argument, a class is represented by a dictionary with a
<code class="code docutils literal notranslate"><span class="pre">class_path</span></code> entry indicating the dot notation expression to import the
class, and optionally some <code class="code docutils literal notranslate"><span class="pre">init_args</span></code> that would be used to instantiate
it. When parsing it will be checked that the class can be imported, that it is a
subclass of the type and that <code class="code docutils literal notranslate"><span class="pre">init_args</span></code> values correspond to valid
arguments to instantiate. After parsing, the config object will include the
<code class="code docutils literal notranslate"><span class="pre">class_path</span></code> and <code class="code docutils literal notranslate"><span class="pre">init_args</span></code> entries. To get a config object with
all subclasses instantiated, the
<a class="reference internal" href="#jsonargparse.core.ArgumentParser.instantiate_subclasses" title="jsonargparse.core.ArgumentParser.instantiate_subclasses"><code class="xref py py-meth docutils literal notranslate"><span class="pre">ArgumentParser.instantiate_subclasses()</span></code></a> method is used.</p>
<p>A simple example would be having some config file <code class="code docutils literal notranslate"><span class="pre">config.yaml</span></code> as:</p>
<div class="highlight-yaml notranslate"><div class="highlight"><pre><span></span><span class="l l-Scalar l-Scalar-Plain">calendar</span><span class="p p-Indicator">:</span>
<span class="l l-Scalar l-Scalar-Plain">class_path</span><span class="p p-Indicator">:</span> <span class="l l-Scalar l-Scalar-Plain">calendar.Calendar</span>
@@ -666,6 +666,34 @@ <h1>Classes as type<a class="headerlink" href="#classes-as-type" title="Permalin
<p>In the example the <code class="code docutils literal notranslate"><span class="pre">class_path</span></code> points to the same class used for the
type. But a subclass of <code class="code docutils literal notranslate"><span class="pre">Calendar</span></code> with an extended list of init
parameters would also work.</p>
<p>When using any of the methods described in <a class="reference internal" href="#classes-methods-functions"><span class="std std-ref">Classes, methods and functions</span></a>,
each argument with a class as the type can be given using a <code class="code docutils literal notranslate"><span class="pre">class_path</span></code>
and <code class="code docutils literal notranslate"><span class="pre">init_args</span></code> pair.</p>
<p>There is also another method
<a class="reference internal" href="#jsonargparse.signatures.SignatureArguments.add_subclass_arguments" title="jsonargparse.signatures.SignatureArguments.add_subclass_arguments"><code class="xref py py-meth docutils literal notranslate"><span class="pre">SignatureArguments.add_subclass_arguments()</span></code></a> which does the same as
<code class="code docutils literal notranslate"><span class="pre">add_argument</span></code> in the example above, but has some added benefits: 1) the
argument is added in a new group automatically using docstrings; 2) the argument
values can be given in an independent config file by specifying a path to it; 3)
by default has useful <code class="code docutils literal notranslate"><span class="pre">metavar</span></code> and <code class="code docutils literal notranslate"><span class="pre">help</span></code> strings; and 4) a special
<code class="code docutils literal notranslate"><span class="pre">--*.help</span></code> argument is added that can be used to show the expected
<code class="code docutils literal notranslate"><span class="pre">init_args</span></code> details for a specific given <code class="code docutils literal notranslate"><span class="pre">class_path</span></code>. Take for
example a tool defined as:</p>
<div class="highlight-python notranslate"><div class="highlight"><pre><span></span><span class="kn">from</span> <span class="nn">calendar</span> <span class="kn">import</span> <span class="n">Calendar</span>
<span class="kn">from</span> <span class="nn">jsonargparse</span> <span class="kn">import</span> <span class="n">ArgumentParser</span>
<span class="o">...</span>
<span class="n">parser</span> <span class="o">=</span> <span class="n">ArgumentParser</span><span class="p">()</span>
<span class="n">parser</span><span class="o">.</span><span class="n">add_subclass_arguments</span><span class="p">(</span><span class="n">Calendar</span><span class="p">,</span> <span class="s1">&#39;calendar&#39;</span><span class="p">)</span>
<span class="o">...</span>
<span class="n">cfg</span> <span class="o">=</span> <span class="n">parser</span><span class="o">.</span><span class="n">parse_args</span><span class="p">()</span>
<span class="o">...</span>
</pre></div>
</div>
<p>If there is some subclass of <code class="code docutils literal notranslate"><span class="pre">Calendar</span></code> which can be imported from
<code class="code docutils literal notranslate"><span class="pre">mycode.MyCalendar</span></code>, then it would be possible to see the corresponding
<code class="code docutils literal notranslate"><span class="pre">init_args</span></code> details by running the tool from the command line as:</p>
<div class="highlight-bash notranslate"><div class="highlight"><pre><span></span>python tool.py --calendar.help mycode.MyCalendar
</pre></div>
</div>
</div>
<div class="section" id="sub-commands">
<span id="id6"></span><h1>Sub-commands<a class="headerlink" href="#sub-commands" title="Permalink to this headline"></a></h1>
@@ -1761,6 +1789,9 @@ <h1>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
<tr class="row-odd"><td><p><a class="reference internal" href="#jsonargparse.signatures.SignatureArguments.add_method_arguments" title="jsonargparse.signatures.SignatureArguments.add_method_arguments"><code class="xref py py-obj docutils literal notranslate"><span class="pre">add_method_arguments</span></code></a>(theclass, themethod[, …])</p></td>
<td><p>Adds arguments from a class based on its type hints and docstrings.</p></td>
</tr>
<tr class="row-even"><td><p><a class="reference internal" href="#jsonargparse.signatures.SignatureArguments.add_subclass_arguments" title="jsonargparse.signatures.SignatureArguments.add_subclass_arguments"><code class="xref py py-obj docutils literal notranslate"><span class="pre">add_subclass_arguments</span></code></a>(baseclass, nested_key)</p></td>
<td><p>Adds arguments to allow specifying any subclass of the given base class.</p></td>
</tr>
</tbody>
</table>
<dl class="py method">
@@ -1854,6 +1885,32 @@ <h1>API Reference<a class="headerlink" href="#api-reference" title="Permalink to
</dl>
</dd></dl>

<dl class="py method">
<dt id="jsonargparse.signatures.SignatureArguments.add_subclass_arguments">
<code class="sig-name descname">add_subclass_arguments</code><span class="sig-paren">(</span><em class="sig-param"><span class="n">baseclass</span></em>, <em class="sig-param"><span class="n">nested_key</span></em>, <em class="sig-param"><span class="n">as_group</span><span class="o">=</span><span class="default_value">True</span></em>, <em class="sig-param"><span class="n">metavar</span><span class="o">=</span><span class="default_value">'{&quot;class_path&quot;:...[,&quot;init_args&quot;:...]}'</span></em>, <em class="sig-param"><span class="n">help</span><span class="o">=</span><span class="default_value">'Dictionary with &quot;class_path&quot; and &quot;init_args&quot; for any subclass of %(baseclass_name)s.'</span></em>, <em class="sig-param"><span class="o">**</span><span class="n">kwargs</span></em><span class="sig-paren">)</span><a class="reference internal" href="_modules/jsonargparse/signatures.html#SignatureArguments.add_subclass_arguments"><span class="viewcode-link">[source]</span></a><a class="headerlink" href="#jsonargparse.signatures.SignatureArguments.add_subclass_arguments" title="Permalink to this definition"></a></dt>
<dd><p>Adds arguments to allow specifying any subclass of the given base class.</p>
<p>This adds an argument that requires a dictionary with a “class_path”
entry which must be a import dot notation expression. Optionally any
init arguments for the class can be given in the “init_args” entry.
Since subclasses can have different init arguments, the help does not
show the details of the arguments of the base class. Instead a help
argument is added that will print the details for a given class path.</p>
<dl class="field-list simple">
<dt class="field-odd">Parameters</dt>
<dd class="field-odd"><ul class="simple">
<li><p><strong>baseclass</strong> (<a class="reference external" href="https://docs.python.org/3/library/typing.html#typing.Type" title="(in Python v3.9)"><code class="xref py py-class docutils literal notranslate"><span class="pre">Type</span></code></a>) – Base class to use to check subclasses.</p></li>
<li><p><strong>nested_key</strong> (<a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str" title="(in Python v3.9)"><code class="xref py py-class docutils literal notranslate"><span class="pre">str</span></code></a>) – Key for nested namespace.</p></li>
<li><p><strong>as_group</strong> (<a class="reference external" href="https://docs.python.org/3/library/functions.html#bool" title="(in Python v3.9)"><code class="xref py py-class docutils literal notranslate"><span class="pre">bool</span></code></a>) – Whether arguments should be added to a new argument group.</p></li>
<li><p><strong>metavar</strong> (<a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str" title="(in Python v3.9)"><code class="xref py py-class docutils literal notranslate"><span class="pre">str</span></code></a>) – Variable string to show in the argument’s help.</p></li>
<li><p><strong>help</strong> (<a class="reference external" href="https://docs.python.org/3/library/stdtypes.html#str" title="(in Python v3.9)"><code class="xref py py-class docutils literal notranslate"><span class="pre">str</span></code></a>) – Description of argument to show in the help.</p></li>
</ul>
</dd>
<dt class="field-even">Raises</dt>
<dd class="field-even"><p><a class="reference external" href="https://docs.python.org/3/library/exceptions.html#ValueError" title="(in Python v3.9)"><strong>ValueError</strong></a> – When not given a class.</p>
</dd>
</dl>
</dd></dl>

</dd></dl>

</div>
Binary file modified docs/objects.inv
Binary file not shown.
4 changes: 2 additions & 2 deletions docs/py-modindex.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Python Module Index &mdash; jsonargparse 3.2.1 documentation</title>
<title>Python Module Index &mdash; jsonargparse 3.3.0 documentation</title>



@@ -62,7 +62,7 @@


<div class="version">
3.2.1
3.3.0
</div>


4 changes: 2 additions & 2 deletions docs/search.html
Original file line number Diff line number Diff line change
@@ -7,7 +7,7 @@

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>Search &mdash; jsonargparse 3.2.1 documentation</title>
<title>Search &mdash; jsonargparse 3.3.0 documentation</title>



@@ -61,7 +61,7 @@


<div class="version">
3.2.1
3.3.0
</div>


2 changes: 1 addition & 1 deletion docs/searchindex.js

Large diffs are not rendered by default.

15 changes: 11 additions & 4 deletions githook-pre-commit
Original file line number Diff line number Diff line change
@@ -62,15 +62,22 @@ done
./setup.py test_coverage;
[ "$?" != "0" ] && exit 1;

## If bumping version, check wheel ok for pypi ##
## Only when bumping version ##
if [ "${BUMPVERSION_NEW_VERSION+x}" != "" ]; then
## Check changelog ##
CHANGELOG=$(grep -E '^v.+\..+\..+ \(....-..-..\)' CHANGELOG.rst | head -n 1);
EXPECTED="v$BUMPVERSION_NEW_VERSION ($(date -u +%Y-%m-%d))";
if [ "$CHANGELOG" != "$EXPECTED" ]; then
echo "${0##*/}: expected latest release in CHANGELOG.rst to be '$EXPECTED'" 1>&2;
exit 1;
fi

## Check wheel ok for pypi ##
./setup.py bdist_wheel;
twine check dist/*.whl;
[ "$?" != "0" ] && exit 1;
fi

## Update documentation ##
if [ "${BUMPVERSION_NEW_VERSION+x}" != "" ]; then
## Update documentation ##
./setup.py build_sphinx --quiet --builder html \
--version $BUMPVERSION_NEW_VERSION --release $BUMPVERSION_NEW_VERSION &&
rm -fr docs &&
2 changes: 1 addition & 1 deletion jsonargparse/__init__.py
Original file line number Diff line number Diff line change
@@ -20,4 +20,4 @@
)


__version__ = '3.2.1'
__version__ = '3.3.0'
53 changes: 44 additions & 9 deletions jsonargparse/actions.py
Original file line number Diff line number Diff line change
@@ -4,9 +4,10 @@
import re
import sys
import yaml
import inspect
import argparse
from enum import Enum
from argparse import Namespace, Action, SUPPRESS, _StoreAction, _SubParsersAction
from argparse import Namespace, Action, SUPPRESS, _StoreAction, _HelpAction, _SubParsersAction

from .optionals import get_config_read_mode, FilesCompleterMethod
from .typing import restricted_number_type
@@ -16,6 +17,7 @@
ParserError,
namespace_to_dict,
dict_to_namespace,
import_object,
Path,
_load_config,
_flat_namespace_to_dict,
@@ -70,6 +72,17 @@ def _is_action_value_list(action:Action):
return False


def _remove_actions(parser, types):

def remove(actions):
rm_actions = [a for a in actions if isinstance(a, types)]
for action in rm_actions:
actions.remove(action)

remove(parser._actions)
remove(parser._action_groups[1]._group_actions)


class ActionConfigFile(Action, FilesCompleterMethod):
"""Action to indicate that an argument is a configuration file or a configuration string."""

@@ -154,6 +167,35 @@ def _check_type(self, value, cfg=None):
return self._load_config(value)


class _ActionHelpClassPath(Action):

def __init__(self, **kwargs):
if 'baseclass' in kwargs:
_check_unknown_kwargs(kwargs, {'baseclass'})
self._baseclass = kwargs['baseclass']
else:
self._baseclass = kwargs.pop('_baseclass')
kwargs['help'] = 'Show the help for the given class path and exit.'
kwargs['metavar'] = 'CLASS'
kwargs['default'] = SUPPRESS
super().__init__(**kwargs)

def __call__(self, *args, **kwargs):
if len(args) == 0:
kwargs['_baseclass'] = self._baseclass
return _ActionHelpClassPath(**kwargs)
val_class = import_object(args[2])
if not _issubclass(val_class, self._baseclass):
raise TypeError('Class "'+args[2]+'" is not a subclass of '+self._baseclass.__name__)
dest = re.sub('\\.help$', '', self.dest) + '.init_args'
ArgumentParser = import_object('jsonargparse.ArgumentParser')
tmp = ArgumentParser()
tmp.add_class_arguments(val_class, dest)
_remove_actions(tmp, (_HelpAction, _ActionPrintConfig))
tmp.print_help()
args[0].exit()


class ActionYesNo(Action):
"""Paired options --{yes_prefix}opt, --{no_prefix}opt to set True or False respectively."""

@@ -416,14 +458,7 @@ def add_subcommand(self, name, parser, **kwargs):

parser.prog = '%s [options] %s' % (self._prog_prefix, name)
parser.env_prefix = self._env_prefix+'_'+name+'_'

def remove_print_config(actions):
print_config = [a for a in actions if isinstance(a, _ActionPrintConfig)]
if len(print_config) > 0:
actions.remove(print_config[0])

remove_print_config(parser._actions)
remove_print_config(parser._action_groups[1]._group_actions)
_remove_actions(parser, _ActionPrintConfig)

# create a pseudo-action to hold the choice help
aliases = kwargs.pop('aliases', ())
113 changes: 86 additions & 27 deletions jsonargparse/signatures.py
Original file line number Diff line number Diff line change
@@ -5,7 +5,7 @@
from typing import Union, Optional, List, Container, Type, Callable

from .util import _issubclass
from .actions import ActionEnum, _ActionConfigLoad
from .actions import ActionEnum, _ActionConfigLoad, _ActionHelpClassPath
from .typing import is_optional
from .jsonschema import ActionJsonSchema
from .optionals import docstring_parser_support, import_docstring_parse, dataclasses_support, import_dataclasses
@@ -190,32 +190,10 @@ def update_has_args_kwargs(base, has_args=True, has_kwargs=True):
has_args, has_kwargs = update_has_args_kwargs(objects[num], has_args, has_kwargs)

## Gather docstrings ##
doc_group = None
doc_params = {}
if docstring_parser_support:
docstring_parse = import_docstring_parse('_add_signature_arguments')
for base in objects:
for doc in docs_func(base):
try:
docstring = docstring_parse(doc)
except ValueError:
self.logger.debug('Failed parsing docstring for '+str(base)) # type: ignore
else:
if docstring.short_description and not doc_group:
doc_group = docstring.short_description
for param in docstring.params:
if param.arg_name not in doc_params:
doc_params[param.arg_name] = param.description
doc_group, doc_params = self._gather_docstrings(objects, docs_func)

## Create group if requested ##
group = self
if as_group:
if doc_group is None:
doc_group = str(objects[0])
name = objects[0].__name__ if nested_key is None else nested_key
group = self.add_argument_group(doc_group, name=name) # type: ignore
if nested_key is not None:
group.add_argument('--'+nested_key, action=_ActionConfigLoad) # type: ignore
group = self._create_group_if_requested(objects[0], nested_key, as_group, doc_group)

## Add objects arguments ##
added_args = set()
@@ -226,11 +204,12 @@ def update_has_args_kwargs(base, has_args=True, has_kwargs=True):
for obj, (add_args, add_kwargs) in zip(objects, add_types):
for num, param in enumerate(inspect.signature(sign_func(obj)).parameters.values()):
name = param.name
kind = param._kind # type: ignore
annotation = param.annotation
default = param.default
is_required = default == inspect._empty # type: ignore
skip_message = 'Skipping parameter "'+name+'" from "'+obj.__name__+'" because of: '
if param._kind in {kinds.VAR_POSITIONAL, kinds.VAR_KEYWORD} or \
if kind in {kinds.VAR_POSITIONAL, kinds.VAR_KEYWORD} or \
(is_required and skip_first and num == 0) or \
(annotation == inspect._empty and not is_required and default is None): # type: ignore
continue
@@ -270,9 +249,89 @@ def update_has_args_kwargs(base, has_args=True, has_kwargs=True):
self.logger.debug(skip_message+'Argument already added.') # type: ignore
else:
opt_str = dest if is_required and as_positional else '--'+dest
group.add_argument(opt_str, **kwargs) # type: ignore
group.add_argument(opt_str, **kwargs)
added_args.add(dest)
elif is_required:
raise ValueError('Required parameter without a type for '+obj.__name__+' parameter '+name+'.')

return len(added_args)


def add_subclass_arguments(
self,
baseclass: Type,
nested_key: str,
as_group: bool = True,
metavar: str = '{"class_path":...[,"init_args":...]}',
help: str = 'Dictionary with "class_path" and "init_args" for any subclass of %(baseclass_name)s.',
**kwargs
):
"""Adds arguments to allow specifying any subclass of the given base class.
This adds an argument that requires a dictionary with a "class_path"
entry which must be a import dot notation expression. Optionally any
init arguments for the class can be given in the "init_args" entry.
Since subclasses can have different init arguments, the help does not
show the details of the arguments of the base class. Instead a help
argument is added that will print the details for a given class path.
Args:
baseclass: Base class to use to check subclasses.
nested_key: Key for nested namespace.
as_group: Whether arguments should be added to a new argument group.
metavar: Variable string to show in the argument's help.
help: Description of argument to show in the help.
Raises:
ValueError: When not given a class.
"""
if not inspect.isclass(baseclass):
raise ValueError('Expected a class object.')

def docs_func(base):
return [base.__init__.__doc__, base.__doc__]

doc_group = self._gather_docstrings([baseclass], docs_func)[0]
group = self._create_group_if_requested(baseclass, nested_key, as_group, doc_group, config_load=False)

group.add_argument('--'+nested_key+'.help', action=_ActionHelpClassPath(baseclass=baseclass))
group.add_argument(
'--' + nested_key,
type=baseclass,
enable_path=True,
metavar=metavar,
help=(help % {'baseclass_name': baseclass.__name__}),
**kwargs
)


def _gather_docstrings(self, objects, docs_func):
doc_group = None
doc_params = {}
if docstring_parser_support:
docstring_parse = import_docstring_parse('_gather_docstrings')
for base in objects:
for doc in docs_func(base):
try:
docstring = docstring_parse(doc)
except ValueError:
self.logger.debug('Failed parsing docstring for '+str(base))
else:
if docstring.short_description and not doc_group:
doc_group = docstring.short_description
for param in docstring.params:
if param.arg_name not in doc_params:
doc_params[param.arg_name] = param.description
return doc_group, doc_params


def _create_group_if_requested(self, obj, nested_key, as_group, doc_group, config_load=True):
group = self
if as_group:
if doc_group is None:
doc_group = str(obj)
name = obj.__name__ if nested_key is None else nested_key
group = self.add_argument_group(doc_group, name=name)
if config_load and nested_key is not None:
group.add_argument('--'+nested_key, action=_ActionConfigLoad)
return group
25 changes: 25 additions & 0 deletions jsonargparse_tests/signatures_tests.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
#!/usr/bin/env python3

import json
import yaml
import calendar
from enum import Enum
from io import StringIO
from contextlib import redirect_stdout
from typing import Dict, List, Tuple, Optional, Union, Any
from jsonargparse_tests.base import *
from jsonargparse.actions import _find_action
@@ -241,6 +244,28 @@ def func(a1 = '1',
self.assertIsNone(_find_action(parser, 'a3').help, 'expected help for a3 to be None')


def test_add_subclass_arguments(self):
parser = ArgumentParser(error_handler=None)
parser.add_subclass_arguments(calendar.Calendar, 'cal')

cal = {'class_path': 'calendar.Calendar', 'init_args': {'firstweekday': 1}}
cfg = parser.parse_args(['--cal='+json.dumps(cal)])
self.assertEqual(namespace_to_dict(cfg.cal), cal)

self.assertRaises(ParserError, lambda: parser.parse_args(['--cal={"class_path":"not.exist.Class"}']))
self.assertRaises(ParserError, lambda: parser.parse_args(['--cal={"class_path":"calendar.January"}']))
self.assertRaises(ParserError, lambda: parser.parse_args(['--cal.help=calendar.January']))
self.assertRaises(ValueError, lambda: parser.add_subclass_arguments(calendar.January, 'jan'))

os.environ['COLUMNS'] = '150'
out = StringIO()
with redirect_stdout(out), self.assertRaises(SystemExit):
parser.parse_args(['--cal.help=calendar.Calendar'])

self.assertIn('--cal.init_args.firstweekday', out.getvalue())



@unittest.skipIf(not jsonschema_support, 'jsonschema package is required')
def test_optional_enum(self):

2 changes: 1 addition & 1 deletion setup.cfg
Original file line number Diff line number Diff line change
@@ -48,7 +48,7 @@ doc =

[metadata]
name = jsonargparse
version = 3.2.1
version = 3.3.0
description = Parsing of command line options, yaml/jsonnet config files and/or environment variables based on argparse.
long_description_content_type = text/x-rst
author = Mauricio Villegas