Skip to content

Commit 692868c

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 a2518aa commit 692868c

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
@@ -4988,6 +4988,8 @@ def do_rdiff(self, subcmd, opts, *args):
49884988
rev2 = -rev - 1
49894989
else:
49904990
return
4991+
rev1 = str(rev1)
4992+
rev2 = str(rev2)
49914993
except:
49924994
print(f'Revision \'{opts.change}\' not an integer', file=sys.stderr)
49934995
return

osc/core.py

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

13411341

1342-
def show_package_meta(apiurl: str, prj: str, pac: str, meta=False, blame=None):
1342+
def show_package_meta(apiurl: str, prj: str, pac: str, meta=False, blame=None) -> List[bytes]:
13431343
query: Dict[str, Union[str, int]] = {}
13441344
if meta:
13451345
query['meta'] = 1
@@ -2948,12 +2948,12 @@ def get_source_file_diff(dir, filename, rev, oldfilename=None, olddir=None, orig
29482948

29492949
def server_diff(
29502950
apiurl: str,
2951-
old_project: str,
2952-
old_package: str,
2953-
old_revision: str,
2951+
old_project: Optional[str],
2952+
old_package: Optional[str],
2953+
old_revision: Optional[str],
29542954
new_project: str,
29552955
new_package: str,
2956-
new_revision: str,
2956+
new_revision: Optional[str],
29572957
unified=False,
29582958
missingok=False,
29592959
meta=False,
@@ -3014,12 +3014,12 @@ def server_diff(
30143014

30153015
def server_diff_noex(
30163016
apiurl: str,
3017-
old_project: str,
3018-
old_package: str,
3019-
old_revision: str,
3017+
old_project: Optional[str],
3018+
old_package: Optional[str],
3019+
old_revision: Optional[str],
30203020
new_project: str,
30213021
new_package: str,
3022-
new_revision: str,
3022+
new_revision: Optional[str],
30233023
unified=False,
30243024
missingok=False,
30253025
meta=False,
@@ -3317,9 +3317,9 @@ def checkout_package(
33173317

33183318

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

35633563
if meta_change:
35643564
src_meta = show_package_meta(apiurl, src_project, src_package_meta)
3565-
dst_meta = replace_pkg_meta(src_meta, dst_package_meta, dst_project)
3565+
dst_meta = replace_pkg_meta(src_meta, dst_package_meta, dst_project).split("\n")
35663566
meta_change = True
35673567

35683568
if disable_publish:
@@ -4853,25 +4853,26 @@ def get_commitlog(
48534853
# revision is srcmd5
48544854
revision_list = [i for i in revision_list if i.srcmd5 == revision]
48554855
else:
4856-
revision = int(revision)
4856+
assert revision is not None
4857+
revision_int = int(revision)
48574858
if revision_is_empty(revision_upper):
4858-
revision_list = [i for i in revision_list if i.rev == revision]
4859+
revision_list = [i for i in revision_list if i.rev == revision_int]
48594860
else:
4860-
revision_upper = int(revision_upper)
4861-
revision_list = [i for i in revision_list if i.rev <= revision_upper and i.rev >= revision]
4861+
revision_upper_int = int(revision_upper)
4862+
revision_list = [i for i in revision_list if i.rev <= revision_upper_int and i.rev >= revision_int]
48624863

48634864
if format == "csv":
48644865
f = io.StringIO()
48654866
writer = csv.writer(f, dialect="unix")
4866-
for revision in reversed(revision_list):
4867+
for i in reversed(revision_list):
48674868
writer.writerow(
48684869
(
4869-
revision.rev,
4870-
revision.user,
4871-
revision.get_time_str(),
4872-
revision.srcmd5,
4873-
revision.comment,
4874-
revision.requestid,
4870+
i.rev,
4871+
i.user,
4872+
i.get_time_str(),
4873+
i.srcmd5,
4874+
i.comment,
4875+
i.requestid,
48754876
)
48764877
)
48774878
f.seek(0)
@@ -4880,42 +4881,42 @@ def get_commitlog(
48804881

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

48954896
if format == "text":
4896-
for revision in reversed(revision_list):
4897+
for i in reversed(revision_list):
48974898
entry = (
4898-
f"r{revision.rev}",
4899-
revision.user,
4900-
revision.get_time_str(),
4901-
revision.srcmd5,
4902-
revision.version,
4903-
f"rq{revision.requestid}" if revision.requestid else ""
4899+
f"r{i.rev}",
4900+
i.user,
4901+
i.get_time_str(),
4902+
i.srcmd5,
4903+
i.version,
4904+
f"rq{i.requestid}" if i.requestid else ""
49044905
)
49054906
yield 76 * "-"
49064907
yield " | ".join(entry)
49074908
yield ""
4908-
yield revision.comment or "<no message>"
4909+
yield i.comment or "<no message>"
49094910
yield ""
49104911
if patch:
49114912
rdiff = server_diff(
49124913
apiurl,
49134914
prj,
49144915
package,
4915-
revision.rev - 1,
4916+
str(i.rev - 1),
49164917
prj,
49174918
package,
4918-
revision.rev,
4919+
str(i.rev),
49194920
meta=meta,
49204921
)
49214922
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)