Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions CHANGELOG.rst
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ Added
``$schema`` key, which is ignored when parsing. This feature is
experimental, so the details of the generated schema might change in non-major
releases (`#961 <https://github.com/mauvilsa/jsonargparse/pull/961>`__).
- A protocol whose single method is ``__call__`` is now also implemented by a
function with a compatible signature, so the import path of a function is
accepted as value, see :ref:`type-hints` (`#963
<https://github.com/mauvilsa/jsonargparse/pull/963>`__).

Fixed
^^^^^
Expand All @@ -59,6 +63,21 @@ Fixed
type is nested in a container or optional, e.g. ``builtins.NoneType`` for
``Optional[list[SomeBaseClass]]`` (`#960
<https://github.com/mauvilsa/jsonargparse/pull/960>`__).
- Functions without a return annotation at runtime, e.g. C-implemented ones like
``time.localtime``, were rejected for ``Callable`` types with a class return
type, even when a stub file gives the return type. Now with the stubs resolver
the return type from the ``.pyi`` is used (`#963
<https://github.com/mauvilsa/jsonargparse/pull/963>`__).
- Unsubscripted ``typing`` sequence aliases, i.e. ``List``, ``Sequence``,
``MutableSequence``, ``Iterable``, ``Collection``, ``Container``,
``Reversible`` and ``Deque``, raised ``AttributeError: __args__``, which made
classes that have one in their signature, e.g.
``torch.utils.data.DataLoader``, impossible to add (`#963
<https://github.com/mauvilsa/jsonargparse/pull/963>`__).
- ``typing.Hashable`` and ``typing.Sized`` were not supported, while the
``collections.abc`` spelling of the same types was. A bare one didn't validate
and a composed one, e.g. ``Optional[Hashable]``, raised ``Unsupported type
hint`` (`#963 <https://github.com/mauvilsa/jsonargparse/pull/963>`__).

Deprecated
^^^^^^^^^^
Expand Down
157 changes: 73 additions & 84 deletions CONTRIBUTING.rst
Original file line number Diff line number Diff line change
@@ -1,66 +1,58 @@
.. _contributing:

Contributing
============

Contributions to jsonargparse are very welcome. There are multiple ways for
people to help and contribute, among them:
Contributions to jsonargparse are very welcome. There are many ways to help,
among them:

- Star ⭐ the GitHub project `<https://github.com/mauvilsa/jsonargparse/>`__.
- `Sponsor 🩷 <https://github.com/sponsors/mauvilsa>`__ its maintenance and
development.
- Spread the word in your community about the features you like from
jsonargparse.
- Help others to learn how to use jsonargparse by creating tutorials, such as
blog posts and videos.
- Help others learn how to use jsonargparse by creating tutorials, such as blog
posts and videos. If you do, let us know so that it can be added to
:ref:`talks-and-articles`.
- Become active in existing GitHub issues and pull requests.
- Create `issues <https://github.com/mauvilsa/jsonargparse/issues>`__ for
reporting bugs and proposing improvements.
- Create `issues <https://github.com/mauvilsa/jsonargparse/issues>`__ to report
bugs and propose improvements.
- Create `pull requests <https://github.com/mauvilsa/jsonargparse/pulls>`__ with
documentation improvements, bug fixes or new features.

.. note::

While creating an issue before submitting a pull request is not mandatory,
it might be helpful. Issues allow for discussion and feedback before
significant development effort is invested. However, in some cases, code
changes can better illustrate a proposal, making it more effective to submit
a pull request directly. In such cases please avoid opening a largely
redundant issue.
Creating an issue before submitting a pull request is not mandatory, but it
can be helpful, since it allows for discussion and feedback before
significant effort is invested. In some cases, though, code changes
illustrate a proposal better, so submitting a pull request directly is more
effective. In such cases please avoid opening a largely redundant issue.

Development environment
-----------------------

If you intend to work with the source code, note that this project does not
include any ``requirements.txt`` file. This is by intention. To make it very
clear what are the requirements for different use cases, all the requirements of
the project are stored in the file ``pyproject.toml``. The basic runtime
requirements are defined in ``dependencies``. Requirements for optional features
are stored in ``[project.optional-dependencies]``. Also in the same section
there are requirements for testing, development and documentation building:
``test``, ``dev`` and ``doc``.
All requirements of the project are defined in ``pyproject.toml``. The basic
runtime requirements are in ``dependencies``. Requirements for optional
features, as well as for testing, development and documentation building
(``test``, ``dev`` and ``doc``), are in ``[project.optional-dependencies]``.

The recommended way to work with the source code is the following. First clone
the repository, then create a virtual environment, activate it and finally
install the development requirements. More precisely the steps are:
The recommended way to work with the source code is to clone the repository,
create a virtual environment, activate it, and install the development
requirements:

.. code-block:: bash

git clone https://github.com/mauvilsa/jsonargparse.git
cd jsonargparse
python -m venv venv
. venv/bin/activate

The crucial step is installing the requirements which would be done by running:

.. code-block:: bash

pip install -e ".[dev,all]"

pre-commit
----------

Please also install the `pre-commit <https://pre-commit.com/>`__ git hooks so
that unit tests and code checks are automatically run locally. This is done as
follows:
Please also install the `pre-commit <https://pre-commit.com/>`__ git hooks, so
that unit tests and code checks run automatically on your machine:

.. code-block:: bash

Expand All @@ -69,19 +61,16 @@ follows:
.. note::

``.pre-commit-config.yaml`` is configured to run the hooks using Python
3.12. Ensure you have Python 3.12 installed and available in your
environment for ``pre-commit`` to function correctly. For development, other
Python versions will work, but for convenience, Python 3.12 is recommended.

The ``pre-push`` stage runs several hooks, including tests, doctests, mypy, and
coverage. These hooks are designed to inform developers of issues that must be
resolved before a pull request can be merged. Note that these hooks may take
some time to complete. If you wish to push without running these hooks, use the
command ``git push --no-verify``.
3.12, so make sure that this version is installed and available. Other
Python versions work for development, but 3.12 is recommended for
convenience.

Formatting of the code is done automatically by pre-commit. If some pre-commit
hooks fail and you decide to skip them, formatting will be automatically applied
by a GitHub action in pull requests.
The ``pre-push`` stage runs several hooks, including tests, doctests, mypy and
coverage. They inform developers of issues that must be resolved before a pull
request can be merged, and can take some time to complete. To push without
running them, use ``git push --no-verify``. Formatting of the code is applied
automatically by pre-commit. Even when pushing with ``--no-verify``, please make
sure that the formatting has been applied.

Documentation
-------------
Expand All @@ -92,8 +81,7 @@ To build the documentation run:

sphinx-build sphinx sphinx/_build sphinx/*.rst

To view the built documentation, open the file ``sphinx/_build/index.html`` in a
browser.
Then open the file ``sphinx/_build/index.html`` in a browser.

Code conventions
----------------
Expand All @@ -103,7 +91,7 @@ Code conventions
Most module filenames start with ``_``, meaning they are private implementation
details. For objects within modules, the ``_`` prefix indicates the object is
only used within that same module. An object without a ``_`` prefix may be
imported by other modules but that does not make it public — it is simply
imported by other modules, but that does not make it public — it is simply
internal to the package. The only truly public objects are those listed in
``jsonargparse.__all__`` and ``jsonargparse.typing.__all__``.

Expand All @@ -116,9 +104,8 @@ style
Tests
-----

Running the unit tests can be done either using `pytest
<https://docs.pytest.org/>`__ or `tox
<https://tox.readthedocs.io/en/stable/>`__. Also pre-commit runs some additional
The unit tests can be run with `pytest <https://docs.pytest.org/>`__ or `tox
<https://tox.readthedocs.io/en/stable/>`__. Pre-commit runs some additional
tests.

.. code-block:: bash
Expand All @@ -127,23 +114,23 @@ tests.
pytest # Run tests using pytest on the python of the environment
pre-commit run -a --hook-stage pre-push # Run pre-push git hooks (tests, doctests, mypy, coverage)

Tests can be run in any environment without the source code. Before v4.47.0, the
tests were included in the main package. Since v4.47.0, they are provided in a
separate package. Prefer installing the tests package with the same version as
the main package. For example, for v4.47.0 run:
The tests can also be run in any environment without the source code. Since
v4.47.0 they are provided in a separate package, whereas before they were
included in the main package. Prefer installing the tests package with the same
version as the main package, for example:

.. code-block:: bash

pip install jsonargparse_tests==4.47.0
python -m jsonargparse_tests

All contributed features and bug fixes must include tests. For bug fixes, ensure
the test fails without the code fix. Almost always tests should exercise only
the public API. Testing internal functions directly is rarely justified and
that the test fails without the code fix. Tests should almost always exercise
only the public API; testing internal functions directly is rarely justified and
should be avoided. For tests involving signatures, define the classes and
functions used at the global module scope. Jsonargparse is not intended to
support dynamically defined classes and functions, so there is no value in
testing such cases.
functions at the global module scope. Jsonargparse is not intended to support
dynamically defined classes and functions, so there is no value in testing such
cases.

For maintainable tests:

Expand All @@ -153,31 +140,35 @@ For maintainable tests:
multiple tests need the same files, parser configuration, or environment.
- Avoid pushing trivial one-line setup into fixtures when it makes the test
harder to read.
- Keep setup separate from assertions so each test clearly shows the behavior
being verified.
- The pytest output must be clean. If a test causes log output, the logs must
be captured and asserted using the ``logger`` fixture and ``capture_logs``
context manager from ``conftest.py``.
- Keep setup separate from assertions, so that each test clearly shows the
behavior being verified.
- Keep the pytest output clean. If a test causes log output, the logs must be
captured and minimally asserted using the ``logger`` fixture and
``capture_logs`` context manager from ``conftest.py``.


Coverage
--------

For a nice html test coverage report, run:
Coverage is required to be 100% in ``jsonargparse/*`` files, with realistic
tests and without unwarranted ``# pragma: no cover``. This ensures that all
existing code is actually needed.

For a nice html coverage report, run:

.. code-block:: bash

pytest --cov --cov-report=html

Then open the file ``htmlcov/index.html`` in a browser.

To get a full coverage report, you need to install all supported python
versions, and then:
A full coverage report requires all supported Python versions to be installed,
and then:

.. code-block:: bash

rm -fr jsonargparse_tests/.coverage jsonargparse_tests/htmlcov
tox -- --cov=../jsonargparse --cov-append
tox --parallel -- --cov=../jsonargparse --cov-append
cd jsonargparse_tests
coverage html

Expand All @@ -186,19 +177,16 @@ Then open the file ``jsonargparse_tests/htmlcov/index.html`` in a browser.
Pull requests
-------------

When creating a pull request, it is recommended that you create a specific
branch in your fork for the changes you want to contribute, instead of using the
``main`` branch.
For the changes you want to contribute, it is recommended to create a specific
branch in your fork, instead of using the ``main`` branch.

The required tasks to do for a pull request, are listed in
`PULL_REQUEST_TEMPLATE.md
The tasks required for a pull request are listed in `PULL_REQUEST_TEMPLATE.md
<https://github.com/mauvilsa/jsonargparse/blob/main/.github/PULL_REQUEST_TEMPLATE.md>`__.

One of the tasks is adding a changelog entry. For this, note that this project
uses semantic versioning. Depending on whether the contribution is a bug fix or
a new feature, the changelog entry would go in a patch or minor release. The
changelog section for the next release does not have a definite date, for
example:
One of the tasks is adding a changelog entry. This project uses semantic
versioning, so the entry goes in a patch release for a bug fix, or in a minor
release for a new feature. The changelog section for the next release does not
have a definite date, for example:

.. code-block::

Expand All @@ -211,13 +199,14 @@ example:

If no such section exists, just add it with "(unreleased)" instead of a date.
Have a look at previous releases to decide under which subsection the new entry
should go. If you are unsure, ask in the pull request.
should go. Entries must describe changes with respect to the previous release,
not with respect to unreleased commits.

Please don't open pull requests with breaking changes unless this has been
Please don't open pull requests with breaking changes, unless this has been
discussed and agreed upon in an issue.

Contributions using coding agents are welcome. However, any agent-generated
code must be fully understood by the submitter and must make sense and follow
these contributing guidelines. Always ask the agent to read and follow these
guidelines. Also ask to read ``.github/PULL_REQUEST_TEMPLATE.md`` so that the
tasks before submitting are covered.
Contributions using coding agents are welcome. However, any agent-generated code
must be fully understood by the submitter, must make sense, and must follow
these contributing guidelines. Always ask the agent to read and follow this
document, and also ``.github/PULL_REQUEST_TEMPLATE.md``, so that the tasks
required before submitting are covered.
37 changes: 8 additions & 29 deletions DOCUMENTATION.rst
Original file line number Diff line number Diff line change
Expand Up @@ -206,32 +206,6 @@ tool supports with their default values. Users can follow these steps:
# Run the tool using the adapted config
python example.py --config config.yaml

Comparison to Fire
------------------

The :func:`.auto_cli` feature is similar to and inspired by `Fire
<https://pypi.org/project/fire/>`__. However, there are fundamental differences.
First, the purpose is not to allow calling any Python object from the command
line. It is only intended for running functions and classes specifically written
for this purpose. Second, the arguments are expected to have type hints, and the
given values will be validated according to these. Third, the return values of
the functions are not automatically printed. :func:`.auto_cli` returns the value
and it is up to the developer to decide what to do with it.


.. _tutorials:

Tutorials
=========

- `"jsonargparse - Say goodbye to configuration hassles"
<https://2022.pycon.de/program/XK73C3/>`__ by Marianne Stecklina at PyCon DE
& PyData Berlin 2022

- Presentation video: https://youtu.be/2gDf2S0nHKg
- GitHub repository: https://github.com/stecklin/pycon22-jsonargparse


.. _parsers:

Parsers
Expand Down Expand Up @@ -587,7 +561,10 @@ Some notes about this support are:
``Proto[int]``. Subscripting substitutes the type arguments in the protocol's
methods, so ``Proto[int]`` and ``Proto[str]`` accept different
implementations. A ``TypeVar`` that remains, in the protocol or in the
implementation, matches any type, as static type checkers do.
implementation, matches any type, as static type checkers do. A protocol whose
single 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's ``dataclass``
and pydantic's ``BaseModel`` are supported even when nested. By default they
Expand Down Expand Up @@ -620,8 +597,10 @@ Some notes about this support are:
subclass of the return type of the callable. For these cases running
:meth:`instantiate <.ArgumentParser.instantiate>` will instantiate the class
or provide a function that returns the instance of the class. For more details
see :ref:`callable-type`. Currently the callable's argument and return types
are not validated.
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.ModuleType`` is supported by giving the dot import path of a module,
and on ``instantiate`` is replaced by the imported module object.
Expand Down
6 changes: 6 additions & 0 deletions README.rst
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ a `substantial user base
it serves as the framework behind pytorch-lightning's `LightningCLI
<https://lightning.ai/docs/pytorch/stable/cli/lightning_cli.html>`__.

The documentation is a reference that describes each feature in isolation, which
is not always the best way to learn. If you would rather see the *why* behind
the features and complete use cases built end to end, have a look at the `talks
and articles <https://jsonargparse.readthedocs.io/en/latest/talks-and-articles.html>`__
page, which collects presentations, blog posts and example projects.

Teaser examples
---------------

Expand Down
3 changes: 3 additions & 0 deletions jsonargparse/_actions.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
"""Collection of useful actions to define arguments."""

import inspect
import os
import re
import sys
Expand Down Expand Up @@ -409,6 +410,8 @@ def resolve_help_type(self, value, option_string):
raise TypeError(f"{option_string}: {ex}") from ex
if not any(is_subclass(val_class, b) or implements_protocol(val_class, b) for b in class_types):
raise TypeError(f'{option_string}: "{value}" is not a {self._kind} {self._basename}')
if not inspect.isclass(val_class): # a function that implements a callable protocol
raise TypeError(f'{option_string}: "{value}" is not a class, so it has no help')
return val_class

def print_help(self, call_args):
Expand Down
Loading