Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-osc-tiny for openSUSE:Factory
checked in at 2022-08-17 18:17:11
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-osc-tiny (Old)
and /work/SRC/openSUSE:Factory/.python-osc-tiny.new.1521 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-osc-tiny"
Wed Aug 17 18:17:11 2022 rev:20 rq:997538 version:0.7.1
Changes:
--------
--- /work/SRC/openSUSE:Factory/python-osc-tiny/python-osc-tiny.changes
2022-08-06 22:08:43.906756610 +0200
+++
/work/SRC/openSUSE:Factory/.python-osc-tiny.new.1521/python-osc-tiny.changes
2022-08-17 18:25:19.387461136 +0200
@@ -1,0 +2,15 @@
+Tue Aug 16 12:34:34 UTC 2022 - Andreas Hasenkopf <[email protected]>
+
+- Release 0.7.1
+ * Make distinction between "true" booleans and pretenders
+
+-------------------------------------------------------------------
+Tue Aug 16 07:51:59 UTC 2022 - Andreas Hasenkopf <[email protected]>
+
+- Release 0.7.0
+ * Support setting of multiple values on attribute
+ * Added feature to download binaries
+ * Handle boolean query params
+ * Convert relative paths to SSH keys to absolute paths
+
+-------------------------------------------------------------------
Old:
----
osc-tiny-0.6.6.tar.gz
New:
----
osc-tiny-0.7.1.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-osc-tiny.spec ++++++
--- /var/tmp/diff_new_pack.4xpRYF/_old 2022-08-17 18:25:19.855462291 +0200
+++ /var/tmp/diff_new_pack.4xpRYF/_new 2022-08-17 18:25:19.863462310 +0200
@@ -19,7 +19,7 @@
%{?!python_module:%define python_module() python-%{**} python3-%{**}}
%define skip_python2 1
Name: python-osc-tiny
-Version: 0.6.6
+Version: 0.7.1
Release: 0
Summary: Client API for openSUSE BuildService
License: MIT
@@ -41,7 +41,6 @@
Requires: python-python-dateutil
Requires: python-pytz
Requires: python-requests
-Requires: python-responses
Suggests: openssh
BuildArch: noarch
# Using 'if' instead of 'with' because the latter requires rpm >= 4.14
++++++ osc-tiny-0.6.6.tar.gz -> osc-tiny-0.7.1.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/PKG-INFO new/osc-tiny-0.7.1/PKG-INFO
--- old/osc-tiny-0.6.6/PKG-INFO 2022-07-21 16:13:15.865302800 +0200
+++ new/osc-tiny-0.7.1/PKG-INFO 2022-08-16 14:32:49.240917400 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: osc-tiny
-Version: 0.6.6
+Version: 0.7.1
Summary: Client API for openSUSE BuildService
Home-page: http://github.com/crazyscientist/osc-tiny
Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master
@@ -23,8 +23,8 @@
OSC Tiny
========
-[](https://travis-ci.com/crazyscientist/osc-tiny)
-[](https://coveralls.io/github/crazyscientist/osc-tiny)
+]
+
[](https://badge.fury.io/py/osc-tiny)
This project aims to provide a minimalistic and transparent client for
accessing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/README.md new/osc-tiny-0.7.1/README.md
--- old/osc-tiny-0.6.6/README.md 2022-07-21 16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/README.md 2022-08-16 14:32:35.000000000 +0200
@@ -1,8 +1,8 @@
OSC Tiny
========
-[](https://travis-ci.com/crazyscientist/osc-tiny)
-[](https://coveralls.io/github/crazyscientist/osc-tiny)
+]
+
[](https://badge.fury.io/py/osc-tiny)
This project aims to provide a minimalistic and transparent client for
accessing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osc_tiny.egg-info/PKG-INFO
new/osc-tiny-0.7.1/osc_tiny.egg-info/PKG-INFO
--- old/osc-tiny-0.6.6/osc_tiny.egg-info/PKG-INFO 2022-07-21
16:13:15.000000000 +0200
+++ new/osc-tiny-0.7.1/osc_tiny.egg-info/PKG-INFO 2022-08-16
14:32:49.000000000 +0200
@@ -1,6 +1,6 @@
Metadata-Version: 2.1
Name: osc-tiny
-Version: 0.6.6
+Version: 0.7.1
Summary: Client API for openSUSE BuildService
Home-page: http://github.com/crazyscientist/osc-tiny
Download-URL: http://github.com/crazyscientist/osc-tiny/tarball/master
@@ -23,8 +23,8 @@
OSC Tiny
========
-[](https://travis-ci.com/crazyscientist/osc-tiny)
-[](https://coveralls.io/github/crazyscientist/osc-tiny)
+]
+
[](https://badge.fury.io/py/osc-tiny)
This project aims to provide a minimalistic and transparent client for
accessing
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/__init__.py
new/osc-tiny-0.7.1/osctiny/__init__.py
--- old/osc-tiny-0.6.6/osctiny/__init__.py 2022-07-21 16:13:03.000000000
+0200
+++ new/osc-tiny-0.7.1/osctiny/__init__.py 2022-08-16 14:32:35.000000000
+0200
@@ -6,4 +6,4 @@
__all__ = ['Osc', 'bs_requests', 'buildresults', 'comments', 'packages',
'projects', 'search', 'users']
-__version__ = "0.6.6"
+__version__ = "0.7.1"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/extensions/buildresults.py
new/osc-tiny-0.7.1/osctiny/extensions/buildresults.py
--- old/osc-tiny-0.6.6/osctiny/extensions/buildresults.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/extensions/buildresults.py 2022-08-16
14:32:35.000000000 +0200
@@ -115,19 +115,26 @@
return self.osc.get_objectified_xml(response)
# pylint: disable=too-many-arguments
- def get_binary(self, project, repo, arch, package, filename):
+ def get_binary(self, project, repo, arch, package, filename, raw=False):
"""
- Get the build binary file
+ Get the content of file
+
+ .. note:: This method decodes the content of the file and returns a
Python string by
+ default.
:param project: Project name
:param repo: Repository name
:param arch: Architecture name
:param package: Package name
:param filename: File name
- :return: Raw response
- :rtype: str
+ :param raw: If ``True``, return a byte string. Otherwise, a string is
returned
+ :return: Content of binary file
+ :rtype: str or bytes
.. versionadded:: 0.2.4
+
+ .. versionchanged:: 0.7.0
+ Added the ``raw`` parameter
"""
response = self.osc.request(
method="GET",
@@ -136,7 +143,34 @@
)),
)
- return response.text
+ return response.content if raw else response.text
+
+ def download_binary(self, project, repo, arch, package, filename, destdir,
destfile=None,
+ overwrite=False):
+ """
+ Download binary file to disk
+
+ :param project: Project name
+ :param repo: Repository name
+ :param arch: Architecture name
+ :param package: Package name
+ :param filename: File name
+ :param pathlib.Path destdir: Destination directory
+ :param str destfile: Target file name. If not specified, it will be
taken from the URL
+ :param bool overwrite: switch to overwrite existing downloaded file
+ :return: Path of downloaded file
+ :rtype: pathlib.Path
+
+ .. versionadded:: 0.7.0
+ """
+ return self.osc.download(
+ url=urljoin(self.osc.url, "{}/{}/{}/{}/{}/{}".format(
+ self.base_path, project, repo, arch, package, filename
+ )),
+ destdir=destdir,
+ destfile=destfile,
+ overwrite=overwrite
+ )
def cmd(self, project, cmd, **params):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/extensions/issues.py
new/osc-tiny-0.7.1/osctiny/extensions/issues.py
--- old/osc-tiny-0.6.6/osctiny/extensions/issues.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/extensions/issues.py 2022-08-16
14:32:35.000000000 +0200
@@ -49,14 +49,14 @@
)
return self.osc.get_objectified_xml(response)
- def get(self, tracker, name, force_update=None):
+ def get(self, tracker, name, force_update=False):
"""
Get details for an issue
:param str tracker: issue tracker name
:param str name: issue name
- :param force_update: If ``True``, BuildService will update the issue
- details internally prior to returning the response
+ :param bool force_update: If ``True``, BuildService will update the
issue
+ details internally prior to returning the
response
:return: Objectified XML element
:rtype: lxml.objectify.ObjectifiedElement
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/extensions/origin.py
new/osc-tiny-0.7.1/osctiny/extensions/origin.py
--- old/osc-tiny-0.6.6/osctiny/extensions/origin.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/extensions/origin.py 2022-08-16
14:32:35.000000000 +0200
@@ -497,7 +497,8 @@
return None
if resolve_inheritance:
- response = self.osc.packages.get_files(package=package,
project=project, withlinked=1)
+ response = self.osc.packages.get_files(package=package,
project=project,
+ withlinked=True)
links = response.xpath("linkinfo/linked")
if len(links) > 0:
project = links[-1].get("project")
@@ -535,7 +536,7 @@
warn("Project {} has no origin definition".format(project))
return
- packages = self.osc.projects.get_files(project, expand='1')
+ packages = self.osc.projects.get_files(project, expand=True)
for package in getattr(packages, "entry", []):
name = package.get("name")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/extensions/packages.py
new/osc-tiny-0.7.1/osctiny/extensions/packages.py
--- old/osc-tiny-0.6.6/osctiny/extensions/packages.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/extensions/packages.py 2022-08-16
14:32:35.000000000 +0200
@@ -3,7 +3,6 @@
------------------
"""
from __future__ import unicode_literals
-import errno
import os
from six.moves.urllib.parse import urljoin
from six import text_type
@@ -22,19 +21,23 @@
base_path = "/source"
new_package_meta_templ = "<package><title/><description/></package>"
- def get_list(self, project, deleted=False):
+ def get_list(self, project: str, deleted: bool = False, expand: bool =
True):
"""
Get packages from project
.. versionadded:: 0.1.7
Parameter ``deleted``
+ .. versionadded:: 0.7.0
+ Parameter ``expand``
+
:param project: name of project
:param deleted: Show deleted packages instead
+ :param expand: Include inherited packages and their project of origin
:return: Objectified XML element
:rtype: lxml.objectify.ObjectifiedElement
"""
- params = {"deleted": deleted}
+ params = {"deleted": deleted, "expand": expand}
response = self.osc.request(
url=urljoin(self.osc.url, "{}/{}".format(self.base_path, project)),
method="GET",
@@ -148,7 +151,7 @@
# pylint: disable=too-many-arguments
def get_file(self, project, package, filename, meta=False, rev=None,
- expand=0):
+ expand=False):
"""
Get a source file
@@ -207,26 +210,20 @@
.. versionchanged:: 0.3.3
Added the parameter ``expand``
- """
- abspath_filename = os.path.abspath(os.path.join(destdir, filename))
- if os.path.isfile(destdir):
- raise OSError(
- errno.EEXIST, "Target directory is a file", destdir
- )
- if not overwrite and os.path.exists(abspath_filename):
- raise OSError(
- errno.EEXIST, "File already exists", abspath_filename
- )
- if not os.path.exists(destdir):
- os.makedirs(destdir)
-
- response = self.get_file(project, package, filename, meta=meta,
rev=rev, expand=expand)
-
- with open(abspath_filename, "wb") as handle:
- for chunk in response.iter_content(1024):
- handle.write(chunk)
- return abspath_filename
+ .. versionchanged:: 0.7.0
+ Moved some logic to :py:meth:`osctiny.osc.Osc.download`
+ """
+ return self.osc.download(
+ url=urljoin(self.osc.url,
+ "{}/{}/{}/{}".format(self.base_path, project, package,
filename)),
+ destdir=destdir,
+ destfile=filename,
+ overwrite=overwrite,
+ meta=meta,
+ rev=rev,
+ expand=expand
+ )
def push_file(self, project, package, filename, data, comment=None):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/extensions/projects.py
new/osc-tiny-0.7.1/osctiny/extensions/projects.py
--- old/osc-tiny-0.6.6/osctiny/extensions/projects.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/extensions/projects.py 2022-08-16
14:32:35.000000000 +0200
@@ -8,13 +8,12 @@
from six import text_type
from lxml.etree import tounicode
-from lxml.objectify import fromstring
+from lxml.objectify import fromstring, SubElement
from ..utils.base import ExtensionBase
-TEMPLATE_CREATE_ATTR = "<attributes><attribute namespace='' name=''>" \
- "<value></value></attribute></attributes>"
+TEMPLATE_CREATE_ATTR = "<attributes><attribute namespace=''
name=''></attribute></attributes>"
TEMPLATE_META = "<project name=''><title></title><description></description>" \
"<person userid='' role='bugowner'/>" \
"<person userid='' role='maintainer'/>" \
@@ -133,8 +132,7 @@
:return: Objectified XML element
:rtype: lxml.objectify.ObjectifiedElement
"""
- if meta:
- kwargs["meta"] = '1'
+ kwargs["meta"] = meta
if rev:
kwargs["rev"] = text_type(rev)
response = self.osc.request(
@@ -185,9 +183,12 @@
:param project: project name
:param attribute: attribute name (can include prefix separated by
colon)
- :param value: attribute value
+ :param value: attribute value or list of values
:return: ``True``, if successful. Otherwise API response
:rtype: bool or lxml.objectify.ObjectifiedElement
+
+ .. versionchanged:: 0.7.0
+ Support attributes with multiple values
"""
url = urljoin(
self.osc.url,
@@ -199,11 +200,14 @@
if match is None:
raise ValueError("Invalid attribute format: {}".format(attribute))
+ value = value if isinstance(value, (list, tuple, set)) else [value]
attr_xml = fromstring(TEMPLATE_CREATE_ATTR)
attr_xml.attribute.set('namespace', match.group("prefix"))
attr_xml.attribute.set('name', match.group("name"))
- # pylint: disable=protected-access
- attr_xml.attribute.value._setText(text_type(value))
+ for val in value:
+ elem = SubElement(attr_xml.attribute, "value")
+ # pylint: disable=protected-access
+ elem._setText(text_type(val))
response = self.osc.request(
url=url,
@@ -297,7 +301,8 @@
"""
if rev:
kwargs["rev"] = rev
- kwargs["meta"] = "1" if meta else "0"
+
+ kwargs["meta"] = meta
response = self.osc.request(
url=urljoin(self.osc.url, "{}/{}/_project/_history".format(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/osc.py
new/osc-tiny-0.7.1/osctiny/osc.py
--- old/osc-tiny-0.6.6/osctiny/osc.py 2022-07-21 16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/osc.py 2022-08-16 14:32:35.000000000 +0200
@@ -6,6 +6,7 @@
from base64 import b64encode
import typing
+import errno
from io import BufferedReader, BytesIO, StringIO
import gc
import logging
@@ -14,7 +15,7 @@
from ssl import get_default_verify_paths
import time
import threading
-from urllib.parse import quote
+from urllib.parse import quote, parse_qs, urlparse
import warnings
# pylint: disable=no-name-in-module
@@ -35,7 +36,7 @@
from .extensions.search import Search
from .extensions.users import Group, Person
from .utils.auth import HttpSignatureAuth
-from .utils.conf import get_credentials
+from .utils.conf import BOOLEAN_PARAMS, get_credentials
from .utils.errors import OscError
try:
@@ -321,7 +322,11 @@
"\n".join(f"{k}: {v}" for k, v in req.data.items())
if isinstance(req.data, dict) else req.data)
logger.debug("Sent parameters:\n%s\n---",
- "\n".join(f"{k}: {v}" for k, v in req.params.items()))
+ "\n".join(f"{k}: {v}" for k, v in (
+ req.params
+ if isinstance(req.params, dict)
+ else parse_qs(req.params, keep_blank_values=True)
+ ).items()))
try:
response = session.send(prepped_req, **settings)
except _ConnectionError as error:
@@ -349,7 +354,15 @@
Translate request parameters to API conform format
.. note:: The build service does not handle URL-encoded Unicode well.
- Therefore parameters are encoded as ``bytes``.
+ Therefore, parameters are encoded as ``bytes``.
+
+ .. warning:: The build service does not declare its parameters
properly and developers do
+ `not intend to fix`_ this server-side. If you want to use
_boolean_ parameters,
+ make sure to use ``True`` and ``False``. If you use ``0``
or ``1`` instead, you
+ might receive unexpected results.
+
+ .. _not intend to fix:
https://github.com/openSUSE/open-build-service/issues
+ /9715
:param params: Request parameter
:type params: dict or str or io.BufferedReader
@@ -373,13 +386,65 @@
if not isinstance(params, dict):
return {}
- for key in params:
- if isinstance(params[key], bool):
+ # The OBS API has a weird expectation regarding boolean parameters and
the maintainers have
+ # made it clear, that they are not going to clean up the API :(
+ # See: https://github.com/openSUSE/open-build-service/issues/9715
+ # Also, there are parameters giving the impression that they are
boolean, but actually are
+ # not.
+ unexpected_bools = {key for key, value in params.items()
+ if isinstance(value, bool) and key not in
BOOLEAN_PARAMS}
+ if unexpected_bools:
+ warnings.warn(f"Received boolean query params, which are not
expected to be: "
+ f"{', '.join(unexpected_bools)}")
+ for key in unexpected_bools:
params[key] = '1' if params[key] else '0'
- return {key.encode(): str(value).encode()
+ return "&".join(
+ quote(str(key))
+ if key in BOOLEAN_PARAMS
+ else f"{quote(str(key))}={quote(str(value))}"
+ for key, value in (
+ (key, value)
for key, value in params.items()
- if value is not None}
+ if not (key in BOOLEAN_PARAMS and value in [False, "0", 0,
None, ""])
+ )
+ if value is not None
+ ).encode()
+
+ def download(self, url, destdir, destfile=None, overwrite=False, **params):
+ """
+ Shortcut for a streaming GET request
+
+ :param str url: Download URL
+ :param pathlib.Path destdir: Destination directory
+ :param str destfile: Target file name. If not specified, it will be
taken from the URL
+ :param bool overwrite: switch to overwrite existing downloaded file
+ :param params: Additional query params
+ :return: absolute path to file or ``None``
+
+ .. versionadded:: 0.7.0
+ """
+ destdir = destdir if isinstance(destdir, Path) else Path(destdir)
+ if not destfile:
+ parsed = urlparse(url)
+ destfile = Path(parsed.path).name
+
+ if destdir.is_file():
+ raise OSError(errno.EEXIST, "Target directory is a file", destdir)
+
+ target = destdir.joinpath(destfile)
+ if not overwrite and target.exists():
+ raise OSError(errno.EEXIST, "File already exists", target)
+ if not destdir.exists():
+ destdir.mkdir(parents=True, exist_ok=True)
+
+ response = self.request(url=url, method="GET", stream=True,
params=params)
+
+ with target.open("wb") as handle:
+ for chunk in response.iter_content(1024):
+ handle.write(chunk)
+
+ return target
def get_objectified_xml(self, response):
"""
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/tests/test_basic.py
new/osc-tiny-0.7.1/osctiny/tests/test_basic.py
--- old/osc-tiny-0.6.6/osctiny/tests/test_basic.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/tests/test_basic.py 2022-08-16
14:32:35.000000000 +0200
@@ -1,6 +1,8 @@
# -*- coding: utf-8 -*-
+import pathlib
import re
-from urllib.parse import unquote_plus
+import tempfile
+from urllib.parse import unquote_plus, parse_qs
import responses
@@ -9,6 +11,43 @@
class BasicTest(OscTest):
+ @responses.activate
+ def test_download(self):
+ filename = 'test-file.bin'
+ url = self.osc.url + '/' + filename
+ content = "L??rem ????sum do??or si?? a??et ..."
+ self.mock_request(
+ method=responses.GET,
+ url=url,
+ body=content.encode()
+ )
+
+ tmpfile1 = pathlib.Path(tempfile.mkstemp()[1])
+ kwargs = {"url": url, "destdir": tmpfile1.parent, "destfile":
tmpfile1.name}
+
+ with self.subTest("overwrite=False"):
+ self.assertRaises(OSError, self.osc.download, **kwargs)
+
+ with self.subTest("overwrite=True"):
+ kwargs2 = kwargs.copy()
+ kwargs2["overwrite"] = True
+ tmpfile2 = self.osc.download(**kwargs2)
+ self.assertEqual(tmpfile1, tmpfile2)
+ with tmpfile2.open("r") as handle:
+ self.assertEqual(content, handle.read())
+
+ with self.subTest("No destfile"):
+ kwargs2 = kwargs.copy()
+ del kwargs2["destfile"]
+ tmpfile2 = self.osc.download(**kwargs2)
+ try:
+ self.assertEqual(tmpfile1.parent, tmpfile2.parent)
+ self.assertEqual(tmpfile2.name, filename)
+ with tmpfile2.open("r") as handle:
+ self.assertEqual(content, handle.read())
+ finally:
+ tmpfile2.unlink()
+
def test_handle_params(self):
def _run(data, expected):
handled = self.osc.handle_params(data)
@@ -19,25 +58,51 @@
(1, {}),
("hello world", b"hello world"),
("f???? b??r", b"f\xc3\xb8\xc3\xb8 b\xc3\xa6r"),
+ # 'withissues' is not a boolean param in the API
(
{"view": "xml", "withissues": 1},
- {b"view": b"xml", b"withissues": b"1"}
+ b"view=xml&withissues=1"
),
(
{"view": "xml", "withissues": True},
- {b"view": b"xml", b"withissues": b"1"}
+ b"view=xml&withissues=1"
),
(
{"view": "xml", "withissues": 0},
- {b"view": b"xml", b"withissues": b"0"}
+ b"view=xml&withissues=0"
),
(
{"view": "xml", "withissues": False},
- {b"view": b"xml", b"withissues": b"0"}
+ b"view=xml&withissues=0"
),
(
{"view": "xml", "withissues": None},
- {b"view": b"xml"}
+ b"view=xml"
+ ),
+ # 'deleted' is a boolean param in the API
+ (
+ {"view": "xml", "deleted": 1},
+ b"view=xml&deleted"
+ ),
+ (
+ {"view": "xml", "deleted": 0},
+ b"view=xml"
+ ),
+ (
+ {"view": "xml", "deleted": "1"},
+ b"view=xml&deleted"
+ ),
+ (
+ {"view": "xml", "deleted": "0"},
+ b"view=xml"
+ ),
+ (
+ {"view": "xml", "deleted": False},
+ b"view=xml"
+ ),
+ (
+ {"view": "xml", "deleted": True},
+ b"view=xml&deleted"
),
)
@@ -89,3 +154,30 @@
for name, filename in data:
with self.subTest(name):
self.osc.request(f"{self.osc.url}file/{filename}")
+
+ @responses.activate
+ def test_request_boolean_params(self):
+ pattern = re.compile(self.osc.url + r'/\?(?P<query>.*)')
+
+ def callback(headers, params, request):
+ match = pattern.match(request.url)
+
+ parsed = parse_qs(match.group("query"), keep_blank_values=True)
+ self.assertEqual(parsed, expected)
+ return 200, headers, ""
+
+ self.mock_request(
+ method=responses.GET,
+ url=pattern,
+ callback=CallbackFactory(callback)
+ )
+
+ for path, expected in (
+ (b"foo", {"foo": [""]}),
+ (b"foo=bar", {"foo": ["bar"]}),
+ (b"foo=foo&bar", {"foo": ["foo"], "bar": [""]}),
+ (b"foo=foo?bar", {"foo": ["foo?bar"]}),
+ (b"foo=foo=bar", {"foo": ["foo=bar"]})
+ ):
+ with self.subTest(path):
+ self.osc.request(self.osc.url, params=path)
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/tests/test_build.py
new/osc-tiny-0.7.1/osctiny/tests/test_build.py
--- old/osc-tiny-0.6.6/osctiny/tests/test_build.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/tests/test_build.py 2022-08-16
14:32:35.000000000 +0200
@@ -15,7 +15,7 @@
status = 500
body = ""
parsed = urlparse(request.url)
- params.update(parse_qs(parsed.query))
+ params.update(parse_qs(parsed.query, keep_blank_values=True))
if not params:
status = 200
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/tests/test_issues.py
new/osc-tiny-0.7.1/osctiny/tests/test_issues.py
--- old/osc-tiny-0.6.6/osctiny/tests/test_issues.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/tests/test_issues.py 2022-08-16
14:32:35.000000000 +0200
@@ -1,5 +1,6 @@
# -*- coding: utf8 -*-
import re
+from urllib.parse import parse_qs, urlparse
from lxml.objectify import ObjectifiedElement
from requests.exceptions import HTTPError
@@ -102,7 +103,9 @@
@responses.activate
def test_get(self):
def callback(headers, params, request):
- if params.get("force_update", ["0"]) == ["1"]:
+ parsed = urlparse(request.url)
+ query_params = parse_qs(parsed.query, keep_blank_values=True)
+ if query_params.get("force_update", ["0"]) == ["1"]:
status, body = 200, u"""
<issue>
<created_at>2020-01-04 14:12:00 UTC</created_at>
@@ -131,17 +134,16 @@
self.mock_request(
method=responses.GET,
- url=re.compile(self.osc.url +
- r'/issue_trackers/bnc/issues/1160086/?.*'),
+ url=re.compile(self.osc.url +
r'/issue_trackers/bnc/issues/1160086/?.*'),
callback=CallbackFactory(callback)
)
- with self.subTest("Manual force update"):
+ with self.subTest("Force update"):
response = self.osc.issues.get("bnc", 1160086, True)
self.assertTrue(hasattr(response, "summary"))
self.assertEqual(len(responses.calls), 2)
- with self.subTest("Manual force update"):
+ with self.subTest("Update"):
response = self.osc.issues.get("bnc", 1160086, False)
self.assertTrue(hasattr(response, "summary"))
# to whom it may concern: `responses.calls` does not get reset
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/tests/test_projects.py
new/osc-tiny-0.7.1/osctiny/tests/test_projects.py
--- old/osc-tiny-0.6.6/osctiny/tests/test_projects.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/tests/test_projects.py 2022-08-16
14:32:35.000000000 +0200
@@ -24,8 +24,8 @@
</directory>
"""
parsed = urlparse(request.url)
- params.update(parse_qs(parsed.query))
- if params.get("deleted", ["0"]) == ["1"]:
+ params.update(parse_qs(parsed.query, keep_blank_values=True))
+ if "deleted" in params:
status = 403
body = """<status code="no_permission_for_deleted">
<summary>only admins can see deleted projects</summary>
@@ -165,9 +165,9 @@
</directory>
"""
parsed = urlparse(request.url)
- params.update(parse_qs(parsed.query))
+ params.update(parse_qs(parsed.query, keep_blank_values=True))
- if params.get("meta", ['0']) == ['1']:
+ if "meta" in params:
status = 200
body = """
<directory name="_project" rev="41" vrev=""
@@ -267,6 +267,7 @@
@responses.activate
def test_set_attribute(self):
def callback(headers, params, request):
+ self.assertEqual(request.body, expected)
status, body = 200, "<status code='ok'></status>"
return status, headers, body
@@ -278,13 +279,27 @@
callback=CallbackFactory(callback)
)
- self.assertTrue(
- self.osc.projects.set_attribute(
- project="test:project",
- attribute="namespace:attr",
- value="value"
+ with self.subTest("Single value"):
+ expected = b'<attributes><attribute namespace="namespace"
name="attr">' \
+ b'<value>value</value></attribute></attributes>'
+ self.assertTrue(
+ self.osc.projects.set_attribute(
+ project="test:project",
+ attribute="namespace:attr",
+ value="value"
+ )
+ )
+
+ with self.subTest("Two values"):
+ expected = b'<attributes><attribute namespace="namespace"
name="attr">' \
+
b'<value>value1</value><value>value2</value></attribute></attributes>'
+ self.assertTrue(
+ self.osc.projects.set_attribute(
+ project="test:project",
+ attribute="namespace:attr",
+ value=["value1", "value2"]
+ )
)
- )
@responses.activate
def test_delete_attribute(self):
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/tests/test_requests.py
new/osc-tiny-0.7.1/osctiny/tests/test_requests.py
--- old/osc-tiny-0.6.6/osctiny/tests/test_requests.py 2022-07-21
16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/osctiny/tests/test_requests.py 2022-08-16
14:32:35.000000000 +0200
@@ -12,7 +12,7 @@
status = 500
body = ""
parsed = urlparse(request.url)
- params.update(parse_qs(parsed.query))
+ params.update(parse_qs(parsed.query, keep_blank_values=True))
if re.search("comments/request/30902", request.url):
status = 200
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/osctiny/utils/conf.py
new/osc-tiny-0.7.1/osctiny/utils/conf.py
--- old/osc-tiny-0.6.6/osctiny/utils/conf.py 2022-07-21 16:13:03.000000000
+0200
+++ new/osc-tiny-0.7.1/osctiny/utils/conf.py 2022-08-16 14:32:35.000000000
+0200
@@ -22,6 +22,30 @@
_conf = None
+# Query parameters that are considered to be boolean by the build service
+BOOLEAN_PARAMS = (
+ "add_repositories",
+ "deleted",
+ "emptylink",
+ "expand",
+ "extend_package_names",
+ "extend_package_names",
+ "ignoredevel",
+ "keeplink",
+ "lastbuild",
+ "lastworking",
+ "locallink",
+ "meta",
+ "multibuild",
+ "noaccess",
+ "parse",
+ "repairlink",
+ "update_path_elements",
+ "withdownloadurl",
+ "withlinked",
+)
+
+
def get_config_path() -> Path:
"""
Return path of ``osc`` configuration file
@@ -157,7 +181,11 @@
if sshkey is not None:
if not sshkey.exists():
- raise ValueError(f"SSH key from config does not exist: {sshkey}")
+ # if it is just a key file name, look at the default SSH dir
(which is the most
+ # common case)
+ sshkey = Path.home() / ".ssh" / sshkey
+ if not sshkey.exists():
+ raise ValueError(f"SSH key from config does not exist:
{sshkey}")
if not password and not sshkey:
raise ValueError(f"`osc` config provides no password or SSH key for
URL {url}")
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/osc-tiny-0.6.6/setup.py new/osc-tiny-0.7.1/setup.py
--- old/osc-tiny-0.6.6/setup.py 2022-07-21 16:13:03.000000000 +0200
+++ new/osc-tiny-0.7.1/setup.py 2022-08-16 14:32:35.000000000 +0200
@@ -26,7 +26,7 @@
setup(
name='osc-tiny',
- version='0.6.6',
+ version='0.7.1',
description='Client API for openSUSE BuildService',
long_description=long_description,
long_description_content_type="text/markdown",