Skip to content

Commit f754564

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 c4c1208 commit f754564

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
@@ -30,6 +30,13 @@
3030
%bcond_with fdupes
3131
%endif
3232

33+
# use typeguard during build on distros where typeguard is available
34+
%if (0%{?suse_version} > 1500 || 0%{?fedora} >= 37)
35+
%bcond_without typeguard
36+
%else
37+
%bcond_with typeguard
38+
%endif
39+
3340
# the macro exists only on openSUSE based distros
3441
%if %{undefined python3_fix_shebang}
3542
%define python3_fix_shebang %nil
@@ -82,6 +89,9 @@ BuildRequires: %{use_python_pkg}-cryptography
8289
BuildRequires: %{use_python_pkg}-devel >= 3.6
8390
BuildRequires: %{use_python_pkg}-rpm
8491
BuildRequires: %{use_python_pkg}-setuptools
92+
%if %{with typeguard}
93+
BuildRequires: %{use_python_pkg}-typeguard
94+
%endif
8595
BuildRequires: %{use_python_pkg}-urllib3
8696
BuildRequires: %{yaml_pkg}
8797
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
@@ -5001,6 +5001,8 @@ def do_rdiff(self, subcmd, opts, *args):
50015001
rev2 = -rev - 1
50025002
else:
50035003
return
5004+
rev1 = str(rev1)
5005+
rev2 = str(rev2)
50045006
except:
50055007
print(f'Revision \'{opts.change}\' not an integer', file=sys.stderr)
50065008
return

osc/core.py

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

13361336

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

29442944
def server_diff(
29452945
apiurl: str,
2946-
old_project: str,
2947-
old_package: str,
2948-
old_revision: str,
2946+
old_project: Optional[str],
2947+
old_package: Optional[str],
2948+
old_revision: Optional[str],
29492949
new_project: str,
29502950
new_package: str,
2951-
new_revision: str,
2951+
new_revision: Optional[str],
29522952
unified=False,
29532953
missingok=False,
29542954
meta=False,
@@ -3009,12 +3009,12 @@ def server_diff(
30093009

30103010
def server_diff_noex(
30113011
apiurl: str,
3012-
old_project: str,
3013-
old_package: str,
3014-
old_revision: str,
3012+
old_project: Optional[str],
3013+
old_package: Optional[str],
3014+
old_revision: Optional[str],
30153015
new_project: str,
30163016
new_package: str,
3017-
new_revision: str,
3017+
new_revision: Optional[str],
30183018
unified=False,
30193019
missingok=False,
30203020
meta=False,
@@ -3312,9 +3312,9 @@ def checkout_package(
33123312

33133313

33143314
def replace_pkg_meta(
3315-
pkgmeta, new_name: str, new_prj: str, keep_maintainers=False, dst_userid=None, keep_develproject=False,
3315+
pkgmeta: List[bytes], new_name: str, new_prj: str, keep_maintainers=False, dst_userid=None, keep_develproject=False,
33163316
keep_lock: bool = False, keep_scmsync: bool = True,
3317-
):
3317+
) -> str:
33183318
"""
33193319
update pkgmeta with new new_name and new_prj and set calling user as the
33203320
only maintainer (unless keep_maintainers is set). Additionally remove the
@@ -3557,7 +3557,7 @@ def aggregate_pac(
35573557

35583558
if meta_change:
35593559
src_meta = show_package_meta(apiurl, src_project, src_package_meta)
3560-
dst_meta = replace_pkg_meta(src_meta, dst_package_meta, dst_project)
3560+
dst_meta = replace_pkg_meta(src_meta, dst_package_meta, dst_project).split("\n")
35613561
meta_change = True
35623562

35633563
if disable_publish:
@@ -4848,25 +4848,26 @@ def get_commitlog(
48484848
# revision is srcmd5
48494849
revision_list = [i for i in revision_list if i.srcmd5 == revision]
48504850
else:
4851-
revision = int(revision)
4851+
assert revision is not None
4852+
revision_int = int(revision)
48524853
if revision_is_empty(revision_upper):
4853-
revision_list = [i for i in revision_list if i.rev == revision]
4854+
revision_list = [i for i in revision_list if i.rev == revision_int]
48544855
else:
4855-
revision_upper = int(revision_upper)
4856-
revision_list = [i for i in revision_list if i.rev <= revision_upper and i.rev >= revision]
4856+
revision_upper_int = int(revision_upper)
4857+
revision_list = [i for i in revision_list if i.rev <= revision_upper_int and i.rev >= revision_int]
48574858

48584859
if format == "csv":
48594860
f = io.StringIO()
48604861
writer = csv.writer(f, dialect="unix")
4861-
for revision in reversed(revision_list):
4862+
for i in reversed(revision_list):
48624863
writer.writerow(
48634864
(
4864-
revision.rev,
4865-
revision.user,
4866-
revision.get_time_str(),
4867-
revision.srcmd5,
4868-
revision.comment,
4869-
revision.requestid,
4865+
i.rev,
4866+
i.user,
4867+
i.get_time_str(),
4868+
i.srcmd5,
4869+
i.comment,
4870+
i.requestid,
48704871
)
48714872
)
48724873
f.seek(0)
@@ -4875,42 +4876,42 @@ def get_commitlog(
48754876

48764877
if format == "xml":
48774878
root = ET.Element("log")
4878-
for revision in reversed(revision_list):
4879+
for i in reversed(revision_list):
48794880
entry = ET.SubElement(root, "logentry")
4880-
entry.attrib["revision"] = str(revision.rev)
4881-
entry.attrib["srcmd5"] = revision.srcmd5
4882-
ET.SubElement(entry, "author").text = revision.user
4883-
ET.SubElement(entry, "date").text = revision.get_time_str()
4884-
ET.SubElement(entry, "requestid").text = str(revision.requestid) if revision.requestid else ""
4885-
ET.SubElement(entry, "msg").text = revision.comment or ""
4881+
entry.attrib["revision"] = str(i.rev)
4882+
entry.attrib["srcmd5"] = i.srcmd5
4883+
ET.SubElement(entry, "author").text = i.user
4884+
ET.SubElement(entry, "date").text = i.get_time_str()
4885+
ET.SubElement(entry, "requestid").text = str(i.requestid) if i.requestid else ""
4886+
ET.SubElement(entry, "msg").text = i.comment or ""
48864887
xmlindent(root)
48874888
yield from ET.tostring(root, encoding="utf-8").decode("utf-8").splitlines()
48884889
return
48894890

48904891
if format == "text":
4891-
for revision in reversed(revision_list):
4892+
for i in reversed(revision_list):
48924893
entry = (
4893-
f"r{revision.rev}",
4894-
revision.user,
4895-
revision.get_time_str(),
4896-
revision.srcmd5,
4897-
revision.version,
4898-
f"rq{revision.requestid}" if revision.requestid else ""
4894+
f"r{i.rev}",
4895+
i.user,
4896+
i.get_time_str(),
4897+
i.srcmd5,
4898+
i.version,
4899+
f"rq{i.requestid}" if i.requestid else ""
48994900
)
49004901
yield 76 * "-"
49014902
yield " | ".join(entry)
49024903
yield ""
4903-
yield revision.comment or "<no message>"
4904+
yield i.comment or "<no message>"
49044905
yield ""
49054906
if patch:
49064907
rdiff = server_diff_noex(
49074908
apiurl,
49084909
prj,
49094910
package,
4910-
revision.rev - 1,
4911+
str(i.rev - 1),
49114912
prj,
49124913
package,
4913-
revision.rev,
4914+
str(i.rev),
49144915
meta=meta,
49154916
)
49164917
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
@@ -794,7 +794,7 @@ def xml_request(
794794
apiurl: str,
795795
path: List[str],
796796
query: Optional[dict] = None,
797-
headers: Optional[str] = None,
797+
headers: Optional[dict] = None,
798798
data: Optional[str] = None,
799799
) -> urllib3.response.HTTPResponse:
800800
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)