diff --git a/.gitignore b/.gitignore index 01b861c7551868688e565d24ceaaf7b35abf6e6b..f2a46fa2791c938565edade9516e0936e1eeeae8 100644 --- a/.gitignore +++ b/.gitignore @@ -22,3 +22,4 @@ dist/ .pytest_cache/ cache/ venv/ +_work/ diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml index 275b47fd42a9f5a10ef6df9fa3c41546aff2da07..69c42068b782ebfc516d771707083f1acad8ba10 100644 --- a/.gitlab-ci.yml +++ b/.gitlab-ci.yml @@ -3,5 +3,6 @@ # SPDX-License-Identifier: BSD-3-Clause include: - - project: bob/dev-profile + - project: software/dev-profile + ref: main file: /gitlab/python.yml diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index b131c74c2f18cfed9c4e3ce5ea853306c6d16815..fd229ea62bf22a43f685405f8ecff68e9c8eb267 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -6,23 +6,23 @@ # See https://pre-commit.com/hooks.html for more hooks repos: - repo: https://github.com/psf/black - rev: 22.10.0 + rev: 23.1.0 hooks: - id: black - repo: https://github.com/pycqa/docformatter - rev: v1.5.0 + rev: v1.6.0.rc1 hooks: - id: docformatter - repo: https://github.com/pycqa/isort - rev: 5.10.1 + rev: 5.12.0 hooks: - id: isort - repo: https://github.com/pycqa/flake8 - rev: 3.9.2 + rev: 6.0.0 hooks: - id: flake8 - repo: https://github.com/pre-commit/mirrors-mypy - rev: v0.982 + rev: v0.991 hooks: - id: mypy args: [ @@ -33,12 +33,12 @@ repos: ] exclude: '^.*/data/second_config\.py$' - repo: https://github.com/asottile/pyupgrade - rev: v3.1.0 + rev: v3.3.1 hooks: - id: pyupgrade args: [--py38-plus] - repo: https://github.com/pre-commit/pre-commit-hooks - rev: v4.3.0 + rev: v4.4.0 hooks: - id: check-ast - id: check-added-large-files @@ -51,6 +51,6 @@ repos: - id: end-of-file-fixer - id: debug-statements - repo: https://github.com/fsfe/reuse-tool - rev: v1.0.0 + rev: v1.1.0 hooks: - id: reuse diff --git a/.reuse/dep5 b/.reuse/dep5 index 0f2bca5ed72e06f48a0cc91e8e6e5f7297f701b6..bdcdf1a2f37c5978e8aac19c017810b690eec4af 100644 --- a/.reuse/dep5 +++ b/.reuse/dep5 @@ -1,7 +1,7 @@ Format: https://www.debian.org/doc/packaging-manuals/copyright-format/1.0/ -Upstream-Name: exposed +Upstream-Name: clapp Upstream-Contact: Andre Anjos <andre.anjos@idiap.ch> -Source: https://gitlab.idiap.ch/bob/exposed +Source: https://gitlab.idiap.ch/software/clapp Files: tests/data/* Copyright: Copyright © 2022 Idiap Research Institute <contact@idiap.ch> diff --git a/README.md b/README.md index 8e3c638776adda2dbe81c57359fe2c713320c0b0..ef4cbdfafd2b83aa3336723df5f8571936e1b7bc 100644 --- a/README.md +++ b/README.md @@ -4,10 +4,10 @@ SPDX-FileCopyrightText: Copyright © 2022 Idiap Research Institute <contact@idia SPDX-License-Identifier: BSD-3-Clause --> -[](https://www.idiap.ch/software/bob/docs/bob/exposed/main/sphinx/index.html) -[](https://gitlab.idiap.ch/bob/exposed/commits/main) -[](https://www.idiap.ch/software/bob/docs/bob/exposed/main/coverage/index.html) -[](https://gitlab.idiap.ch/bob/exposed) +[](https://www.idiap.ch/software/biosignal/docs/software/clapp/main/sphinx/index.html) +[](https://gitlab.idiap.ch/software/clapp/commits/main) +[](https://www.idiap.ch/software/biosignal/docs/software/clapp/main/coverage/index.html) +[](https://gitlab.idiap.ch/software/clapp) # Configuration Support for Python Packages and CLIs diff --git a/doc/api.rst b/doc/api.rst index 792dced2192eca90122546db1e6c764dbcf16ced..2d2a8b90e0abc305a0256fb4569134456aa9405d 100644 --- a/doc/api.rst +++ b/doc/api.rst @@ -2,22 +2,22 @@ .. .. SPDX-License-Identifier: BSD-3-Clause -.. _exposed.api: +.. _clapp.api: ============ Python API ============ -This section includes information for using the Python API of ``exposed``. +This section includes information for using the Python API of ``clapp``. .. autosummary:: :toctree: api - exposed.rc - exposed.config - exposed.logging - exposed.click + clapp.rc + clapp.config + clapp.logging + clapp.click .. include:: links.rst diff --git a/doc/click.rst b/doc/click.rst index 392fd0e2920d9a38924bca1525e26f24b3873777..77da80f9a3e854387dbcd673d4382712648b992c 100644 --- a/doc/click.rst +++ b/doc/click.rst @@ -2,7 +2,7 @@ .. .. SPDX-License-Identifier: BSD-3-Clause -.. _exposed.click: +.. _clapp.click: ====================== Command-Line Helpers @@ -12,25 +12,25 @@ This package provides a few handy additions to the :py:mod:`click` command-line interface (CLI) library, allowing one to build even more powerful CLIs. -.. _exposed.click.verbosity: +.. _clapp.click.verbosity: Verbosity Option ---------------- -The :py:func:`exposed.click.verbosity_option` :py:mod:`click` decorator allows +The :py:func:`clapp.click.verbosity_option` :py:mod:`click` decorator allows one to control the logging-level of a pre-defined :py:class:logging.Logger. Here is an example usage. .. code-block:: python - import exposed.click + import clapp.click import logging # retrieve the base-package logger logger = logging.getLogger(__name__.split(".", 1)[0]) - @exposed.click.verbosity_option(logger) + @clapp.click.verbosity_option(logger) def cli(verbose): pass @@ -53,35 +53,35 @@ with the appropriate logging level, mapped as such: .. code-block:: python - @exposed.click.verbosity_option(logger, expose_value=False) + @clapp.click.verbosity_option(logger, expose_value=False) def cli(): pass -.. _exposed.click.configcommand: +.. _clapp.click.configcommand: Config Command -------------- -The :py:class:`exposed.click.ConfigCommand` is a type of +The :py:class:`clapp.click.ConfigCommand` is a type of :py:class:`click.Command` in which declared CLI options may be either passed -via the command-line, or loaded from a :ref:`exposed.config`. It works by +via the command-line, or loaded from a :ref:`clapp.config`. It works by reading the Python configuration file and filling up option values pretty much as :py:mod:`click` would do, with one exception: CLI options can now be of any Pythonic type. -To implement this, a CLI implemented via :py:class:`exposed.click.ConfigCommand` +To implement this, a CLI implemented via :py:class:`clapp.click.ConfigCommand` may not declare any arguments, only options. All arguments are interpreted as configuration files, from where option values will be set, in order. Any type of configuration resource can be provided (file paths, python modules or entry-points). Command-line options take precedence over values set in configuration files. The order of configuration files matters, and the final values for CLI options follow the same rules as in -:ref:`exposed.config.chain-loading`. +:ref:`clapp.config.chain-loading`. Options that may be read from configuration files must also be marked with the -custom click-type :py:class:`exposed.click.ResourceOption`. +custom click-type :py:class:`clapp.click.ResourceOption`. Here is an example usage of this class: @@ -130,13 +130,13 @@ Then, the application shown above would also be able to work like this: python example_cli.py my-config -Options with type :py:class:`exposed.click.ResourceOption` may also point to +Options with type :py:class:`clapp.click.ResourceOption` may also point to individual resources (specific variables on python modules). This may be, however, a more seldomly used feature. Read the class documentation for details. -.. _exposed.click.aliasedgroups: +.. _clapp.click.aliasedgroups: Aliased Command Groups ---------------------- @@ -145,7 +145,7 @@ When designing an CLI with multiple subcommands, it is sometimes useful to be able to shorten command names. For example, being able to use ``git ci`` instead of ``git commit``, is a form of aliasing. To do so in :py:mod:`click` CLIs, it suffices to subclass all command group instances with -:py:class:`exposed.click.AliasedGroup`. This should include groups and +:py:class:`clapp.click.AliasedGroup`. This should include groups and subgroups of any depth in your CLI. Here is an example usage: @@ -158,13 +158,13 @@ You may then shorten the command to be called such as this: .. command-output:: python example_alias.py pu -.. _exposed.click.config_helpers: +.. _clapp.click.config_helpers: Experiment Options (Config) Command-Group ----------------------------------------- When building complex CLIs in which support for `configuration -<:ref:exposed.config>`_ is required, it may be convenient to provide users with +<:ref:clapp.config>`_ is required, it may be convenient to provide users with CLI subcommands to display configuration resources (examples) shipped with the package. To this end, we provide an easy to plug :py:class:`click.Group` decorator that attaches a few useful subcommands to a predefined CLI command, @@ -186,7 +186,7 @@ You may try to use that example application like this: .. code-block:: shell # lists all installed resources in the entry-point-group - # "exposed.test.config" + # "clapp.test.config" $ python doc/example_config.py list module: tests.data complex @@ -225,13 +225,13 @@ You may try to use that example application like this: d=[1, 2, 37], ) -.. _exposed.click.rc_helpers: +.. _clapp.click.rc_helpers: Global Configuration (RC) Command-Group --------------------------------------- When building complex CLIs in which support for `global configuration -<:ref:exposed.rc>`_ is required, it may be convenient to provide users with CLI +<:ref:clapp.rc>`_ is required, it may be convenient to provide users with CLI subcommands to display current values, set or get the value of specific configuration variables. For example, the ``git`` CLI provides the ``git config`` command that fulfills this task. Here is an example on how to build a @@ -267,7 +267,7 @@ You may try to use that example application like this: $ -.. _exposed.click.entrypoins: +.. _clapp.click.entrypoins: Multi-package Command Groups ---------------------------- @@ -277,27 +277,27 @@ recommend you look into the `Click-Plugins extension module <click-plugins_>`_ as means to implement this in a Python-oriented way, using the `package entry-points`_ (plugin) mechanism. -.. _exposed.click.log_parameters: +.. _clapp.click.log_parameters: Log Parameters -------------- -The :py:func:`exposed.click.log_parameters` :py:mod:`click` method allows +The :py:func:`clapp.click.log_parameters` :py:mod:`click` method allows one to log the parameters used within the current click context and their value for debuging purposes. Here is an example usage. .. code-block:: python - import exposed.click + import clapp.click import logging # retrieve the base-package logger logger = logging.getLogger(__name__) - @exposed.click.verbosity_option(logger, short_name="vvv") + @clapp.click.verbosity_option(logger, short_name="vvv") def cli(verbose): - exposed.click.log_parameters(logger) + clapp.click.log_parameters(logger) A pre-defined :py:class:`logging.Logger` have to be provided and, optionally, a list of parameters to ignore can be provided as well, as a Tuple. diff --git a/doc/conf.py b/doc/conf.py index 762db8e1ccae7cf71ad3f912ae5729ea5074b923..99c31e2bc40fe2bbc967abdace444fe08bac4311 100644 --- a/doc/conf.py +++ b/doc/conf.py @@ -67,7 +67,7 @@ source_suffix = ".rst" master_doc = "index" # General information about the project. -project = "exposed" +project = "clapp" package = distribution(project) copyright = "%s, Idiap Research Institute" % time.strftime("%Y") @@ -98,7 +98,7 @@ owner = ["Idiap Research Institute"] html_theme = "furo" html_theme_options = { - "source_edit_link": f"https://gitlab.idiap.ch/bob/{project}/-/edit/main/doc/{{filename}}", + "source_edit_link": f"https://gitlab.idiap.ch/software/{project}/-/edit/main/doc/{{filename}}", } html_title = f"{project} {release}" diff --git a/doc/config.rst b/doc/config.rst index 0612e3608c3ca9c1806178d06d33eb45a7e0a1bb..b89eee1ad2bb5624e7e6ae5cdc8eead32823ef4d 100644 --- a/doc/config.rst +++ b/doc/config.rst @@ -2,7 +2,7 @@ .. .. SPDX-License-Identifier: BSD-3-Clause -.. _exposed.config: +.. _clapp.config: ==================================== Experimental Configuration Options @@ -29,21 +29,21 @@ Loading configuration options ----------------------------- There is only one single function that matters in this module: -:py:func:`exposed.config.load`. You should use it to load Python configuration +:py:func:`clapp.config.load`. You should use it to load Python configuration options: To load a configuration file, containing options into a dictionary mapping variable names to values (of any Python type), use -:py:func:`exposed.config.load`: +:py:func:`clapp.config.load`: .. doctest:: >>> import os.path - >>> from exposed.config import load + >>> from clapp.config import load >>> options = load([os.path.join(data, "basic_config.py")]) -If the function :py:func:`exposed.config.load` succeeds, it returns a +If the function :py:func:`clapp.config.load` succeeds, it returns a python module containing variables which represent the configuration resource. For example, if the file ``basic_config.py`` contained: @@ -62,14 +62,14 @@ Then, the object ``options`` would look like this: b = 3 -.. _exposed.config.chain-loading: +.. _clapp.config.chain-loading: Chain Loading ------------- It is possible to implement chain configuration loading and overriding by passing iterables with more than one filename to -:py:func:`exposed.config.load`. Suppose we have two configuration files +:py:func:`clapp.config.load`. Suppose we have two configuration files which must be loaded in sequence: .. literalinclude:: data/basic_config.py @@ -88,7 +88,7 @@ Then, one can chain-load them like this: .. doctest:: >>> import os.path - >>> from exposed.config import load + >>> from clapp.config import load >>> file1 = os.path.join(data, "basic_config.py") >>> file2 = os.path.join(data, "second_config.py") >>> configuration = load([file1, file2]) @@ -102,15 +102,15 @@ The user wanting to override the values needs to manage the overriding and the order in which the override happens. -.. _exposed.config.entry_points: +.. _clapp.config.entry_points: Entry Points and Python Modules ------------------------------- -The function :py:func:`exposed.config.load` can also load config files through +The function :py:func:`clapp.config.load` can also load config files through module entry-points, or Python module names. Entry-points are simply aliases to Python modules and objects. To load entry-points via -:py:func:`exposed.config.load`, you must provide the group name of the entry +:py:func:`clapp.config.load`, you must provide the group name of the entry points. For example, if in your package setup, you defined the following entry-points to 2 python modules such as the examples above: @@ -128,7 +128,7 @@ You could do the same as such: .. code-block:: python - >>> from exposed.config import load + >>> from clapp.config import load >>> configuration = load(["basic", "second"], entry_point_group="mypackage.config") >>> print(f"a = {configuration.a} \nb = {configuration.b} \nc = {configuration.c}") a = 1 @@ -139,7 +139,7 @@ Or even refer to the module names themselves (instead of the entry-point names): .. code-block:: python - >>> from exposed.config import load + >>> from clapp.config import load >>> configuration = load(["mypackage.config.basic", "mypackage.config.second"]) >>> print(f"a = {configuration.a} \nb = {configuration.b} \nc = {configuration.c}") a = 1 @@ -157,19 +157,19 @@ Of course, mixture of entry-point names, paths and module names are also accepta c = 4 -.. _exposed.config.resource: +.. _clapp.config.resource: Loading Single Objects ---------------------- -The function :py:func:`exposed.config.load` can also be used to load the +The function :py:func:`clapp.config.load` can also be used to load the contents of specific variables within configuration files. To do this, you need provide the name of an attribute to load. .. doctest:: >>> import os.path - >>> from exposed.config import load + >>> from clapp.config import load >>> load([os.path.join(data, "basic_config.py")], attribute_name="b") 3 diff --git a/doc/example_alias.py b/doc/example_alias.py index 53b299f495bfd422f1c55c72e13243a0f30008d0..eb5a232d5851051db49255f662281d1bf4a23518 100644 --- a/doc/example_alias.py +++ b/doc/example_alias.py @@ -10,10 +10,10 @@ import click -import exposed.click +import clapp.click -@click.group(cls=exposed.click.AliasedGroup) +@click.group(cls=clapp.click.AliasedGroup) def main(): pass diff --git a/doc/example_cli.py b/doc/example_cli.py index 199ba008933ad13dc5eafd39e81ce88185aa4503..ec1af4317b13874cdbb1ec7534166c1de99429ac 100644 --- a/doc/example_cli.py +++ b/doc/example_cli.py @@ -10,8 +10,8 @@ import click -from exposed.click import ConfigCommand, ResourceOption, verbosity_option -from exposed.logging import setup +from clapp.click import ConfigCommand, ResourceOption, verbosity_option +from clapp.logging import setup logger = setup(__name__.split(".", 1)[0]) @@ -40,11 +40,10 @@ Examples: cls=ResourceOption, ) @verbosity_option(logger=logger) -@click.version_option(package_name="exposed") +@click.version_option(package_name="clapp") @click.pass_context def main(ctx, **_): """Tests our Click interfaces.""" - # Add imports needed for your code here, and avoid spending time loading! # In this example, we just print the loaded options to demonstrate loading diff --git a/doc/example_config.py b/doc/example_config.py index 4a71788dd45dffc6850d679be995a87e5a1b665c..3fc69fcf317c38f7024bb6d07ddef434ad4a88c5 100644 --- a/doc/example_config.py +++ b/doc/example_config.py @@ -2,13 +2,13 @@ # # SPDX-License-Identifier: BSD-3-Clause -from exposed.click import config_group -from exposed.logging import setup +from clapp.click import config_group +from clapp.logging import setup logger = setup(__name__.split(".", 1)[0]) -@config_group(logger=logger, entry_point_group="exposed.test.config") +@config_group(logger=logger, entry_point_group="clapp.test.config") def main(**kwargs): """Use this command to list/describe/copy package config resources.""" pass diff --git a/doc/example_defaults.py b/doc/example_defaults.py index 5c64a81b3775caeac7511b82ec9086a886c81100..286eee24a318d6ba39fb4d5148ca78aa4ee907a5 100644 --- a/doc/example_defaults.py +++ b/doc/example_defaults.py @@ -2,9 +2,9 @@ # # SPDX-License-Identifier: BSD-3-Clause -from exposed.click import user_defaults_group -from exposed.logging import setup -from exposed.rc import UserDefaults +from clapp.click import user_defaults_group +from clapp.logging import setup +from clapp.rc import UserDefaults logger = setup(__name__.split(".", 1)[0]) rc = UserDefaults("myapp.toml", logger=logger) diff --git a/doc/example_logging.py b/doc/example_logging.py index aa363f4f1f6318cff6677931e8b8c581a3d80dc4..e9eb36d8bf635fdd934703997ce659d9a80b8a40 100644 --- a/doc/example_logging.py +++ b/doc/example_logging.py @@ -4,7 +4,7 @@ import logging -from exposed.logging import setup +from clapp.logging import setup logger = setup(__name__.split(".", 1)[0], format="%(levelname)s: %(message)s") logger.setLevel(logging.INFO) diff --git a/doc/index.rst b/doc/index.rst index 1e338aa6d183f8786314c011bee2941ad5339d08..7b55c09d88f73d0f6eed1f8c50ff9cc3149b281b 100644 --- a/doc/index.rst +++ b/doc/index.rst @@ -2,7 +2,7 @@ .. .. SPDX-License-Identifier: BSD-3-Clause -.. _exposed: +.. _clapp: ==================================================== Configuration Support for Python Packages and CLIs diff --git a/doc/install.rst b/doc/install.rst index 082aec2303ed27364c1af821fe0ade2d96cf8b8b..37e5594e6f982ebc4ff12cedcca2c22685691def 100644 --- a/doc/install.rst +++ b/doc/install.rst @@ -2,7 +2,7 @@ .. .. SPDX-License-Identifier: BSD-3-Clause -.. _exposed.install: +.. _clapp.install: ============== Installation @@ -17,28 +17,28 @@ combination from the tabbed pane below. .. code-block:: sh - pip install exposed + pip install clapp .. tab:: pip/beta .. code-block:: sh - pip install git+https://gitlab.idiap.ch/bob/exposed + pip install git+https://gitlab.idiap.ch/software/clapp .. tab:: conda/stable .. code-block:: sh - mamba install -c https://www.idiap.ch/software/bob/conda -c conda-forge exposed + mamba install -c https://www.idiap.ch/software/biosignal/conda -c conda-forge clapp .. tab:: conda/beta .. code-block:: sh - mamba install -c https://www.idiap.ch/software/bob/conda/label/beta -c conda-forge exposed + mamba install -c https://www.idiap.ch/software/biosignal/conda/label/beta -c conda-forge clapp .. include:: links.rst diff --git a/doc/logging.rst b/doc/logging.rst index 1ffdfc775a7e95324e7db32fd2ba18df80c007ca..36b3138ef901750d395cbe2ab4c82888f9d5ac08 100644 --- a/doc/logging.rst +++ b/doc/logging.rst @@ -2,7 +2,7 @@ .. .. SPDX-License-Identifier: BSD-3-Clause -.. _exposed.logging: +.. _clapp.logging: ============================ Logging Helpers and Policy @@ -10,17 +10,17 @@ We advise the use of the Python :py:mod:`logging` module to log messages from your library. If you are unfamiliar with the design and use of that standard -Python module, we suggest you read our :ref:`exposed.logging.rationale`. +Python module, we suggest you read our :ref:`clapp.logging.rationale`. We provide a single a method in this library to help setup a particular :py:class:`logging.Logger` to output to a (text-based) stream. The -documentation of :py:func:`exposed.logging.setup` explains in details what it +documentation of :py:func:`clapp.logging.setup` explains in details what it does. To use it in an application, follow this pattern: .. code-block:: python import logging - from exposed.logging import setup + from clapp.logging import setup logger = setup("mypackage", format="%(levelname)s: %(message)s") logger.setLevel(logging.INFO) # set log-level as you wish logger.info("test message") # use at application level, normally @@ -28,11 +28,11 @@ does. To use it in an application, follow this pattern: To help with setting the base logger level via the CLI, we provide a -:py:mod:`click` :ref:`exposed.click.verbosity`. A full example can be seen at -:ref:`exposed.click.configcommand` and :ref:`exposed.click.rc_helpers`. +:py:mod:`click` :ref:`clapp.click.verbosity`. A full example can be seen at +:ref:`clapp.click.configcommand` and :ref:`clapp.click.rc_helpers`. -.. _exposed.logging.rationale: +.. _clapp.logging.rationale: Logging Setup Rationale ----------------------- diff --git a/doc/rc.rst b/doc/rc.rst index 50c4e7e89afd134bcf6b879c20bcb6529a9bddf0..ee2a2863c486163baf6ede797e329e3bb318e180 100644 --- a/doc/rc.rst +++ b/doc/rc.rst @@ -2,7 +2,7 @@ .. .. SPDX-License-Identifier: BSD-3-Clause -.. _exposed.rc: +.. _clapp.rc: ============================== Global Configuration Options @@ -14,7 +14,7 @@ are local to a system or machine can be, for example, access credentials to a database, or the root location of files used in a Machine Learning (ML) pipeline. Typically, in these cases, developers want to allow users to configure such values once and have a programmatic way to access such values at -run time. Module :py:mod:`exposed.rc` provides code to facilitate the +run time. Module :py:mod:`clapp.rc` provides code to facilitate the implementation and setup of this functionality. @@ -22,18 +22,18 @@ Storage ------- Global configuration options are stored in TOML_ format, in a file whose -location is specified by you. The class :py:class:`exposed.rc.UserDefaults` can +location is specified by you. The class :py:class:`clapp.rc.UserDefaults` can load such a file and provide access to values set therein: .. code-block:: python - >>> from exposed import rc + >>> from clapp import rc >>> defaults = rc.UserDefaults("myapprc.toml") .. note:: If the input filename given upon the construction of - :py:class:`exposed.rc.UserDefaults` is not absolute, it is considered + :py:class:`clapp.rc.UserDefaults` is not absolute, it is considered relative to the value of the environment variable ``$XDG_CONFIG_HOME``. In UNIX-style operating systems, the above example would typically resolve to ``${HOME}/.config/myapprc.toml``. Check the `XDG defaults <xdg-defaults_>`_ @@ -44,7 +44,7 @@ Reading and writing values -------------------------- You may use dictionary methods to get and set variables on any -:py:class:`exposed.rc.UserDefaults`, besides all other methods related to +:py:class:`clapp.rc.UserDefaults`, besides all other methods related to mapping types (such as ``len()`` or ``setdefault()``). @@ -52,7 +52,7 @@ Writing changes back -------------------- To write changes back to the configuration file, use the -:py:meth:`exposed.rc.UserDefaults.write` method, which requires no parameters, +:py:meth:`clapp.rc.UserDefaults.write` method, which requires no parameters, writing directly to the "default" location set during construction: .. code-block:: python @@ -72,7 +72,7 @@ Adding a global RC functionality to your module To add a global object that reads user defaults into your application, we recommend you create a module containing a configured instance of -:py:class:`exposed.rc.UserDefaults`. Then, within your command-line interface, +:py:class:`clapp.rc.UserDefaults`. Then, within your command-line interface, import that module to trigger reading out the necessary variables. For example: @@ -80,7 +80,7 @@ example: .. code-block:: python # module "config" - from exposed.rc import UserDefaults + from clapp.rc import UserDefaults rc = UserDefaults("~/.myapprc.toml", "MYAPPRC") # module "cli" @@ -92,7 +92,7 @@ Defining a command-line interface to the RC functionality --------------------------------------------------------- We provide command plugins for you to define CLI-based get/set operations on -your configuration file. This is discussed at :ref:`exposed.click`. +your configuration file. This is discussed at :ref:`clapp.click`. .. include:: links.rst diff --git a/pyproject.toml b/pyproject.toml index 11a3083da63f3b2729bcbee3f9556a686d5fae3a..697304d710f5f38eb0346c82fce8a2fc521504a1 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -7,7 +7,7 @@ build-backend = "setuptools.build_meta" [project] -name = "exposed" +name = "clapp" version = "1.0.0b0" requires-python = ">=3.9" description = "Configuration Support for Python Packages and CLIs" @@ -32,10 +32,10 @@ dependencies = [ ] [project.urls] -documentation = "https://www.idiap.ch/software/bob/docs/bob/exposed/main/" -homepage = "https://pypi.org/project/exposed" -repository = "https://gitlab.idiap.ch/bob/exposed" -changelog = "https://gitlab.idiap.ch/bob/exposed/-/releases" +documentation = "https://www.idiap.ch/software/biosignal/docs/software/clapp/main/" +homepage = "https://pypi.org/project/clapp" +repository = "https://gitlab.idiap.ch/software/clapp" +changelog = "https://gitlab.idiap.ch/software/clapp/-/releases" [project.optional-dependencies] qa = ["pre-commit"] @@ -54,7 +54,7 @@ test = [ "coverage", ] -[project.entry-points."exposed.test.config"] +[project.entry-points."clapp.test.config"] first = "tests.data.basic_config" first-a = "tests.data.basic_config:a" first-b = "tests.data.basic_config:b" @@ -87,7 +87,7 @@ line-length = 80 [tool.pytest.ini_options] addopts = [ - "--cov=exposed", + "--cov=clapp", "--cov-report=term-missing", "--import-mode=append", ] diff --git a/src/exposed/__init__.py b/src/clapp/__init__.py similarity index 100% rename from src/exposed/__init__.py rename to src/clapp/__init__.py diff --git a/src/exposed/click.py b/src/clapp/click.py similarity index 97% rename from src/exposed/click.py rename to src/clapp/click.py index e95b2062c41e3254aa28b7a94a02866df7f8174a..057076536c47df4fe398e0e70ba1a0650c34d158 100644 --- a/src/exposed/click.py +++ b/src/clapp/click.py @@ -25,10 +25,10 @@ from .config import load, mod_to_context, resource_keys from .rc import UserDefaults module_logger = logging.getLogger(__name__) -"""Module logger""" +"""Module logger.""" _COMMON_CONTEXT_SETTINGS = dict(help_option_names=["-h", "--help"]) -"""Common click context settings""" +"""Common click context settings.""" def verbosity_option( @@ -84,7 +84,6 @@ def verbosity_option( def custom_verbosity_option(f): def callback(ctx, param, value): - ctx.meta[name] = value log_level: int = { 0: logging.ERROR, @@ -144,10 +143,10 @@ class ConfigCommand(click.Command): """ config_argument_name: str - """The name of the config argument""" + """The name of the config argument.""" entry_point_group: str - """The name of entry point that will be used to load the config files""" + """The name of entry point that will be used to load the config files.""" def __init__( self, @@ -157,7 +156,6 @@ class ConfigCommand(click.Command): entry_point_group: str | None = None, **kwargs: typing.Any, ) -> None: - self.entry_point_group = entry_point_group configs_argument_name = "CONFIG" @@ -297,7 +295,7 @@ class ResourceOption(click.Option): 2. If ``entry_point_group`` is provided, it will treat values given to it (by any means) as resources to be loaded. Loading is done using - :py:func:`.config.load`. Check :ref:`exposed.config.resource` for more + :py:func:`.config.load`. Check :ref:`clapp.config.resource` for more details on this topic. The final value cannot be a string. You may use this class in three ways: @@ -318,8 +316,10 @@ class ResourceOption(click.Option): entry_point_group: str | None """If provided, the strings values to this option are assumed to be entry - points from ``entry_point_group`` that need to be loaded. This may be - different than the wrapping :py:class:`ConfigCommand`.""" + points from ``entry_point_group`` that need to be loaded. + + This may be different than the wrapping :py:class:`ConfigCommand`. + """ string_exceptions: list[str] | None """If provided and ``entry_point_group`` is provided, the code will not @@ -345,7 +345,6 @@ class ResourceOption(click.Option): string_exceptions=None, **kwargs, ) -> None: - # By default, if unspecified, click options are converted to strings. # By using ResourceOption's, however, we allow for complex user types # to be set into options. So, if no specific ``type``, a ``default``, @@ -574,7 +573,6 @@ def user_defaults_group( The KEY may contain dots (``.``) to access values from subsections in the TOML_ document. """ - try: click.echo(config[key]) except KeyError: @@ -591,15 +589,18 @@ def user_defaults_group( def set(key: str, value: str, **_: typing.Any) -> None: """Sets the value for a key on the user-defaults file. - If ``key`` contains dots (``.``), then this sets nested subsection + If ``key`` contains dots (``.``), then this sets nested + subsection variables on the configuration file. Values are parsed and translated following the rules of TOML_. .. warning:: - This command will override the current configuration file and my - erase any user comments added by hand. To avoid this, simply - edit your configuration file by hand. + This command will override the current configuration file + and my + erase any user comments added by hand. To avoid this, + simply + edit your configuration file by hand. """ try: tmp = tomli.loads(f"v = {value}") @@ -627,15 +628,19 @@ def user_defaults_group( def rm(key: str, **_: typing.Any) -> None: """Removes the given key from the configuration file. - This command will remove the KEY from the configuration file. If - the input key corresponds to a section in the configuration file, + This command will remove the KEY from the configuration + file. If + the input key corresponds to a section in the configuration + file, then the whole configuration section will be removed. .. warning:: - This command will override the current configuration file and my - erase any user comments added by hand. To avoid this, simply - edit your configuration file by hand. + This command will override the current configuration file + and my + erase any user comments added by hand. To avoid this, + simply + edit your configuration file by hand. """ try: del config[key] @@ -705,7 +710,6 @@ def config_group( @verbosity_option(logger=logger) def list(ctx, **_: typing.Any): """Lists installed configuration resources.""" - from .config import _retrieve_entry_points entry_points: dict[str, EntryPoint] = { @@ -737,7 +741,6 @@ def config_group( entry_points_by_module[k][name] = ep for config_type in sorted(entry_points_by_module): - # calculates the longest config name so we offset the printing longest_name_length = max( len(k) for k in entry_points_by_module[config_type].keys() @@ -805,7 +808,6 @@ def config_group( @verbosity_option(logger=logger) def describe(ctx, name, **_: typing.Any): """Describes a specific configuration resource.""" - from .config import _retrieve_entry_points entry_points: dict[str, EntryPoint] = { diff --git a/src/exposed/config.py b/src/clapp/config.py similarity index 98% rename from src/exposed/config.py rename to src/clapp/config.py index ec8cdb7f59e53cfc68171de9d7389e0b97258417..8dcabc16c703e53771c3a85ba4081f809d7f9b3c 100644 --- a/src/exposed/config.py +++ b/src/clapp/config.py @@ -21,8 +21,7 @@ logger = logging.getLogger(__name__) _LOADED_CONFIGS = [] """Small gambiarra (https://www.urbandictionary.com/define.php?term=Gambiarra) -to avoid the garbage collector to collect some already imported modules. -""" +to avoid the garbage collector to collect some already imported modules.""" def _load_context(path: str, mod: types.ModuleType) -> types.ModuleType: @@ -54,7 +53,6 @@ def _load_context(path: str, mod: types.ModuleType) -> types.ModuleType: A python module with the fully resolved context """ - # executes the module code on the context of previously imported modules with open(path, "rb") as f: exec(compile(f.read(), path, "exec"), mod.__dict__) @@ -79,7 +77,6 @@ def _get_module_filename(module_name: str) -> str | None: The path that corresponds to file implementing the provided module name """ - try: loader = pkgutil.get_loader(module_name) if isinstance(loader, FileLoader): @@ -92,7 +89,6 @@ def _get_module_filename(module_name: str) -> str | None: def _object_name( path: str | pathlib.Path, common_name: str | None ) -> tuple[str, str | None]: - if isinstance(path, pathlib.Path): path = str(path) @@ -103,16 +99,19 @@ def _object_name( def _retrieve_entry_points(group: str) -> typing.Iterable[EntryPoint]: """Wraps various entry-point retrieval mechanisms. - For Python 3.9 and 3.10, :py:func:`importlib.metadata.entry_points()` - returns a dictionary keyed by entry-point group names. From Python 3.10 - onwards, one may pass the ``group`` keyword to that function to enable - pre-filtering, or use the ``select()`` method on the returned value, which + For Python 3.9 and 3.10, + :py:func:`importlib.metadata.entry_points()` + returns a dictionary keyed by entry-point group names. From Python + 3.10 + onwards, one may pass the ``group`` keyword to that function to + enable + pre-filtering, or use the ``select()`` method on the returned value, + which is no longer a dictionary. For anything before Python 3.8, you must use the backported library ``importlib_metadata``. """ - if sys.version_info[:2] < (3, 10): all_entry_points = entry_points() return all_entry_points.get(group, []) # Python 3.9 @@ -177,7 +176,6 @@ def _resolve_entry_point_or_modules( object_names = [] for path in paths: - module_name = ( "user_config" # fixed module name for files with full paths ) diff --git a/src/exposed/logging.py b/src/clapp/logging.py similarity index 100% rename from src/exposed/logging.py rename to src/clapp/logging.py diff --git a/src/exposed/rc.py b/src/clapp/rc.py similarity index 99% rename from src/exposed/rc.py rename to src/clapp/rc.py index 09163086f5cdd5bdcac7687985b01b0d8d10f542..3a93898cb755e1d613dd75f61d21390e438d4a3f 100644 --- a/src/exposed/rc.py +++ b/src/clapp/rc.py @@ -60,7 +60,6 @@ class UserDefaults(collections.abc.MutableMapping): path: str | pathlib.Path, logger: logging.Logger | None = None, ) -> None: - self.logger = logger or logging.getLogger(__name__) self.path = pathlib.Path(path).expanduser() @@ -74,7 +73,6 @@ class UserDefaults(collections.abc.MutableMapping): def read(self) -> None: """Reads configuration file, replaces any internal values.""" - if self.path.exists(): self.logger.debug( "User configuration file exists, reading contents..." @@ -108,7 +106,6 @@ class UserDefaults(collections.abc.MutableMapping): def write(self) -> None: """Stores any modifications done on the user configuration.""" - if self.path.exists(): backup = pathlib.Path(str(self.path) + "~") self.logger.debug(f"Backing-up {str(self.path)} -> {str(backup)}") @@ -125,7 +122,6 @@ class UserDefaults(collections.abc.MutableMapping): return t.getvalue().decode(encoding="utf-8") def __getitem__(self, k: str) -> typing.Any: - if k in self.data: return self.data[k] diff --git a/tests/conftest.py b/tests/conftest.py index b38c83dcb0dda21f65bfcd1df743fe303349c4f3..91064ebbb65540aec9bf9c6ab6d9d2042109b4fa 100644 --- a/tests/conftest.py +++ b/tests/conftest.py @@ -78,7 +78,6 @@ class MyCliRunner(CliRunner): @pytest.fixture def cli_runner(request) -> MyCliRunner: """A wrapper round Click's test CliRunner to improve usefulness.""" - return MyCliRunner( # workaround Click's environment isolation so debugging works. in_pdb=request.config.getoption("--pdb-trace") @@ -88,5 +87,4 @@ def cli_runner(request) -> MyCliRunner: @fixture def datadir(request) -> pathlib.Path: """Returns the directory in which the test is sitting.""" - return pathlib.Path(request.module.__file__).parents[0] / "data" diff --git a/tests/data/test_dump_config2.py b/tests/data/test_dump_config2.py index d6ea01e3f8badd84ccd0cf74a3de3b3bbd7a54fd..9764dbd00de34d1f38ac99b8118d1c66f5b93fa7 100644 --- a/tests/data/test_dump_config2.py +++ b/tests/data/test_dump_config2.py @@ -16,12 +16,12 @@ Blablabla bli blo. # database = None """Required parameter: database (--database, -d) -bla bla bla Can be a `exposed.test.config' entry point, a module name, or a path to a Python file which contains a variable named `database'. +bla bla bla Can be a `clapp.test.config' entry point, a module name, or a path to a Python file which contains a variable named `database'. Registered entries are: ['complex', 'complex-var', 'error-config', 'first', 'first-a', 'first-b', 'second', 'second-b', 'second-c', 'verbose-config']""" # annotator = None """Required parameter: annotator (--annotator, -a) -bli bli bli Can be a `exposed.test.config' entry point, a module name, or a path to a Python file which contains a variable named `annotator'. +bli bli bli Can be a `clapp.test.config' entry point, a module name, or a path to a Python file which contains a variable named `annotator'. Registered entries are: ['complex', 'complex-var', 'error-config', 'first', 'first-a', 'first-b', 'second', 'second-b', 'second-c', 'verbose-config']""" # output_dir = None diff --git a/tests/test_click.py b/tests/test_click.py index 251a5abef93ce112a659484525e42b4828196cce..4be1cf36084bbfadde1f23804cd62efd88cf0434 100644 --- a/tests/test_click.py +++ b/tests/test_click.py @@ -9,7 +9,7 @@ import click from click.testing import CliRunner -from exposed.click import ( +from clapp.click import ( AliasedGroup, ConfigCommand, ResourceOption, @@ -49,7 +49,7 @@ def test_prefix_aliasing(): def test_commands_with_config_1(): # random test - @click.command(cls=ConfigCommand, entry_point_group="exposed.test.config") + @click.command(cls=ConfigCommand, entry_point_group="clapp.test.config") def cli(**_): pass @@ -60,7 +60,7 @@ def test_commands_with_config_1(): def test_commands_with_config_2(): # test option with valid default value - @click.command(cls=ConfigCommand, entry_point_group="exposed.test.config") + @click.command(cls=ConfigCommand, entry_point_group="clapp.test.config") @click.option("-a", type=click.INT, cls=ResourceOption) def cli(a, **_): assert type(a) == int, (type(a), a) @@ -87,7 +87,7 @@ def test_commands_with_config_2(): def test_commands_with_config_3(): # test required options - @click.command(cls=ConfigCommand, entry_point_group="exposed.test.config") + @click.command(cls=ConfigCommand, entry_point_group="clapp.test.config") @click.option("-a", cls=ResourceOption, required=True) def cli(a, **_): click.echo(f"{a}") @@ -168,13 +168,13 @@ def test_config_dump(tmp_path, datadir): def test_config_dump2(tmp_path, datadir): - @click.command(cls=ConfigCommand, entry_point_group="exposed.test.config") + @click.command(cls=ConfigCommand, entry_point_group="clapp.test.config") @click.option( "--database", "-d", required=True, cls=ResourceOption, - entry_point_group="exposed.test.config", + entry_point_group="clapp.test.config", help="bla bla bla", ) @click.option( @@ -182,7 +182,7 @@ def test_config_dump2(tmp_path, datadir): "-a", required=True, cls=ResourceOption, - entry_point_group="exposed.test.config", + entry_point_group="clapp.test.config", help="bli bli bli", ) @click.option( @@ -235,7 +235,7 @@ def test_config_dump2(tmp_path, datadir): def test_config_command_with_callback_options(): - @click.command(cls=ConfigCommand, entry_point_group="exposed.test.config") + @click.command(cls=ConfigCommand, entry_point_group="clapp.test.config") @verbosity_option( logging.getLogger(__name__), envvar="VERBOSE", cls=ResourceOption ) @@ -261,7 +261,7 @@ def test_resource_option(): # test usage without ConfigCommand and with entry_point_group @click.command() @click.option( - "-a", "--a", cls=ResourceOption, entry_point_group="exposed.test.config" + "-a", "--a", cls=ResourceOption, entry_point_group="clapp.test.config" ) def cli1(a): assert a == 1 @@ -290,7 +290,7 @@ def test_resource_option(): "--a", cls=ResourceOption, string_exceptions=("tests.data.basic_config"), - entry_point_group="exposed.test.config", + entry_point_group="clapp.test.config", ) def cli3(a): assert a == "tests.data.basic_config" diff --git a/tests/test_config.py b/tests/test_config.py index 5e33e1a19908fa9b9cc4638b720885e06b8c463a..7d4fbe7fcc49f084176a3860b01e03072874d590 100644 --- a/tests/test_config.py +++ b/tests/test_config.py @@ -9,9 +9,9 @@ import pytest from click.testing import CliRunner -from exposed.click import config_group -from exposed.config import load, mod_to_context -from exposed.logging import setup as logger_setup +from clapp.click import config_group +from clapp.config import load, mod_to_context +from clapp.logging import setup as logger_setup def test_basic(datadir): @@ -59,7 +59,7 @@ def test_config_with_module(): def test_config_with_entry_point(): c = load( - ["first", "second", "complex"], entry_point_group="exposed.test.config" + ["first", "second", "complex"], entry_point_group="clapp.test.config" ) assert hasattr(c, "a") and c.a == 1 assert hasattr(c, "b") and c.b == 6 @@ -68,7 +68,7 @@ def test_config_with_entry_point(): def test_config_with_entry_point_file_missing(): with pytest.raises(ValueError): - load(["error-config"], entry_point_group="exposed.test.config") + load(["error-config"], entry_point_group="clapp.test.config") def test_config_with_mixture(datadir): @@ -78,7 +78,7 @@ def test_config_with_mixture(datadir): "tests.data.second_config", "complex", ], - entry_point_group="exposed.test.config", + entry_point_group="clapp.test.config", ) assert hasattr(c, "a") and c.a == 1 assert hasattr(c, "b") and c.b == 6 @@ -110,7 +110,7 @@ def cli_messages(): high_level_stream=messages, ) - @config_group(logger=logger, entry_point_group="exposed.test.config") + @config_group(logger=logger, entry_point_group="clapp.test.config") def cli(**_): """This is the documentation provided by the user.""" pass diff --git a/tests/test_logging.py b/tests/test_logging.py index 4813fd8b78a016f48dbd59db8d1269b8a7c84a51..684656d385fc90fae407abbae105780c5042f5cd 100644 --- a/tests/test_logging.py +++ b/tests/test_logging.py @@ -9,16 +9,16 @@ import click from click.testing import CliRunner -import exposed.logging +import clapp.logging -from exposed.click import verbosity_option +from clapp.click import verbosity_option def test_logger_setup(): lo = io.StringIO() hi = io.StringIO() - logger = exposed.logging.setup( + logger = clapp.logging.setup( "awesome.logger", format="%(message)s", low_level_stream=lo, @@ -40,7 +40,7 @@ def test_logger_click_no_v(): lo = io.StringIO() hi = io.StringIO() - logger = exposed.logging.setup( + logger = clapp.logging.setup( "awesome.logger", format="%(message)s", low_level_stream=lo, @@ -68,7 +68,7 @@ def test_logger_click_v(): lo = io.StringIO() hi = io.StringIO() - logger = exposed.logging.setup( + logger = clapp.logging.setup( "awesome.logger", format="%(message)s", low_level_stream=lo, @@ -95,7 +95,7 @@ def test_logger_click_vv(): lo = io.StringIO() hi = io.StringIO() - logger = exposed.logging.setup( + logger = clapp.logging.setup( "awesome.logger", format="%(message)s", low_level_stream=lo, @@ -122,7 +122,7 @@ def test_logger_click_vvv(): lo = io.StringIO() hi = io.StringIO() - logger = exposed.logging.setup( + logger = clapp.logging.setup( "awesome.logger", format="%(message)s", low_level_stream=lo, @@ -149,7 +149,7 @@ def test_logger_click_3x_verbose(): lo = io.StringIO() hi = io.StringIO() - logger = exposed.logging.setup( + logger = clapp.logging.setup( "awesome.logger", format="%(message)s", low_level_stream=lo, @@ -176,7 +176,7 @@ def test_logger_click_3x_verb(): lo = io.StringIO() hi = io.StringIO() - logger = exposed.logging.setup( + logger = clapp.logging.setup( "awesome.logger", format="%(message)s", low_level_stream=lo, diff --git a/tests/test_rc.py b/tests/test_rc.py index 8f34d957b1500e82384eba96501f1e82f91db2da..fe350d6c62fceed86ee4fd93b8ac9cd544eaf708 100644 --- a/tests/test_rc.py +++ b/tests/test_rc.py @@ -11,8 +11,8 @@ import pytest from click.testing import CliRunner -from exposed.click import user_defaults_group -from exposed.rc import UserDefaults +from clapp.click import user_defaults_group +from clapp.rc import UserDefaults def _check_userdefaults_ex1_contents(rc):