From 008d43b35e86f4c0e52fd195ea873598e6e398ba Mon Sep 17 00:00:00 2001 From: JensDiemer Date: Fri, 1 Dec 2023 21:50:56 +0100 Subject: [PATCH] Apply https://github.com/jedie/cookiecutter_templates updates --- .gitignore | 1 + README.md | 3 +- manageprojects/cli/cli_app.py | 9 ++ manageprojects/cli/dev.py | 142 +++++++++++-------------------- manageprojects/tests/__init__.py | 32 +++++-- pyproject.toml | 4 +- 6 files changed, 89 insertions(+), 102 deletions(-) diff --git a/.gitignore b/.gitignore index 202bedb..94a4547 100644 --- a/.gitignore +++ b/.gitignore @@ -3,6 +3,7 @@ __pycache__ /dist/ /coverage.* +*.orig !.github !.editorconfig diff --git a/README.md b/README.md index b725438..c8a0729 100644 --- a/README.md +++ b/README.md @@ -299,7 +299,7 @@ Usage: ./dev-cli.py [OPTIONS] COMMAND [ARGS]... ╰──────────────────────────────────────────────────────────────────────────────────────────────────╯ ╭─ Commands ───────────────────────────────────────────────────────────────────────────────────────╮ │ check-code-style Check code style by calling darker + flake8 │ -│ coverage Run and show coverage. │ +│ coverage Run tests and show coverage report. │ │ fix-code-style Fix code style of all manageprojects source code files via darker │ │ install Run pip-sync and install 'manageprojects' via pip as editable. │ │ mypy Run Mypy (configured in pyproject.toml) │ @@ -322,6 +322,7 @@ See also git tags: https://github.com/jedie/manageprojects/tags [comment]: <> (✂✂✂ auto generated history start ✂✂✂) * [**dev**](https://github.com/jedie/manageprojects/compare/v0.15.4...main) + * 2023-12-01 - Apply https://github.com/jedie/cookiecutter_templates updates * 2023-12-01 - Use: cli_base.cli_tools.test_utils.logs.AssertLogs * [v0.15.4](https://github.com/jedie/manageprojects/compare/v0.15.3...v0.15.4) * 2023-11-27 - Use "flake8-bugbear", too. diff --git a/manageprojects/cli/cli_app.py b/manageprojects/cli/cli_app.py index 6525823..5311ded 100644 --- a/manageprojects/cli/cli_app.py +++ b/manageprojects/cli/cli_app.py @@ -14,6 +14,8 @@ from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE from cli_base.cli_tools.version_info import print_version from rich import print # noqa +from rich.console import Console +from rich.traceback import install as rich_traceback_install from rich_click import RichGroup import manageprojects @@ -422,6 +424,13 @@ def version(): def main(): print_version(manageprojects) + console = Console() + rich_traceback_install( + width=console.size.width, # full terminal width + show_locals=True, + suppress=[click], + max_frames=2, + ) # Execute Click CLI: cli.name = './cli.py' diff --git a/manageprojects/cli/dev.py b/manageprojects/cli/dev.py index 92562a1..7d54876 100644 --- a/manageprojects/cli/dev.py +++ b/manageprojects/cli/dev.py @@ -7,9 +7,13 @@ import rich_click as click from bx_py_utils.path import assert_is_file +from cli_base.cli_tools.dev_tools import run_coverage, run_tox, run_unittest_cli from cli_base.cli_tools.subprocess_utils import verbose_check_call +from cli_base.cli_tools.test_utils.snapshot import UpdateTestSnapshotFiles +from cli_base.cli_tools.verbosity import OPTION_KWARGS_VERBOSE from cli_base.cli_tools.version_info import print_version -from rich import print # noqa; noqa +from rich.console import Console +from rich.traceback import install as rich_traceback_install from rich_click import RichGroup import manageprojects @@ -59,31 +63,15 @@ def cli(): @click.command() -@click.option('--verbose/--no-verbose', **OPTION_ARGS_DEFAULT_FALSE) -def mypy(verbose: bool = True): +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def mypy(verbosity: int): """Run Mypy (configured in pyproject.toml)""" - verbose_check_call('mypy', '.', cwd=PACKAGE_ROOT, verbose=verbose, exit_on_error=True) + verbose_check_call('mypy', '.', cwd=PACKAGE_ROOT, verbose=verbosity > 0, exit_on_error=True) cli.add_command(mypy) -@click.command() -@click.option('--verbose/--no-verbose', **OPTION_ARGS_DEFAULT_FALSE) -def coverage(verbose: bool = True): - """ - Run and show coverage. - """ - verbose_check_call('coverage', 'run', verbose=verbose, exit_on_error=True) - verbose_check_call('coverage', 'combine', '--append', verbose=verbose, exit_on_error=True) - verbose_check_call('coverage', 'report', '--fail-under=30', verbose=verbose, exit_on_error=True) - verbose_check_call('coverage', 'xml', verbose=verbose, exit_on_error=True) - verbose_check_call('coverage', 'json', verbose=verbose, exit_on_error=True) - - -cli.add_command(coverage) - - @click.command() def install(): """ @@ -163,7 +151,7 @@ def publish(): """ Build and upload this project to PyPi """ - _run_unittest_cli(verbose=False, exit_after_run=False) # Don't publish a broken state + run_unittest_cli(verbose=False, exit_after_run=False) # Don't publish a broken state publish_package( module=manageprojects, @@ -177,12 +165,12 @@ def publish(): @click.command() @click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE) -@click.option('--verbose/--no-verbose', **OPTION_ARGS_DEFAULT_FALSE) -def fix_code_style(color: bool = True, verbose: bool = False): +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def fix_code_style(color: bool, verbosity: int): """ Fix code style of all manageprojects source code files via darker """ - code_style.fix(package_root=PACKAGE_ROOT, color=color, verbose=verbose) + code_style.fix(package_root=PACKAGE_ROOT, color=color, verbose=verbosity > 0) cli.add_command(fix_code_style) @@ -190,95 +178,58 @@ def fix_code_style(color: bool = True, verbose: bool = False): @click.command() @click.option('--color/--no-color', **OPTION_ARGS_DEFAULT_TRUE) -@click.option('--verbose/--no-verbose', **OPTION_ARGS_DEFAULT_FALSE) -def check_code_style(color: bool = True, verbose: bool = False): +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def check_code_style(color: bool, verbosity: int): """ Check code style by calling darker + flake8 """ - code_style.check(package_root=PACKAGE_ROOT, color=color, verbose=verbose) + code_style.check(package_root=PACKAGE_ROOT, color=color, verbose=verbosity > 0) cli.add_command(check_code_style) @click.command() -def update_test_snapshot_files(): +@click.option('-v', '--verbosity', **OPTION_KWARGS_VERBOSE) +def update_test_snapshot_files(verbosity: int): """ Update all test snapshot files (by remove and recreate all snapshot files) """ - def iter_snapshot_files(): - yield from PACKAGE_ROOT.rglob('*.snapshot.*') - - removed_file_count = 0 - for item in iter_snapshot_files(): - item.unlink() - removed_file_count += 1 - print(f'{removed_file_count} test snapshot files removed... run tests...') - - # Just recreate them by running tests: - _run_unittest_cli( - extra_env=dict( - RAISE_SNAPSHOT_ERRORS='0', # Recreate snapshot files without error - ), - verbose=False, - exit_after_run=False, - ) - - new_files = len(list(iter_snapshot_files())) - print(f'{new_files} test snapshot files created, ok.\n') + with UpdateTestSnapshotFiles(root_path=PACKAGE_ROOT, verbose=verbosity > 0): + # Just recreate them by running tests: + run_unittest_cli( + extra_env=dict( + RAISE_SNAPSHOT_ERRORS='0', # Recreate snapshot files without error + ), + verbose=verbosity > 1, + exit_after_run=False, + ) cli.add_command(update_test_snapshot_files) -def _run_unittest_cli(extra_env=None, verbose=True, exit_after_run=True): +@click.command() # Dummy command +def test(): """ - Call the origin unittest CLI and pass all args to it. + Run unittests """ - if extra_env is None: - extra_env = dict() - - extra_env.update( - dict( - PYTHONUNBUFFERED='1', - PYTHONWARNINGS='always', - ) - ) + run_unittest_cli() - args = sys.argv[2:] - if not args: - if verbose: - args = ('--verbose', '--locals', '--buffer') - else: - args = ('--locals', '--buffer') - verbose_check_call( - sys.executable, - '-m', - 'unittest', - *args, - timeout=15 * 60, - extra_env=extra_env, - ) - if exit_after_run: - sys.exit(0) +cli.add_command(test) @click.command() # Dummy command -def test(): +def coverage(): """ - Run unittests + Run tests and show coverage report. """ - _run_unittest_cli() + run_coverage() -cli.add_command(test) - - -def _run_tox(): - verbose_check_call(sys.executable, '-m', 'tox', *sys.argv[2:]) - sys.exit(0) +cli.add_command(coverage) @click.command() # Dummy "tox" command @@ -286,7 +237,7 @@ def tox(): """ Run tox """ - _run_tox() + run_tox() cli.add_command(tox) @@ -305,13 +256,24 @@ def version(): def main(): print_version(manageprojects) + console = Console() + rich_traceback_install( + width=console.size.width, # full terminal width + show_locals=True, + suppress=[click], + max_frames=2, + ) + if len(sys.argv) >= 2: - # Check if we just pass a command call + # Check if we can just pass a command call to origin CLI: command = sys.argv[1] - if command == 'test': - _run_unittest_cli() - elif command == 'tox': - _run_tox() + command_map = { + 'test': run_unittest_cli, + 'tox': run_tox, + 'coverage': run_coverage, + } + if real_func := command_map.get(command): + real_func(argv=sys.argv, exit_after_run=True) # Execute Click CLI: cli() diff --git a/manageprojects/tests/__init__.py b/manageprojects/tests/__init__.py index b7e2b1a..e6ca7ca 100644 --- a/manageprojects/tests/__init__.py +++ b/manageprojects/tests/__init__.py @@ -1,15 +1,31 @@ import os import unittest.util +from pathlib import Path -from manageprojects.utilities.log_utils import log_config +from bx_py_utils.test_utils.deny_requests import deny_any_real_request +from cli_base.cli_tools.verbosity import MAX_LOG_LEVEL, setup_logging +from rich import print # noqa -# Hacky way to expand the failed test output: -unittest.util._MAX_LENGTH = os.environ.get('UNITTEST_MAX_LENGTH', 300) +def pre_configure_tests() -> None: + print(f'Configure unittests via "load_tests Protocol" from {Path(__file__).relative_to(Path.cwd())}') + # Hacky way to display more "assert"-Context in failing tests: + _MIN_MAX_DIFF = unittest.util._MAX_LENGTH - unittest.util._MIN_DIFF_LEN + unittest.util._MAX_LENGTH = int(os.environ.get('UNITTEST_MAX_LENGTH', 300)) + unittest.util._MIN_DIFF_LEN = unittest.util._MAX_LENGTH - _MIN_MAX_DIFF -log_config( - format='%(levelname)s %(name)s.%(funcName)s %(lineno)d | %(message)s', - log_in_file=False, - raise_log_output=True, -) + # Deny any request via docket/urllib3 because tests they should mock all requests: + deny_any_real_request() + + # Display DEBUG logs in tests: + setup_logging(verbosity=MAX_LOG_LEVEL) + + +def load_tests(loader, tests, pattern): + """ + Use unittest "load_tests Protocol" as a hook to setup test environment before running tests. + https://docs.python.org/3/library/unittest.html#load-tests-protocol + """ + pre_configure_tests() + return loader.discover(start_dir=Path(__file__).parent, pattern=pattern) diff --git a/pyproject.toml b/pyproject.toml index e53f76a..31c8c8a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -131,9 +131,6 @@ commands_pre = pip-sync requirements.dev.txt commands = {envpython} -m coverage run --context='{envname}' - {envpython} -m coverage combine --append - {envpython} -m coverage xml - {envpython} -m coverage report """ @@ -165,6 +162,7 @@ applied_migrations = [ "8d0ebe1", # 2023-08-17T18:15:10+02:00 "be3f649", # 2023-08-22T19:36:57+02:00 "385f654", # 2023-10-08T21:09:24+02:00 + "d1ed4b1", # 2023-12-01T21:41:29+01:00 ] [manageprojects.cookiecutter_context.cookiecutter]