Skip to content

Commit 5deb83a

Browse files
committed
Enable typeguard during build time and tests, fix issues found
It is also possible to use typeguard in osc-wrapper.py by setting OSC_TYPEGUARD=1.
1 parent 87e8168 commit 5deb83a

11 files changed

Lines changed: 116 additions & 47 deletions

File tree

.github/workflows/tests.yaml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -151,6 +151,7 @@ jobs:
151151
sudo apt-get -y --no-install-recommends install libwww-perl
152152
# obs-scm-bridge is not available as a package at the moment, install it from github
153153
sudo pip3 config set global.break-system-packages 1
154+
sudo pip3 install typeguard
154155
sudo pip3 install git+https://github.com/openSUSE/obs-scm-bridge
155156
sudo chmod a+x /usr/local/lib/*/*/obs_scm_bridge
156157
sudo mkdir -p /usr/lib/obs/service
@@ -173,4 +174,4 @@ jobs:
173174
- name: "Run tests"
174175
run: |
175176
cd behave
176-
behave -Dosc=../osc-wrapper.py -Dgit-obs=../git-obs.py -Dgit-osc-precommit-hook=../git-osc-precommit-hook.py -Dpodman_max_containers=2
177+
OSC_TYPEGUARD=1 behave -Dosc=../osc-wrapper.py -Dgit-obs=../git-obs.py -Dgit-osc-precommit-hook=../git-osc-precommit-hook.py -Dpodman_max_containers=2

contrib/osc.spec

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,13 @@
3535
%bcond_with fdupes
3636
%endif
3737

38+
# use typeguard during build on distros where typeguard is available
39+
%if (0%{?suse_version} > 1500 || 0%{?fedora} >= 37)
40+
%bcond_without typeguard
41+
%else
42+
%bcond_with typeguard
43+
%endif
44+
3845
# the macro exists only on openSUSE based distros
3946
%if %{undefined python3_fix_shebang}
4047
%define python3_fix_shebang %nil
@@ -87,6 +94,9 @@ BuildRequires: %{use_python_pkg}-cryptography
8794
BuildRequires: %{use_python_pkg}-devel >= 3.6
8895
BuildRequires: %{use_python_pkg}-rpm
8996
BuildRequires: %{use_python_pkg}-setuptools
97+
%if %{with typeguard}
98+
BuildRequires: %{use_python_pkg}-typeguard
99+
%endif
90100
BuildRequires: %{use_python_pkg}-urllib3
91101
BuildRequires: %{yaml_pkg}
92102
BuildRequires: diffstat

osc-wrapper.py

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,29 @@
44
This wrapper allows osc to be called from the source directory during development.
55
"""
66

7+
8+
import os
9+
10+
11+
USE_TYPEGUARD = os.environ.get("OSC_TYPEGUARD", "1").lower() in ("1", "true", "on")
12+
13+
if USE_TYPEGUARD:
14+
try:
15+
from typeguard import install_import_hook
16+
except ImportError:
17+
install_import_hook = None
18+
19+
if install_import_hook is None:
20+
try:
21+
from typeguard.importhook import install_import_hook
22+
except ImportError:
23+
install_import_hook = None
24+
25+
if install_import_hook:
26+
# install typeguard import hook only if available
27+
install_import_hook("osc")
28+
29+
730
import osc.babysitter
831

932
osc.babysitter.main()

osc/commandline.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4990,6 +4990,8 @@ def do_rdiff(self, subcmd, opts, *args):
49904990
rev2 = -rev - 1
49914991
else:
49924992
return
4993+
rev1 = str(rev1)
4994+
rev2 = str(rev2)
49934995
except:
49944996
print(f'Revision \'{opts.change}\' not an integer', file=sys.stderr)
49954997
return

osc/core.py

Lines changed: 41 additions & 40 deletions
Original file line numberDiff line numberDiff line change
@@ -1335,7 +1335,7 @@ def show_package_trigger_reason(apiurl: str, prj: str, pac: str, repo: str, arch
13351335
raise
13361336

13371337

1338-
def show_package_meta(apiurl: str, prj: str, pac: str, meta=False, blame=None):
1338+
def show_package_meta(apiurl: str, prj: str, pac: str, meta=False, blame=None) -> List[bytes]:
13391339
query: Dict[str, Union[str, int]] = {}
13401340
if meta:
13411341
query['meta'] = 1
@@ -2944,12 +2944,12 @@ def get_source_file_diff(dir, filename, rev, oldfilename=None, olddir=None, orig
29442944

29452945
def server_diff(
29462946
apiurl: str,
2947-
old_project: str,
2948-
old_package: str,
2949-
old_revision: str,
2947+
old_project: Optional[str],
2948+
old_package: Optional[str],
2949+
old_revision: Optional[str],
29502950
new_project: str,
29512951
new_package: str,
2952-
new_revision: str,
2952+
new_revision: Optional[str],
29532953
unified: bool = False,
29542954
missingok: bool = False,
29552955
meta: bool = False,
@@ -3016,12 +3016,12 @@ def server_diff(
30163016

30173017
def server_diff_noex(
30183018
apiurl: str,
3019-
old_project: str,
3020-
old_package: str,
3021-
old_revision: str,
3019+
old_project: Optional[str],
3020+
old_package: Optional[str],
3021+
old_revision: Optional[str],
30223022
new_project: str,
30233023
new_package: str,
3024-
new_revision: str,
3024+
new_revision: Optional[str],
30253025
unified=False,
30263026
missingok=False,
30273027
meta=False,
@@ -3319,9 +3319,9 @@ def checkout_package(
33193319

33203320

33213321
def replace_pkg_meta(
3322-
pkgmeta, new_name: str, new_prj: str, keep_maintainers=False, dst_userid=None, keep_develproject=False,
3322+
pkgmeta: List[bytes], new_name: str, new_prj: str, keep_maintainers=False, dst_userid=None, keep_develproject=False,
33233323
keep_lock: bool = False, keep_scmsync: bool = True,
3324-
):
3324+
) -> str:
33253325
"""
33263326
update pkgmeta with new new_name and new_prj and set calling user as the
33273327
only maintainer (unless keep_maintainers is set). Additionally remove the
@@ -3564,7 +3564,7 @@ def aggregate_pac(
35643564

35653565
if meta_change:
35663566
src_meta = show_package_meta(apiurl, src_project, src_package_meta)
3567-
dst_meta = replace_pkg_meta(src_meta, dst_package_meta, dst_project)
3567+
dst_meta = replace_pkg_meta(src_meta, dst_package_meta, dst_project).split("\n")
35683568
meta_change = True
35693569

35703570
if disable_publish:
@@ -4855,25 +4855,26 @@ def get_commitlog(
48554855
# revision is srcmd5
48564856
revision_list = [i for i in revision_list if i.srcmd5 == revision]
48574857
else:
4858-
revision = int(revision)
4858+
assert revision is not None
4859+
revision_int = int(revision)
48594860
if revision_is_empty(revision_upper):
4860-
revision_list = [i for i in revision_list if i.rev == revision]
4861+
revision_list = [i for i in revision_list if i.rev == revision_int]
48614862
else:
4862-
revision_upper = int(revision_upper)
4863-
revision_list = [i for i in revision_list if i.rev <= revision_upper and i.rev >= revision]
4863+
revision_upper_int = int(revision_upper)
4864+
revision_list = [i for i in revision_list if i.rev <= revision_upper_int and i.rev >= revision_int]
48644865

48654866
if format == "csv":
48664867
f = io.StringIO()
48674868
writer = csv.writer(f, dialect="unix")
4868-
for revision in reversed(revision_list):
4869+
for i in reversed(revision_list):
48694870
writer.writerow(
48704871
(
4871-
revision.rev,
4872-
revision.user,
4873-
revision.get_time_str(),
4874-
revision.srcmd5,
4875-
revision.comment,
4876-
revision.requestid,
4872+
i.rev,
4873+
i.user,
4874+
i.get_time_str(),
4875+
i.srcmd5,
4876+
i.comment,
4877+
i.requestid,
48774878
)
48784879
)
48794880
f.seek(0)
@@ -4882,42 +4883,42 @@ def get_commitlog(
48824883

48834884
if format == "xml":
48844885
root = ET.Element("log")
4885-
for revision in reversed(revision_list):
4886+
for i in reversed(revision_list):
48864887
entry = ET.SubElement(root, "logentry")
4887-
entry.attrib["revision"] = str(revision.rev)
4888-
entry.attrib["srcmd5"] = revision.srcmd5
4889-
ET.SubElement(entry, "author").text = revision.user
4890-
ET.SubElement(entry, "date").text = revision.get_time_str()
4891-
ET.SubElement(entry, "requestid").text = str(revision.requestid) if revision.requestid else ""
4892-
ET.SubElement(entry, "msg").text = revision.comment or ""
4888+
entry.attrib["revision"] = str(i.rev)
4889+
entry.attrib["srcmd5"] = i.srcmd5
4890+
ET.SubElement(entry, "author").text = i.user
4891+
ET.SubElement(entry, "date").text = i.get_time_str()
4892+
ET.SubElement(entry, "requestid").text = str(i.requestid) if i.requestid else ""
4893+
ET.SubElement(entry, "msg").text = i.comment or ""
48934894
xmlindent(root)
48944895
yield from ET.tostring(root, encoding="utf-8").decode("utf-8").splitlines()
48954896
return
48964897

48974898
if format == "text":
4898-
for revision in reversed(revision_list):
4899+
for i in reversed(revision_list):
48994900
entry = (
4900-
f"r{revision.rev}",
4901-
revision.user,
4902-
revision.get_time_str(),
4903-
revision.srcmd5,
4904-
revision.version,
4905-
f"rq{revision.requestid}" if revision.requestid else ""
4901+
f"r{i.rev}",
4902+
i.user,
4903+
i.get_time_str(),
4904+
i.srcmd5,
4905+
i.version,
4906+
f"rq{i.requestid}" if i.requestid else ""
49064907
)
49074908
yield 76 * "-"
49084909
yield " | ".join(entry)
49094910
yield ""
4910-
yield revision.comment or "<no message>"
4911+
yield i.comment or "<no message>"
49114912
yield ""
49124913
if patch:
49134914
rdiff = server_diff(
49144915
apiurl,
49154916
prj,
49164917
package,
4917-
revision.rev - 1,
4918+
str(i.rev - 1),
49184919
prj,
49194920
package,
4920-
revision.rev,
4921+
str(i.rev),
49214922
meta=meta,
49224923
)
49234924
yield highlight_diff(rdiff).decode("utf-8", errors="replace")

osc/meter.py

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -102,7 +102,6 @@ def create_text_meter(*args, **kwargs) -> TextMeterBase:
102102

103103
use_pb_fallback = kwargs.pop("use_pb_fallback", False)
104104

105-
meter_class: TextMeterBase
106105
if config.quiet:
107106
meter_class = NoTextMeter
108107
elif not have_pb_module or not config.show_download_progress or not sys.stdout.isatty() or use_pb_fallback:

osc/output/output.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,9 @@
55
import subprocess
66
import sys
77
import tempfile
8+
from typing import BinaryIO
89
from typing import Dict
10+
from typing import Generator
911
from typing import List
1012
from typing import Optional
1113
from typing import TextIO
@@ -137,7 +139,7 @@ def safe_print(*args, **kwargs):
137139
print(*args, **kwargs)
138140

139141

140-
def safe_write(file: TextIO, text: Union[str, bytes], *, add_newline: bool = False):
142+
def safe_write(file: Union[BinaryIO, TextIO], text: Union[str, bytes], *, add_newline: bool = False):
141143
"""
142144
Run sanitize_text() on ``text`` and write it to ``file``.
143145
@@ -211,7 +213,7 @@ def run_pager(message: Union[bytes, str], tmp_suffix: str = ""):
211213
run_external(*cmd, env=env)
212214

213215

214-
def pipe_to_pager(lines: Union[List[bytes], List[str]], *, add_newlines=False):
216+
def pipe_to_pager(lines: Union[List[bytes], List[str], Generator[bytes, None, None], Generator[str, None, None]], *, add_newlines=False):
215217
"""
216218
Pipe ``lines`` to the pager.
217219
If running in a non-interactive terminal, print the data instead.

osc/util/ar.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,7 +37,7 @@ def __str__(self):
3737
class ArHdr:
3838
"""Represents an ar header entry"""
3939

40-
def __init__(self, fn: bytes, date: bytes, uid: bytes, gid: bytes, mode: bytes, size: bytes, fmag: bytes, off: bytes):
40+
def __init__(self, fn: bytes, date: bytes, uid: bytes, gid: bytes, mode: bytes, size: bytes, fmag: bytes, off: int):
4141
self.file = fn.strip()
4242
self.date = date.strip()
4343
self.uid = uid.strip()

osc/util/models.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -834,7 +834,7 @@ def xml_request(
834834
apiurl: str,
835835
path: List[str],
836836
query: Optional[dict] = None,
837-
headers: Optional[str] = None,
837+
headers: Optional[dict] = None,
838838
data: Optional[str] = None,
839839
) -> urllib3.response.HTTPResponse:
840840
from ..connection import http_request

osc/util/safewriter.py

Lines changed: 18 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,39 @@
1+
import io
2+
13
# be careful when debugging this code:
24
# don't add print statements when setting sys.stdout = SafeWriter(sys.stdout)...
3-
class SafeWriter:
5+
class SafeWriter(io.TextIOBase):
46
"""
57
Safely write an (unicode) str. In case of an "UnicodeEncodeError" the
68
the str is encoded with the "encoding" encoding.
79
All getattr, setattr calls are passed through to the "writer" instance.
810
"""
911

1012
def __init__(self, writer, encoding='unicode_escape'):
13+
super().__init__()
1114
self._writer = writer
1215
self._encoding = encoding
1316

17+
# TextIOBase requires overriding the following stub methods: detach, read, readline, and write
18+
19+
def detach(self, *args, **kwargs):
20+
return self._writer.detach(*args, **kwargs)
21+
22+
def read(self, *args, **kwargs):
23+
return self._writer.read(args, **kwargs)
24+
25+
def readline(self, *args, **kwargs):
26+
return self._writer.readline(args, **kwargs)
27+
1428
def write(self, s):
1529
try:
1630
self._writer.write(s)
1731
except UnicodeEncodeError as e:
1832
self._writer.write(s.encode(self._encoding))
1933

34+
def fileno(self, *args, **kwargs):
35+
return self._writer.fileno(*args, **kwargs)
36+
2037
def __getattr__(self, name):
2138
return getattr(self._writer, name)
2239

0 commit comments

Comments
 (0)