From c1fb29cdd59af5d0b6cbba36132de11efec64200 Mon Sep 17 00:00:00 2001 From: Daniel Mach Date: Mon, 13 May 2024 14:10:51 +0200 Subject: [PATCH] Enable typeguard during build time and tests, fix issues found --- .github/workflows/tests.yaml | 1 + contrib/osc.spec | 10 ++++++++++ osc-wrapper.py | 23 +++++++++++++++++++++++ osc/commandline.py | 28 ++++++++++++++-------------- osc/core.py | 30 +++++++++++++++--------------- osc/meter.py | 2 +- osc/output/output.py | 6 ++++-- osc/util/safewriter.py | 19 ++++++++++++++++++- tests/__init__.py | 14 ++++++++++++++ 9 files changed, 100 insertions(+), 33 deletions(-) diff --git a/.github/workflows/tests.yaml b/.github/workflows/tests.yaml index 5b7a51c638..419f629d11 100644 --- a/.github/workflows/tests.yaml +++ b/.github/workflows/tests.yaml @@ -106,6 +106,7 @@ jobs: sudo sh -c '. /etc/os-release; echo "deb [trusted=yes] http://download.opensuse.org/repositories/openSUSE:Tools/xUbuntu_${VERSION_ID} ./" > /etc/apt/sources.list.d/openSUSE-Tools.list' sudo apt-get -y update sudo apt-get -y --no-install-recommends install python3-behave diffstat diffutils python3 python3-cryptography python3-pip python3-rpm python3-setuptools python3-urllib3 obs-build obs-service-set-version + sudo pip3 install typeguard - name: "Checkout sources" uses: actions/checkout@v3 diff --git a/contrib/osc.spec b/contrib/osc.spec index d26c04b10a..f5b3f3384d 100644 --- a/contrib/osc.spec +++ b/contrib/osc.spec @@ -30,6 +30,13 @@ %bcond_with fdupes %endif +# use typeguard during build on distros where typeguard is available +%if (0%{?suse_version} > 1500 || 0%{?fedora} >= 37) +%bcond_without typeguard +%else +%bcond_with typeguard +%endif + %define argparse_manpage_pkg argparse-manpage %define obs_build_pkg obs-build %define ssh_add_pkg openssh-clients @@ -69,6 +76,9 @@ BuildRequires: %{use_python_pkg}-cryptography BuildRequires: %{use_python_pkg}-devel >= 3.6 BuildRequires: %{use_python_pkg}-rpm BuildRequires: %{use_python_pkg}-setuptools +%if %{with typeguard} +BuildRequires: %{use_python_pkg}-typeguard +%endif BuildRequires: %{use_python_pkg}-urllib3 BuildRequires: diffstat %if %{with fdupes} diff --git a/osc-wrapper.py b/osc-wrapper.py index 952f69cf62..4cc02a26b4 100755 --- a/osc-wrapper.py +++ b/osc-wrapper.py @@ -4,6 +4,29 @@ This wrapper allows osc to be called from the source directory during development. """ + +import os + + +USE_TYPEGUARD = os.environ.get("OSC_TYPEGUARD", "1").lower() in ("1", "true", "on") + +if USE_TYPEGUARD: + try: + from typeguard import install_import_hook + except ImportError: + install_import_hook = None + + if install_import_hook is None: + try: + from typeguard.importhook import install_import_hook + except ImportError: + install_import_hook = None + + if install_import_hook: + # install typeguard import hook only if available + install_import_hook("osc") + + import osc.babysitter osc.babysitter.main() diff --git a/osc/commandline.py b/osc/commandline.py index 09384174d2..d165dd57ab 100644 --- a/osc/commandline.py +++ b/osc/commandline.py @@ -594,10 +594,10 @@ def pop_args( args, arg1_name: str = None, arg1_is_optional: bool = False, - arg1_default: str = None, + arg1_default: Optional[str] = None, arg2_name: str = None, arg2_is_optional: bool = False, - arg2_default: str = None, + arg2_default: Optional[str] = None, ): """ Pop 2 arguments from `args`. @@ -669,9 +669,9 @@ def pop_args( def pop_project_package_from_args( args: List[str], project_is_optional: bool = False, - default_project: str = None, + default_project: Optional[str] = None, package_is_optional: bool = False, - default_package: str = None, + default_package: Optional[str] = None, ): """ Pop project and package from given `args`. @@ -740,9 +740,9 @@ def pop_project_package_from_args( def pop_repository_arch_from_args( args: List[str], repository_is_optional: bool = False, - default_repository: str = None, + default_repository: Optional[str] = None, arch_is_optional: bool = False, - default_arch: str = None, + default_arch: Optional[str] = None, ): """ Pop repository and arch from given `args`. @@ -779,13 +779,13 @@ def pop_repository_arch_from_args( def pop_project_package_repository_arch_from_args( args: List[str], project_is_optional: bool = False, - default_project: str = None, + default_project: Optional[str] = None, package_is_optional: bool = False, - default_package: str = None, + default_package: Optional[str] = None, repository_is_optional: bool = False, - default_repository: str = None, + default_repository: Optional[str] = None, arch_is_optional: bool = False, - default_arch: str = None, + default_arch: Optional[str] = None, ): """ Pop project, package, repository and arch from given `args`. @@ -865,13 +865,13 @@ def pop_project_package_repository_arch_from_args( def pop_project_package_targetproject_targetpackage_from_args( args: List[str], project_is_optional: bool = False, - default_project: str = None, + default_project: Optional[str] = None, package_is_optional: bool = False, - default_package: str = None, + default_package: Optional[str] = None, target_project_is_optional: bool = False, - default_target_project: str = None, + default_target_project: Optional[str] = None, target_package_is_optional: bool = False, - default_target_package: str = None, + default_target_package: Optional[str] = None, ): """ Pop project, package, target project and target package from given `args`. diff --git a/osc/core.py b/osc/core.py index 1a3f5ab3d4..062b393a36 100644 --- a/osc/core.py +++ b/osc/core.py @@ -2319,7 +2319,7 @@ def get_request_collection( package=None, states=None, review_states=None, - types: List[str] = None, + types: Optional[List[str]] = None, ids=None, withfullhistory=False ): @@ -2862,12 +2862,12 @@ def get_source_file_diff(dir, filename, rev, oldfilename=None, olddir=None, orig def server_diff( apiurl: str, - old_project: str, - old_package: str, - old_revision: str, + old_project: Optional[str], + old_package: Optional[str], + old_revision: Optional[str], new_project: str, new_package: str, - new_revision: str, + new_revision: Optional[str], unified=False, missingok=False, meta=False, @@ -2875,7 +2875,7 @@ def server_diff( onlyissues=False, full=True, xml=False, - files: list = None, + files: Optional[list] = None, ): query: Dict[str, Union[str, int]] = {"cmd": "diff"} if expand: @@ -2928,19 +2928,19 @@ def server_diff( def server_diff_noex( apiurl: str, - old_project: str, - old_package: str, - old_revision: str, + old_project: Optional[str], + old_package: Optional[str], + old_revision: Optional[str], new_project: str, new_package: str, - new_revision: str, + new_revision: Optional[str], unified=False, missingok=False, meta=False, expand=True, onlyissues=False, xml=False, - files: list = None, + files: Optional[list] = None, ): try: return server_diff(apiurl, @@ -3087,7 +3087,7 @@ def checkout_package( pathname=None, prj_obj=None, expand_link=False, - prj_dir: Path=None, + prj_dir: Optional[Path] = None, server_service_files=None, service_files=None, progress_obj=None, @@ -3792,7 +3792,7 @@ def copy_pac( return 'Done.' -def lock(apiurl: str, project: str, package: str, msg: str = None): +def lock(apiurl: str, project: str, package: str, msg: Optional[str] = None): url_path = ["source", project] if package: url_path += [package] @@ -5097,7 +5097,7 @@ def owner( return res -def set_link_rev(apiurl: str, project: str, package: str, revision="", expand=False, msg: str=None, vrev: str=None): +def set_link_rev(apiurl: str, project: str, package: str, revision="", expand=False, msg: Optional[str] = None, vrev: Optional[str] = None): url = makeurl(apiurl, ["source", project, package, "_link"]) try: f = http_GET(url) @@ -5118,7 +5118,7 @@ def set_link_rev(apiurl: str, project: str, package: str, revision="", expand=Fa return revision -def _set_link_rev(apiurl: str, project: str, package: str, root, revision="", expand=False, setvrev: str=None): +def _set_link_rev(apiurl: str, project: str, package: str, root, revision="", expand=False, setvrev: Optional[str] = None): """ Updates the rev attribute of the _link xml. If revision is set to None the rev and vrev attributes are removed from the _link xml. diff --git a/osc/meter.py b/osc/meter.py index b9d1b7669a..1683d3b428 100644 --- a/osc/meter.py +++ b/osc/meter.py @@ -60,7 +60,7 @@ def end(self): self.bar.finish() -class NoPBTextMeter: +class NoPBTextMeter(TextMeterBase): def start(self, basename: str, size: Optional[int] = None): pass diff --git a/osc/output/output.py b/osc/output/output.py index 50aa98b598..75b5f80165 100644 --- a/osc/output/output.py +++ b/osc/output/output.py @@ -5,7 +5,9 @@ import subprocess import sys import tempfile +from typing import BinaryIO from typing import Dict +from typing import Generator from typing import List from typing import Optional from typing import TextIO @@ -137,7 +139,7 @@ def safe_print(*args, **kwargs): print(*args, **kwargs) -def safe_write(file: TextIO, text: Union[str, bytes], *, add_newline: bool = False): +def safe_write(file: Union[BinaryIO, TextIO], text: Union[str, bytes], *, add_newline: bool = False): """ Run sanitize_text() on ``text`` and write it to ``file``. @@ -211,7 +213,7 @@ def run_pager(message: Union[bytes, str], tmp_suffix: str = ""): run_external(*cmd, env=env) -def pipe_to_pager(lines: Union[List[bytes], List[str]], *, add_newlines=False): +def pipe_to_pager(lines: Union[List[bytes], List[str], Generator[bytes, None, None], Generator[str, None, None]], *, add_newlines=False): """ Pipe ``lines`` to the pager. If running in a non-interactive terminal, print the data instead. diff --git a/osc/util/safewriter.py b/osc/util/safewriter.py index 817948c0e5..75ef5cf9e4 100644 --- a/osc/util/safewriter.py +++ b/osc/util/safewriter.py @@ -1,6 +1,8 @@ +import io + # be careful when debugging this code: # don't add print statements when setting sys.stdout = SafeWriter(sys.stdout)... -class SafeWriter: +class SafeWriter(io.TextIOBase): """ Safely write an (unicode) str. In case of an "UnicodeEncodeError" the the str is encoded with the "encoding" encoding. @@ -8,15 +10,30 @@ class SafeWriter: """ def __init__(self, writer, encoding='unicode_escape'): + super().__init__() self._writer = writer self._encoding = encoding + # TextIOBase requires overriding the following stub methods: detach, read, readline, and write + + def detach(self, *args, **kwargs): + return self._writer.detach(*args, **kwargs) + + def read(self, *args, **kwargs): + return self._writer.read(args, **kwargs) + + def readline(self, *args, **kwargs): + return self._writer.readline(args, **kwargs) + def write(self, s): try: self._writer.write(s) except UnicodeEncodeError as e: self._writer.write(s.encode(self._encoding)) + def fileno(self, *args, **kwargs): + return self._writer.fileno(*args, **kwargs) + def __getattr__(self, name): return getattr(self._writer, name) diff --git a/tests/__init__.py b/tests/__init__.py index e69de29bb2..2740d5773f 100644 --- a/tests/__init__.py +++ b/tests/__init__.py @@ -0,0 +1,14 @@ +try: + from typeguard import install_import_hook +except ImportError: + install_import_hook = None + +if not install_import_hook: + try: + from typeguard.importhook import install_import_hook + except ImportError: + install_import_hook = None + +if install_import_hook: + # install typeguard import hook only if available + install_import_hook("osc")