././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/0000755000175000017500000000000014324634246011764 5ustar00roamroam././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666397759.0 cfg_diag-0.4.0/.editorconfig0000644000175000017500000000052514324633077014444 0ustar00roamroam# https://editorconfig.org/ root = true [*] end_of_line = lf insert_final_newline = true charset = utf-8 [*.md] indent_style = space indent_size = 2 [*.nix] indent_style = space indent_size = 2 [*.py] indent_style = space indent_size = 4 [setup.cfg] indent_style = space indent_size = 4 [tox.ini] indent_style = space indent_size = 4 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666397951.0 cfg_diag-0.4.0/CONTRIBUTING.md0000644000175000017500000000266514324633377014232 0ustar00roamroam# Recommended workflow for hacking on cfg-diag ## Source code formatting The `cfg-diag` source is formatted automatically using the `black` tool. Run `tox -e black-reformat` or `nox -s black-reformat -- do reformat` to reformat the Python source files. Run `tox -e black` or `nox -s black` to verify that the source files are formatted correctly. The Nix expressions in the `nix/` subdirectory are formatted automatically using the `nixpkgs-fmt` tool. Run `nix/reformat.sh` to invoke it. ## Coding style, type safety, etc The `cfg-diag` source should pass static checks via the `flake8` and `pylint` tools and type checks via the `mypy` tool. Run `tox -p all -e pep8,pylint,mypy` or `nox -s pep8 pylint mypy` to run the static checkers. ## Unit tests The `cfg-diag` library has a set of trivial unit tests to ensure that the `.diag()` and `.diag_()` methods of all the classes behave as expected. Run `tox -e unit_tests` or `nox -s unit_tests` to run the tests. ## Doing it all in one step All the tests defined in the `tox.ini` file can be run using [the tox-delay tool][tox-delay]: `tox-delay -p all -e unit_tests` All the tests defined in the `noxfile.py` file can be run using [the nox-stages tool][nox-stages]: `nox-stages run '@check' '@tests'` [tox-delay]: https://devel.ringlet.net/devel/tox-delay/ (Run some Tox tests after others have completed) [nox-stages]: https://gitlab.com/ppentchev/nox-dump (Run Nox sessions in parallel, in stages) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1661978668.0 cfg_diag-0.4.0/LICENSE0000644000175000017500000000240214303744054012763 0ustar00roamroamCopyright (c) 2021, 2022 StorPool All rights reserved. Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666397951.0 cfg_diag-0.4.0/MANIFEST.in0000644000175000017500000000026414324633377013530 0ustar00roamroaminclude .editorconfig include CONTRIBUTING.md include LICENSE include NEWS.md recursive-include nix *.nix *.sh include noxfile.py include tox.ini recursive-include unit_tests *.py ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666398075.0 cfg_diag-0.4.0/NEWS.md0000644000175000017500000000422114324633573013063 0ustar00roamroam# Change log for cfg-diag library ## 0.4.0 - API break: restore the API of the `ConfigDiag*` class hierarchy: only a single `.diag()` method that accepts a fixed string. Add the new `Config*` class hierarchy with the `.diag(lambda)` and `.diag(string)` API - correctly refer to the `.diag_()` method for fixed strings in the documentation - add Nix expressions for running the tests - start a CONTRIBUTING.md document describing a possible workflow ## 0.3.1 - add the year 2022 to the copyright notices - relicense a unit test file to StorPool - include the noxfile in the sdist tarball ## 0.3.0 - API break: the `.diag()` method now accepts a callable function that will only be invoked if needed; it must return the string to be output - add the `.diag_()` method for strings that are not expensive to build - list Python 3.11 as a supported version - various Tox configuration and testing clean-ups: - use Pylint 2.14; it no longer outputs the `no-self-use` lint - drop the flake8 + hacking environment, it is incompatible with recent versions of flake8 - use `pytest.mark.parametrize()` instead of the ddt module - add both lower and upper version constraints to most of the package dependencies in the Tox test environments - move the mypy configuration to the pyproject.toml file - use types-setuptools and drop a `type: ignore` annotation - add a Nox definitions file ## 0.2.1 - include the .editorconfig file in the sdist tarball ## 0.2.0 - drop Python 3.6 as a supported version, add Python 3.10 instead - reformat the source code using black 22 and 100 characters per line - drop the useless basepython definition from the Tox test environments - add a pytest >= 6 dependency for the mypy environment and drop the type stubs - reflow the tox.ini file with a four-character indent - add an EditorConfig definitions file - add a flake8 + hacking Tox test environment ## 0.1.1 - move some tool configuration options to setup.py and pyproject.toml - add Python 3.9 as a supported version - use unittest.mock instead of mock - fix a code comment - add a manifest to include more files in the source distribution ## 0.1.0 - first public release ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/PKG-INFO0000644000175000017500000000617614324634246013073 0ustar00roamroamMetadata-Version: 2.1 Name: cfg_diag Version: 0.4.0 Summary: A common configuration-storage class with a .diag() method Home-page: https://github.com/storpool/python-cfg_diag Author: Peter Pentchev Author-email: support@storpool.com License: BSD-2 Keywords: version Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: DFSG approved Classifier: License :: Freely Distributable Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Description-Content-Type: text/markdown License-File: LICENSE # A common configuration-storage class with a .diag() method ## Description This module provides four classes that may be used as base classes for storing program runtime configuration with a `verbose` boolean field. The classes provide a `.diag(func)` method that will check the object's `verbose` field and, if it is set to a true value, invoke the specified function and output the message that it returns. If the message is not expensive to format (e.g. it does not include stringifying elaborate data structures), the `.diag_(msg)` method may be used instead. The `ConfigUnfrozen` and `ConfigUnfrozenStdOut` classes are normal dataclasses, while the `Config` and `ConfigStdOut` ones are frozen. The `Config` and `ConfigUnfrozen` classes will output any diagnostic messages to the standard error stream, while the `ConfigStdOut` and `ConfigUnfrozenStdOut` ones will output the diagnostic messages to the standard output stream. For compatibility with `cfg-diag` versions 0.1.x and 0.2.x, there is also a parallel `ConfigDiag*` class hierarchy; the classes there are organized in exactly the same way as those in the `Config*` hierarchy, but they only provide a single `.diag(msg)` method that accepts a fixed, already-built, string instead of a callback function. These classes are deprecated and will most probably be removed in a future version of the `cfg-diag` library. ## Example Subclass the frozen `Config` class, add a program-specific field: @dataclasses.dataclass(frozen=True) class Config(cfg_diag.Config): """Runtime configuration for the fribble program.""" path: pathlib.Path Initialize this class from an argument parser object: return Config(path=args.path, verbose=args.verbose) Output a diagnostic message if requested: cfg.diag_("This will either appear or it will not") cfg.diag(lambda: f"Here's the thing: {thing!r}") ## Contact This module is [developed in a GitHub repository][github]. Contact [the StorPool support team][support] for information. [github]: https://github.com/storpool/python-cfg_diag [support]: mailto:support@storpool.com ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666369508.0 cfg_diag-0.4.0/README.md0000644000175000017500000000410714324543744013247 0ustar00roamroam# A common configuration-storage class with a .diag() method ## Description This module provides four classes that may be used as base classes for storing program runtime configuration with a `verbose` boolean field. The classes provide a `.diag(func)` method that will check the object's `verbose` field and, if it is set to a true value, invoke the specified function and output the message that it returns. If the message is not expensive to format (e.g. it does not include stringifying elaborate data structures), the `.diag_(msg)` method may be used instead. The `ConfigUnfrozen` and `ConfigUnfrozenStdOut` classes are normal dataclasses, while the `Config` and `ConfigStdOut` ones are frozen. The `Config` and `ConfigUnfrozen` classes will output any diagnostic messages to the standard error stream, while the `ConfigStdOut` and `ConfigUnfrozenStdOut` ones will output the diagnostic messages to the standard output stream. For compatibility with `cfg-diag` versions 0.1.x and 0.2.x, there is also a parallel `ConfigDiag*` class hierarchy; the classes there are organized in exactly the same way as those in the `Config*` hierarchy, but they only provide a single `.diag(msg)` method that accepts a fixed, already-built, string instead of a callback function. These classes are deprecated and will most probably be removed in a future version of the `cfg-diag` library. ## Example Subclass the frozen `Config` class, add a program-specific field: @dataclasses.dataclass(frozen=True) class Config(cfg_diag.Config): """Runtime configuration for the fribble program.""" path: pathlib.Path Initialize this class from an argument parser object: return Config(path=args.path, verbose=args.verbose) Output a diagnostic message if requested: cfg.diag_("This will either appear or it will not") cfg.diag(lambda: f"Here's the thing: {thing!r}") ## Contact This module is [developed in a GitHub repository][github]. Contact [the StorPool support team][support] for information. [github]: https://github.com/storpool/python-cfg_diag [support]: mailto:support@storpool.com ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/nix/0000755000175000017500000000000014324634246012562 5ustar00roamroam././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666397951.0 cfg_diag-0.4.0/nix/python-pytest.nix0000644000175000017500000000054714324633377016163 0ustar00roamroam{ pkgs ? import { } , py-ver ? 311 }: let python-name = "python${toString py-ver}"; python = builtins.getAttr python-name pkgs; python-pkgs = python.withPackages (p: with p; [ pytest ]); in pkgs.mkShell { buildInputs = [ python-pkgs ]; shellHook = '' set -e PYTHONPATH="$(pwd)/src" python3 -m pytest -v unit_tests exit ''; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666397854.0 cfg_diag-0.4.0/nix/python-tox.nix0000644000175000017500000000070114324633236015427 0ustar00roamroam{ pkgs ? import { } , py-ver ? 311 }: let python-name = "python${toString py-ver}"; python = builtins.getAttr python-name pkgs; # tomli is needed until https://github.com/NixOS/nixpkgs/pull/194020 goes in python-pkgs = python.withPackages (p: with p; [ tomli tox ]); in pkgs.mkShell { buildInputs = [ python-pkgs ]; shellHook = '' set -e TOX_SKIP_ENV=unit_tests tox -p all tox -p all -e unit_tests exit ''; } ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666395287.0 cfg_diag-0.4.0/nix/reformat.sh0000755000175000017500000000024414324626227014740 0ustar00roamroam#!/bin/sh set -e script_path="$(readlink -f -- "$0")" nix_dir="$(dirname -- "$script_path")" nix-shell --pure -p nixpkgs-fmt --run "nixpkgs-fmt '$nix_dir'/*.nix" ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1661552391.0 cfg_diag-0.4.0/noxfile.py0000644000175000017500000000733614302243407014002 0ustar00roamroam# # Copyright (c) 2022 StorPool # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # """Test definitions for the Nox test runner.""" import pathlib import tempfile import nox nox.needs_version = ">= 2022.8.7" PYFILES = [ "noxfile.py", "src/cfg_diag", "setup.py", "unit_tests", ] DEPS = { "black": ["black >= 22, < 23"], "pytest": ["pytest >= 7, < 8"], "test": ["pytest >= 7, < 8"], } @nox.session(name="black-reformat") def black_reformat(session: nox.Session) -> None: """Reformat the source files using the black tool.""" if session.posargs != ["do", "reformat"]: raise Exception("The black-reformat session is special") session.install(*DEPS["black"]) session.run("black", *PYFILES) @nox.session(tags=["check"]) def black(session: nox.Session) -> None: """Run the black format checker on the source files.""" session.install(*DEPS["black"]) session.run("black", "--check", *PYFILES) @nox.session(tags=["check"]) def pep8(session: nox.Session) -> None: """Run the flake8 checker on the source files.""" session.install("flake8 >= 5, < 6") session.run("flake8", *PYFILES) @nox.session(tags=["check"]) def mypy(session: nox.Session) -> None: """Run the mypy type checker on the source files.""" session.install("mypy >= 0.942", "nox >= 2022.8.7", "types-setuptools", *DEPS["pytest"]) session.run("mypy", *PYFILES) @nox.session(tags=["check"]) def pylint(session: nox.Session) -> None: """Run the pylint checker on the source files.""" session.install("nox >= 2022.8.7", "pylint >= 2.14, < 2.16", *DEPS["pytest"]) session.run("pylint", *PYFILES) @nox.session(tags=["tests"]) def unit_tests(session: nox.Session) -> None: """Build a wheel, run the unit test suite using pytest.""" with tempfile.TemporaryDirectory() as tempd_obj: tempd = pathlib.Path(tempd_obj) print(f"Using {tempd} as a temporary directory") session.install("build") session.run("python3", "-m", "build", "--wheel", "--outdir", str(tempd)) files = [item for item in tempd.iterdir() if item.is_file()] if len(files) != 1: raise Exception(f"Unexpected number of files in {tempd}: {files!r}") wheel = files[0] if not (wheel.name.startswith("cfg_diag-") and wheel.name.endswith(".whl")): raise Exception(f"Unexpected built wheel filename: {wheel}") print(f"Found a wheel: {wheel}") session.install(str(wheel), *DEPS["pytest"]) session.run("pytest", "unit_tests") ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666369508.0 cfg_diag-0.4.0/pyproject.toml0000644000175000017500000000033014324543744014676 0ustar00roamroam[build-system] requires = ["setuptools", "wheel"] build-backend = "setuptools.build_meta" [tool.black] line-length = 100 [tool.mypy] strict = true python_version = "3.7" [tool.pylint.messages] disable = ["R0801"] ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/setup.cfg0000644000175000017500000000224114324634246013604 0ustar00roamroam[metadata] name = cfg_diag version = attr: cfg_diag.VERSION author = Peter Pentchev author_email = support@storpool.com description = A common configuration-storage class with a .diag() method long_description = file: README.md long_description_content_type = text/markdown license = BSD-2 keywords = version url = https://github.com/storpool/python-cfg_diag classifiers = Development Status :: 5 - Production/Stable Intended Audience :: Developers License :: DFSG approved License :: Freely Distributable License :: OSI Approved :: BSD License Operating System :: OS Independent Programming Language :: Python Programming Language :: Python :: 3 Programming Language :: Python :: 3.7 Programming Language :: Python :: 3.8 Programming Language :: Python :: 3.9 Programming Language :: Python :: 3.10 Programming Language :: Python :: 3.11 Topic :: Software Development :: Libraries Topic :: Software Development :: Libraries :: Python Modules [options] zip_safe = True package_dir = = src packages = cfg_diag setup_requires = install_requires = [options.package_data] cfg_diag = py.typed [flake8] max_line_length = 100 [egg_info] tag_build = tag_date = 0 ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1661978668.0 cfg_diag-0.4.0/setup.py0000755000175000017500000000265314303744054013503 0ustar00roamroam#!/usr/bin/python3 # # Copyright (c) 2021, 2022 StorPool # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # """Setup configuration for the common config class module.""" import setuptools setuptools.setup() ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/src/0000755000175000017500000000000014324634246012553 5ustar00roamroam././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/src/cfg_diag/0000755000175000017500000000000014324634246014276 5ustar00roamroam././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666369508.0 cfg_diag-0.4.0/src/cfg_diag/__init__.py0000644000175000017500000001416414324543744016417 0ustar00roamroam# # Copyright (c) 2021, 2022 StorPool # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # """A common configuration-storage class with a .diag() method. This module provides four classes that may be used as base classes for storing program runtime configuration with a `verbose` boolean field. The classes provide a `.diag(func)` method that will check the object's `verbose` field and, if it is set to a true value, invoke the specified function and output the message that it returns. If the message is not expensive to format (e.g. it does not include stringifying elaborate data structures), the `.diag_(msg)` method may be used instead. The `ConfigUnfrozen` and `ConfigUnfrozenStdOut` classes are normal dataclasses, while the `Config` and `ConfigStdOut` ones are frozen. The `Config` and `ConfigUnfrozen` classes will output any diagnostic messages to the standard error stream, while the `ConfigStdOut` and `ConfigUnfrozenStdOut` ones will output the diagnostic messages to the standard output stream. For compatibility with cfg-diag versions 0.1.x and 0.2.x, there is also a parallel `ConfigDiag*` class hierarchy; the classes there are organized in exactly the same way as those in the `Config*` hierarchy, but they only provide a single `.diag(msg)` method that accepts a fixed, already-built, string instead of a callback function. These classes are deprecated and will most probably be removed in a future version of the `cfg-diag` library. Example: Subclass the frozen `Config` class, add a program-specific field: @dataclasses.dataclass(frozen=True) class Config(cfg_diag.Config): '''Runtime configuration for the fribble program.''' path: pathlib.Path Initialize this class from an argument parser object: return Config(path=args.path, verbose=args.verbose) Output a diagnostic message if requested: cfg.diag_("This will either appear or it will not") cfg.diag(lambda: f"Here's the thing: {thing!r}") """ import dataclasses import sys from typing import Callable VERSION = "0.4.0" class ConfigBase: """Output diagnostic messages if requested. Child classes MUST define a boolean-like `verbose` attribute! """ # pylint: disable=too-few-public-methods _config_diag_to_stderr = True def diag(self, func: Callable[[], str]) -> None: """Output the diagnostic message returned by the function if requested.""" if self.verbose: # type: ignore # pylint: disable=no-member print( func(), file=sys.stderr if self._config_diag_to_stderr else sys.stdout, ) def diag_(self, msg: str) -> None: """Output a diagnostic message if requested.""" self.diag(lambda: msg) @dataclasses.dataclass class ConfigUnfrozen(ConfigBase): """A base class for configuration storage.""" verbose: bool @dataclasses.dataclass(frozen=True) class Config(ConfigBase): """A frozen base class for configuration storage.""" verbose: bool @dataclasses.dataclass class ConfigUnfrozenStdOut(ConfigUnfrozen): """A base class that outputs diagnostic messages to stdout.""" def __post_init__(self) -> None: """Redirect the output to the standard output stream.""" self._config_diag_to_stderr = False @dataclasses.dataclass(frozen=True) class ConfigStdOut(Config): """A frozen base class with diagnostic messages output to stdout.""" def __post_init__(self) -> None: """Redirect the output to the standard output stream.""" object.__setattr__(self, "_config_diag_to_stderr", False) class ConfigDiagBase: """Output diagnostic messages if requested. Child classes MUST define a boolean-like `verbose` attribute! """ # pylint: disable=too-few-public-methods _config_diag_to_stderr = True def diag(self, msg: str) -> None: """Output a diagnostic message if requested.""" if self.verbose: # type: ignore # pylint: disable=no-member print( msg, file=sys.stderr if self._config_diag_to_stderr else sys.stdout, ) @dataclasses.dataclass class ConfigDiagUnfrozen(ConfigDiagBase): """A base class for configuration storage.""" verbose: bool @dataclasses.dataclass(frozen=True) class ConfigDiag(ConfigDiagBase): """A frozen base class for configuration storage.""" verbose: bool @dataclasses.dataclass class ConfigDiagUnfrozenStdOut(ConfigDiagUnfrozen): """A base class that outputs diagnostic messages to stdout.""" def __post_init__(self) -> None: """Redirect the output to the standard output stream.""" self._config_diag_to_stderr = False @dataclasses.dataclass(frozen=True) class ConfigDiagStdOut(ConfigDiag): """A frozen base class with diagnostic messages output to stdout.""" def __post_init__(self) -> None: """Redirect the output to the standard output stream.""" object.__setattr__(self, "_config_diag_to_stderr", False) ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616370679.0 cfg_diag-0.4.0/src/cfg_diag/py.typed0000644000175000017500000000000014025755767015775 0ustar00roamroam././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/src/cfg_diag.egg-info/0000755000175000017500000000000014324634246015770 5ustar00roamroam././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666398373.0 cfg_diag-0.4.0/src/cfg_diag.egg-info/PKG-INFO0000644000175000017500000000617614324634245017076 0ustar00roamroamMetadata-Version: 2.1 Name: cfg-diag Version: 0.4.0 Summary: A common configuration-storage class with a .diag() method Home-page: https://github.com/storpool/python-cfg_diag Author: Peter Pentchev Author-email: support@storpool.com License: BSD-2 Keywords: version Classifier: Development Status :: 5 - Production/Stable Classifier: Intended Audience :: Developers Classifier: License :: DFSG approved Classifier: License :: Freely Distributable Classifier: License :: OSI Approved :: BSD License Classifier: Operating System :: OS Independent Classifier: Programming Language :: Python Classifier: Programming Language :: Python :: 3 Classifier: Programming Language :: Python :: 3.7 Classifier: Programming Language :: Python :: 3.8 Classifier: Programming Language :: Python :: 3.9 Classifier: Programming Language :: Python :: 3.10 Classifier: Programming Language :: Python :: 3.11 Classifier: Topic :: Software Development :: Libraries Classifier: Topic :: Software Development :: Libraries :: Python Modules Description-Content-Type: text/markdown License-File: LICENSE # A common configuration-storage class with a .diag() method ## Description This module provides four classes that may be used as base classes for storing program runtime configuration with a `verbose` boolean field. The classes provide a `.diag(func)` method that will check the object's `verbose` field and, if it is set to a true value, invoke the specified function and output the message that it returns. If the message is not expensive to format (e.g. it does not include stringifying elaborate data structures), the `.diag_(msg)` method may be used instead. The `ConfigUnfrozen` and `ConfigUnfrozenStdOut` classes are normal dataclasses, while the `Config` and `ConfigStdOut` ones are frozen. The `Config` and `ConfigUnfrozen` classes will output any diagnostic messages to the standard error stream, while the `ConfigStdOut` and `ConfigUnfrozenStdOut` ones will output the diagnostic messages to the standard output stream. For compatibility with `cfg-diag` versions 0.1.x and 0.2.x, there is also a parallel `ConfigDiag*` class hierarchy; the classes there are organized in exactly the same way as those in the `Config*` hierarchy, but they only provide a single `.diag(msg)` method that accepts a fixed, already-built, string instead of a callback function. These classes are deprecated and will most probably be removed in a future version of the `cfg-diag` library. ## Example Subclass the frozen `Config` class, add a program-specific field: @dataclasses.dataclass(frozen=True) class Config(cfg_diag.Config): """Runtime configuration for the fribble program.""" path: pathlib.Path Initialize this class from an argument parser object: return Config(path=args.path, verbose=args.verbose) Output a diagnostic message if requested: cfg.diag_("This will either appear or it will not") cfg.diag(lambda: f"Here's the thing: {thing!r}") ## Contact This module is [developed in a GitHub repository][github]. Contact [the StorPool support team][support] for information. [github]: https://github.com/storpool/python-cfg_diag [support]: mailto:support@storpool.com ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666398373.0 cfg_diag-0.4.0/src/cfg_diag.egg-info/SOURCES.txt0000644000175000017500000000073114324634245017654 0ustar00roamroam.editorconfig CONTRIBUTING.md LICENSE MANIFEST.in NEWS.md README.md noxfile.py pyproject.toml setup.cfg setup.py tox.ini nix/python-pytest.nix nix/python-tox.nix nix/reformat.sh src/cfg_diag/__init__.py src/cfg_diag/py.typed src/cfg_diag.egg-info/PKG-INFO src/cfg_diag.egg-info/SOURCES.txt src/cfg_diag.egg-info/dependency_links.txt src/cfg_diag.egg-info/top_level.txt src/cfg_diag.egg-info/zip-safe unit_tests/__init__.py unit_tests/test_cfg.py unit_tests/test_cfg_diag.py././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666398373.0 cfg_diag-0.4.0/src/cfg_diag.egg-info/dependency_links.txt0000644000175000017500000000000114324634245022035 0ustar00roamroam ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666398373.0 cfg_diag-0.4.0/src/cfg_diag.egg-info/top_level.txt0000644000175000017500000000001114324634245020511 0ustar00roamroamcfg_diag ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666398373.0 cfg_diag-0.4.0/src/cfg_diag.egg-info/zip-safe0000644000175000017500000000000114324634245017417 0ustar00roamroam ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1661552391.0 cfg_diag-0.4.0/tox.ini0000644000175000017500000000157714302243407013300 0ustar00roamroam[tox] envlist = black pep8 mypy pylint unit_tests isolated_build = True [defs] pyfiles = noxfile.py setup.py src/cfg_diag unit_tests [testenv:black] skip_install = True deps = black >= 22, < 23 commands = black --check {[defs]pyfiles} [testenv:black-reformat] skip_install = True deps = black >= 22, < 23 commands = black {[defs]pyfiles} [testenv:pep8] skip_install = True deps = flake8 >= 5, < 6 commands = flake8 {[defs]pyfiles} [testenv:mypy] skip_install = True deps = mypy >= 0.942 nox >= 2022.8.7 pytest >= 7, < 8 types-setuptools commands = mypy {[defs]pyfiles} [testenv:pylint] skip_install = True deps = nox >= 2022.8.7 pylint >= 2.14, < 2.16 pytest >= 7, < 8 commands = pylint {[defs]pyfiles} [testenv:unit_tests] deps = pytest >= 7, < 8 commands = pytest -s -vv unit_tests ././@PaxHeader0000000000000000000000000000003300000000000010211 xustar0027 mtime=1666398373.666863 cfg_diag-0.4.0/unit_tests/0000755000175000017500000000000014324634246014165 5ustar00roamroam././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1616370679.0 cfg_diag-0.4.0/unit_tests/__init__.py0000644000175000017500000000000014025755767016276 0ustar00roamroam././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666353605.0 cfg_diag-0.4.0/unit_tests/test_cfg.py0000644000175000017500000000677514324504705016350 0ustar00roamroam# # Copyright (c) 2021, 2022 StorPool # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # """Test the Config classes.""" import dataclasses import sys from unittest import mock from typing import Any, IO, Optional, Type, Union import pytest import cfg_diag TEST_CLASSES = ( cfg_diag.Config, cfg_diag.ConfigStdOut, cfg_diag.ConfigUnfrozen, cfg_diag.ConfigUnfrozenStdOut, ) ConfigType = Union[Type[cfg_diag.Config], Type[cfg_diag.ConfigUnfrozen]] @pytest.mark.parametrize("cls", TEST_CLASSES) def test_frozen(cls: ConfigType) -> None: """Test some aspect of the Config classes.""" obj = cls(verbose=False) assert not obj.verbose if "Unfrozen" in cls.__name__: obj.verbose = True # type: ignore assert obj.verbose else: with pytest.raises(dataclasses.FrozenInstanceError): obj.verbose = True # type: ignore assert not obj.verbose @pytest.mark.parametrize("cls", TEST_CLASSES) def test_no_output(cls: ConfigType) -> None: """Make sure there is no output with verbose=False.""" res = [] def mock_print(msg: str, file: Optional[IO[Any]] = None) -> None: """Mock the print() builtin function.""" res.append((msg, file)) obj = cls(verbose=False) assert not obj.verbose with mock.patch("builtins.print", new=mock_print): obj.diag_("This is not a diagnostic message.") obj.diag(lambda: "This is not a diagnostic message either.") assert not res @pytest.mark.parametrize("cls", TEST_CLASSES) def test_output(cls: ConfigType) -> None: """Make sure something is output with verbose=True.""" to_stdout = "StdOut" in cls.__name__ res = [] def mock_print(msg: str, file: Optional[IO[Any]] = None) -> None: """Mock the print() builtin function.""" res.append((msg, file)) obj = cls(verbose=True) assert obj.verbose with mock.patch("builtins.print", new=mock_print): obj.diag_("This is a diagnostic message.") obj.diag(lambda: "This is also a diagnostic message.") assert res == [ ( "This is a diagnostic message.", sys.stdout if to_stdout else sys.stderr, ), ( "This is also a diagnostic message.", sys.stdout if to_stdout else sys.stderr, ), ] ././@PaxHeader0000000000000000000000000000002600000000000010213 xustar0022 mtime=1666369508.0 cfg_diag-0.4.0/unit_tests/test_cfg_diag.py0000644000175000017500000000645314324543744017333 0ustar00roamroam# # Copyright (c) 2021, 2022 StorPool # All rights reserved. # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions # are met: # 1. Redistributions of source code must retain the above copyright # notice, this list of conditions and the following disclaimer. # 2. Redistributions in binary form must reproduce the above copyright # notice, this list of conditions and the following disclaimer in the # documentation and/or other materials provided with the distribution. # # THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE # ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS # OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) # HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY # OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF # SUCH DAMAGE. # """Test the ConfigDiag classes.""" import dataclasses import sys from unittest import mock from typing import Any, IO, Optional, Type, Union import pytest import cfg_diag TEST_CLASSES = ( cfg_diag.ConfigDiag, cfg_diag.ConfigDiagStdOut, cfg_diag.ConfigDiagUnfrozen, cfg_diag.ConfigDiagUnfrozenStdOut, ) ConfigDiagType = Union[Type[cfg_diag.ConfigDiag], Type[cfg_diag.ConfigDiagUnfrozen]] @pytest.mark.parametrize("cls", TEST_CLASSES) def test_frozen(cls: ConfigDiagType) -> None: """Test some aspect of the ConfigDiag classes.""" obj = cls(verbose=False) assert not obj.verbose if "Unfrozen" in cls.__name__: obj.verbose = True # type: ignore assert obj.verbose else: with pytest.raises(dataclasses.FrozenInstanceError): obj.verbose = True # type: ignore assert not obj.verbose @pytest.mark.parametrize("cls", TEST_CLASSES) def test_no_output(cls: ConfigDiagType) -> None: """Make sure there is no output with verbose=False.""" res = [] def mock_print(msg: str, file: Optional[IO[Any]] = None) -> None: """Mock the print() builtin function.""" res.append((msg, file)) obj = cls(verbose=False) assert not obj.verbose with mock.patch("builtins.print", new=mock_print): obj.diag("This is not a diagnostic message.") assert not res @pytest.mark.parametrize("cls", TEST_CLASSES) def test_output(cls: ConfigDiagType) -> None: """Make sure something is output with verbose=True.""" to_stdout = "StdOut" in cls.__name__ res = [] def mock_print(msg: str, file: Optional[IO[Any]] = None) -> None: """Mock the print() builtin function.""" res.append((msg, file)) obj = cls(verbose=True) assert obj.verbose with mock.patch("builtins.print", new=mock_print): obj.diag("This is a diagnostic message.") assert res == [ ( "This is a diagnostic message.", sys.stdout if to_stdout else sys.stderr, ), ]