diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 248a7d9..4f79752 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -13,7 +13,7 @@ repos: - id: check-yaml files: .*\.(yaml|yml)$ - repo: https://github.com/astral-sh/ruff-pre-commit - rev: v0.14.7 + rev: v0.14.8 hooks: - id: ruff-check args: ['--fix', '--unsafe-fixes'] diff --git a/oslo_upgradecheck/_i18n.py b/oslo_upgradecheck/_i18n.py index 9404346..be5e04b 100644 --- a/oslo_upgradecheck/_i18n.py +++ b/oslo_upgradecheck/_i18n.py @@ -30,5 +30,5 @@ _C = _translators.contextual_form _P = _translators.plural_form -def get_available_languages(): +def get_available_languages() -> list[str]: return oslo_i18n.get_available_languages(DOMAIN) diff --git a/oslo_upgradecheck/common_checks.py b/oslo_upgradecheck/common_checks.py index 1fb2fdd..3bd92dc 100644 --- a/oslo_upgradecheck/common_checks.py +++ b/oslo_upgradecheck/common_checks.py @@ -10,7 +10,8 @@ # License for the specific language governing permissions and limitations # under the License. -from oslo_policy import opts as policy_opts +from oslo_config import cfg # type: ignore +from oslo_policy import opts as policy_opts # type: ignore from oslo_utils import fileutils from oslo_upgradecheck import upgradecheck @@ -20,7 +21,9 @@ Common checks which can be used by multiple services. """ -def check_policy_json(self, conf): +def check_policy_json( + self: upgradecheck.UpgradeCommands, conf: cfg.ConfigOpts +) -> upgradecheck.Result: "Checks to see if policy file is JSON-formatted policy file." # NOTE(gmann): This method need [oslo_policy].policy_file diff --git a/oslo_upgradecheck/tests/test_common_checks.py b/oslo_upgradecheck/tests/test_common_checks.py index 8795fbb..04bc82f 100644 --- a/oslo_upgradecheck/tests/test_common_checks.py +++ b/oslo_upgradecheck/tests/test_common_checks.py @@ -15,11 +15,11 @@ import os.path import tempfile import yaml -from oslo_config import cfg +from oslo_config import cfg # type: ignore from oslo_config import fixture as config -from oslo_policy import opts as policy_opts -from oslo_serialization import jsonutils -from oslotest import base +from oslo_policy import opts as policy_opts # type: ignore +from oslo_serialization import jsonutils # type: ignore +from oslotest import base # type: ignore from oslo_upgradecheck import common_checks from oslo_upgradecheck import upgradecheck diff --git a/oslo_upgradecheck/tests/test_upgradecheck.py b/oslo_upgradecheck/tests/test_upgradecheck.py index 784e366..cafdff3 100644 --- a/oslo_upgradecheck/tests/test_upgradecheck.py +++ b/oslo_upgradecheck/tests/test_upgradecheck.py @@ -22,8 +22,8 @@ import subprocess import sys from unittest import mock -from oslo_config import cfg -from oslotest import base +from oslo_config import cfg # type: ignore +from oslotest import base # type: ignore from oslo_upgradecheck import upgradecheck @@ -54,8 +54,13 @@ class TestCommands(upgradecheck.UpgradeCommands): ) -class SuccessCommands(TestCommands): - _upgrade_checks = () +class SuccessCommands(upgradecheck.UpgradeCommands): + def success(self): + return upgradecheck.Result( + upgradecheck.Code.SUCCESS, 'Always succeeds' + ) + + _upgrade_checks = (('always succeeds', success),) class TestUpgradeCommands(base.BaseTestCase): diff --git a/oslo_upgradecheck/upgradecheck.py b/oslo_upgradecheck/upgradecheck.py index 68f8a1b..3ecb2ca 100644 --- a/oslo_upgradecheck/upgradecheck.py +++ b/oslo_upgradecheck/upgradecheck.py @@ -13,18 +13,20 @@ # License for the specific language governing permissions and limitations # under the License. +from collections.abc import Callable, Iterable import json import sys import textwrap import traceback +from typing import Any, TypedDict import enum -from oslo_config import cfg +from oslo_config import cfg # type: ignore import prettytable from oslo_upgradecheck._i18n import _ -CONF = None +CONF: cfg.ConfigOpts | None = None class Code(enum.IntEnum): @@ -59,12 +61,23 @@ class Result: information on what issue was discovered along with any remediation. """ - def __init__(self, code, details=None): + def __init__(self, code: Code, details: str | None = None) -> None: super().__init__() self.code = code self.details = details +class _OutputCheck(TypedDict): + check: str + result: Code + details: str | None + + +class _Output(TypedDict): + name: str + checks: list[_OutputCheck] + + class UpgradeCommands: """Base class for upgrade checks @@ -79,18 +92,27 @@ class UpgradeCommands: """ display_title = _('Upgrade Check Results') - _upgrade_checks = () + _upgrade_checks: tuple[ + tuple[ + str, + Callable[..., Result] + | tuple[Callable[..., Result], dict[str, Any]], + ], + ..., + ] = () - def _get_details(self, upgrade_check_result): - if upgrade_check_result.details is not None: - # wrap the text on the details to 60 characters - return '\n'.join( - textwrap.wrap( - upgrade_check_result.details, 60, subsequent_indent=' ' - ) + def _get_details(self, upgrade_check_result: Result) -> str | None: + if upgrade_check_result.details is None: + return None + + # wrap the text on the details to 60 characters + return '\n'.join( + textwrap.wrap( + upgrade_check_result.details, 60, subsequent_indent=' ' ) + ) - def check(self): + def check(self) -> Code: """Performs checks to see if the deployment is ready for upgrade. These checks are expected to be run BEFORE services are restarted with @@ -130,14 +152,15 @@ class UpgradeCommands: # Since registering opts can be overridden by consuming code, we can't # assume that our locally defined option exists. if ( - hasattr(CONF, 'command') + CONF is not None + and hasattr(CONF, 'command') and hasattr(CONF.command, 'json') and CONF.command.json ): # NOTE(bnemec): We use str on the translated string to # force immediate translation if lazy translation is in use. # See lp1801761 for details. - output = {'name': str(self.display_title), 'checks': []} + output: _Output = {'name': str(self.display_title), 'checks': []} for name, result in check_results: output['checks'].append( { @@ -169,7 +192,9 @@ class UpgradeCommands: return return_code -def register_cli_options(conf, upgrade_command): +def register_cli_options( + conf: cfg.ConfigOpts, upgrade_command: UpgradeCommands +) -> None: """Set up the command line options. Adds a subcommand to support 'upgrade check' on the command line. @@ -179,7 +204,7 @@ def register_cli_options(conf, upgrade_command): :param upgrade_command: The UpgradeCommands instance. """ - def add_parsers(subparsers): + def add_parsers(subparsers: Any) -> None: upgrade_action = subparsers.add_parser('upgrade') upgrade_action.add_argument('check') upgrade_action.set_defaults(action_fn=upgrade_command.check) @@ -194,14 +219,14 @@ def register_cli_options(conf, upgrade_command): conf.register_cli_opt(opt) -def run(conf): +def run(conf: cfg.ConfigOpts) -> int: """Run the requested command. :param conf: An oslo.confg ConfigOpts instance on which the upgrade commands have been previously registered. """ try: - return conf.command.action_fn() + return conf.command.action_fn() # type: ignore except Exception: print(_('Error:\n%s') % traceback.format_exc()) # This is 255 so it's not confused with the upgrade check exit codes. @@ -209,12 +234,12 @@ def run(conf): def main( - conf, - project, - upgrade_command, - argv=sys.argv[1:], - default_config_files=None, -): + conf: cfg.ConfigOpts, + project: str, + upgrade_command: UpgradeCommands, + argv: list[str] = sys.argv[1:], + default_config_files: Iterable[str] | None = None, +) -> int: """Simple implementation of main for upgrade checks This can be used in upgrade check commands to provide the minimum diff --git a/pyproject.toml b/pyproject.toml index a70f925..cb81f61 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -35,6 +35,20 @@ packages = [ "oslo_upgradecheck" ] +[tool.mypy] +python_version = "3.10" +show_column_numbers = true +show_error_context = true +strict = true +exclude = '(?x)(doc | examples | releasenotes)' + +[[tool.mypy.overrides]] +module = ["oslo_upgradecheck.tests.*"] +disallow_untyped_calls = false +disallow_untyped_defs = false +disallow_subclassing_any = false +disallow_any_generics = false + [tool.ruff] line-length = 79 diff --git a/tox.ini b/tox.ini index 87fdfad..f89d108 100644 --- a/tox.ini +++ b/tox.ini @@ -15,11 +15,25 @@ deps = commands = stestr run --slowest {posargs} [testenv:pep8] +description = + Run style checks. skip_install = true deps = pre-commit + {[testenv:mypy]deps} commands = pre-commit run -a + {[testenv:mypy]commands} + +[testenv:mypy] +description = + Run type checks. +deps = + {[testenv]deps} + mypy + types-PyYAML +commands = + mypy --cache-dir="{envdir}/mypy_cache" {posargs:oslo_upgradecheck} [testenv:venv] commands = {posargs}