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 2024-06-28 15:47:44 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/python-osc-tiny (Old) and /work/SRC/openSUSE:Factory/.python-osc-tiny.new.18349 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Package is "python-osc-tiny" Fri Jun 28 15:47:44 2024 rev:34 rq:1183801 version:0.10.0 Changes: -------- --- /work/SRC/openSUSE:Factory/python-osc-tiny/python-osc-tiny.changes 2024-05-22 21:32:44.714567797 +0200 +++ /work/SRC/openSUSE:Factory/.python-osc-tiny.new.18349/python-osc-tiny.changes 2024-06-28 15:51:23.487491514 +0200 @@ -1,0 +2,7 @@ +Thu Jun 27 15:45:09 UTC 2024 - Chen Huang <chhuang@suse.com> + +- Release 0.10.0 + * Added capability to create requests (#165) + * Install the theme for the docs (#163) + +------------------------------------------------------------------- Old: ---- osc-tiny-0.9.1.tar.gz New: ---- osc-tiny-0.10.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ python-osc-tiny.spec ++++++ --- /var/tmp/diff_new_pack.Iu2a4S/_old 2024-06-28 15:51:28.563677205 +0200 +++ /var/tmp/diff_new_pack.Iu2a4S/_new 2024-06-28 15:51:28.567677352 +0200 @@ -19,7 +19,7 @@ %define skip_python2 1 %{?sle15allpythons} Name: python-osc-tiny -Version: 0.9.1 +Version: 0.10.0 Release: 0 Summary: Client API for openSUSE BuildService License: MIT ++++++ osc-tiny-0.9.1.tar.gz -> osc-tiny-0.10.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/PKG-INFO new/osc-tiny-0.10.0/PKG-INFO --- old/osc-tiny-0.9.1/PKG-INFO 2024-05-22 10:58:11.398337600 +0200 +++ new/osc-tiny-0.10.0/PKG-INFO 2024-06-27 13:00:45.511684400 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: osc-tiny -Version: 0.9.1 +Version: 0.10.0 Summary: Client API for openSUSE BuildService Home-page: https://github.com/SUSE/osc-tiny Author: Andreas Hasenkopf diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osc_tiny.egg-info/PKG-INFO new/osc-tiny-0.10.0/osc_tiny.egg-info/PKG-INFO --- old/osc-tiny-0.9.1/osc_tiny.egg-info/PKG-INFO 2024-05-22 10:58:11.000000000 +0200 +++ new/osc-tiny-0.10.0/osc_tiny.egg-info/PKG-INFO 2024-06-27 13:00:45.000000000 +0200 @@ -1,6 +1,6 @@ Metadata-Version: 2.1 Name: osc-tiny -Version: 0.9.1 +Version: 0.10.0 Summary: Client API for openSUSE BuildService Home-page: https://github.com/SUSE/osc-tiny Author: Andreas Hasenkopf diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osc_tiny.egg-info/SOURCES.txt new/osc-tiny-0.10.0/osc_tiny.egg-info/SOURCES.txt --- old/osc-tiny-0.9.1/osc_tiny.egg-info/SOURCES.txt 2024-05-22 10:58:11.000000000 +0200 +++ new/osc-tiny-0.10.0/osc_tiny.egg-info/SOURCES.txt 2024-06-27 13:00:45.000000000 +0200 @@ -26,6 +26,7 @@ osctiny/extensions/staging.py osctiny/extensions/users.py osctiny/models/__init__.py +osctiny/models/request.py osctiny/models/staging.py osctiny/tests/__init__.py osctiny/tests/base.py diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osctiny/__init__.py new/osc-tiny-0.10.0/osctiny/__init__.py --- old/osc-tiny-0.9.1/osctiny/__init__.py 2024-05-22 10:58:07.000000000 +0200 +++ new/osc-tiny-0.10.0/osctiny/__init__.py 2024-06-27 13:00:41.000000000 +0200 @@ -6,4 +6,4 @@ __all__ = ['Osc', 'bs_requests', 'buildresults', 'comments', 'packages', 'projects', 'search', 'users'] -__version__ = "0.9.1" +__version__ = "0.10.0" diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osctiny/extensions/bs_requests.py new/osc-tiny-0.10.0/osctiny/extensions/bs_requests.py --- old/osc-tiny-0.9.1/osctiny/extensions/bs_requests.py 2024-05-22 10:58:07.000000000 +0200 +++ new/osc-tiny-0.10.0/osctiny/extensions/bs_requests.py 2024-06-27 13:00:41.000000000 +0200 @@ -8,7 +8,8 @@ from lxml.etree import XMLSyntaxError from lxml.objectify import ObjectifiedElement -from ..models import IntOrString, ParamsType +from ..models import E, IntOrString, ParamsType +from ..models.request import Action, Review from ..utils.base import ExtensionBase @@ -27,6 +28,41 @@ ) return request_id + # pylint: disable=too-many-arguments + def create(self, actions: typing.Iterable[Action], + reviewers: typing.Optional[typing.Iterable[Review]] = None, + addrevision: bool = False, ignore_delegate: bool = False, + ignore_build_state: bool = False) -> int: + """ + Create a request + + :param actions: Actions to be included in request + :param reviewers: Reviewers to be assigned + :param addrevision: Ask the server to add revisions of the current sources to the request. + :param ignore_delegate: Enforce a new package instance in a project which has + ``OBS:DelegateRequestTarget`` set + :param ignore_build_state: Skip the build state check + :return: Request ID + + .. versionadded:: 0.10.0 + """ + request = E.request(*(action.asxml() for action in actions), + *(review.asxml() for review in reviewers or [])) + + response = self.osc.request( + url=urljoin(self.osc.url, self.base_path), + method="POST", + data=request, + params={ + "cmd": "create", + "addrevision": addrevision, + "ignore_delegate": ignore_delegate, + "ignore_build_state": ignore_build_state + }, + ) + data = self.osc.get_objectified_xml(response) + return int(data.get("id")) + def get_list(self, **params: ParamsType) -> ObjectifiedElement: """ Get a list or request objects diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osctiny/models/__init__.py new/osc-tiny-0.10.0/osctiny/models/__init__.py --- old/osc-tiny-0.9.1/osctiny/models/__init__.py 2024-05-22 10:58:07.000000000 +0200 +++ new/osc-tiny-0.10.0/osctiny/models/__init__.py 2024-06-27 13:00:41.000000000 +0200 @@ -5,8 +5,13 @@ from io import BufferedReader, BytesIO, StringIO import typing -from lxml.objectify import ObjectifiedElement +from lxml.objectify import ElementMaker, ObjectifiedElement +#: XML maker without python type annotations +E = ElementMaker(annotate=False) +#: Acceptable types for HTTP parameters ParamsType = typing.Union[bytes, str, StringIO, BytesIO, BufferedReader, dict, ObjectifiedElement] + +#: Int or string with only numbers IntOrString = typing.Union[int, str] diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osctiny/models/request.py new/osc-tiny-0.10.0/osctiny/models/request.py --- old/osc-tiny-0.9.1/osctiny/models/request.py 1970-01-01 01:00:00.000000000 +0100 +++ new/osc-tiny-0.10.0/osctiny/models/request.py 2024-06-27 13:00:41.000000000 +0200 @@ -0,0 +1,105 @@ +""" +Models for requests +^^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 0.10.0 +""" +# pylint: disable=missing-function-docstring,no-member +# Note: The `no-member` rule needs to be disabled due to this bug: +# https://github.com/pylint-dev/pylint/issues/7891 +import enum +import typing + +from lxml.objectify import ObjectifiedElement + +from . import E + + +class ActionType(enum.Enum): + """ + Request action types as defined in OpenBuildService + """ + ADD_ROLE = "add_role" + CHANGE_DEVEL = "change_devel" + DELETE = "delete" + MAINTENANCE_INCIDENT = "maintenance_incident" + MAINTENANCE_RELEASE = "maintenance_release" + RELEASE = "release" + SET_BUGOWNER = "set_bugowner" + SUBMIT = "submit" + + +class Person(typing.NamedTuple): + """ + Person model for use in actions + """ + name: str + + def asxml(self) -> ObjectifiedElement: + return E.person(name=self.name) + + +class Source(typing.NamedTuple): + """ + Source for an action + """ + project: str + package: str + rev: typing.Optional[str] = None + + def asxml(self) -> ObjectifiedElement: + return E.source(**{field: getattr(self, field) + for field in self._fields + if getattr(self, field, None) is not None}) + + +class Target(typing.NamedTuple): + """ + Target for an action + """ + project: str + package: str + releaseproject: typing.Optional[str] = None + + def asxml(self) -> ObjectifiedElement: + return E.target(**{field: getattr(self, field) + for field in self._fields + if getattr(self, field, None) is not None}) + + +class Action(typing.NamedTuple): + """ + Request action + """ + type: ActionType + target: Target + person: typing.Optional[Person] = None + source: typing.Optional[Source] = None + + def asxml(self) -> ObjectifiedElement: + sub_elems = [self.target.asxml()] + for field in (self.person, self.source): + if field is not None: + sub_elems.append(field.asxml()) + return E.action(*sub_elems, type=self.type.value) + + +class By(enum.Enum): + """ + Types by which reviews can be assigned + """ + USER = "by_user" + GROUP = "by_group" + PROJECT = "by_project" + PACKAGE = "by_package" + + +class Review(typing.NamedTuple): + """ + Assign a review + """ + by: By + name: str + + def asxml(self) -> ObjectifiedElement: + return E.review(**{self.by.value: self.name}) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osctiny/models/staging.py new/osc-tiny-0.10.0/osctiny/models/staging.py --- old/osc-tiny-0.9.1/osctiny/models/staging.py 2024-05-22 10:58:07.000000000 +0200 +++ new/osc-tiny-0.10.0/osctiny/models/staging.py 2024-06-27 13:00:41.000000000 +0200 @@ -1,15 +1,16 @@ """ Models for Staging ^^^^^^^^^^^^^^^^^^ + +.. versionadded:: 0.9.0 """ # pylint: disable=missing-class-docstring,missing-function-docstring import enum import typing -from lxml.objectify import ObjectifiedElement, ElementMaker - +from lxml.objectify import ObjectifiedElement -E = ElementMaker(annotate=False) +from ..models import E class ExcludedRequest(typing.NamedTuple): diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/osctiny/tests/test_requests.py new/osc-tiny-0.10.0/osctiny/tests/test_requests.py --- old/osc-tiny-0.9.1/osctiny/tests/test_requests.py 2024-05-22 10:58:07.000000000 +0200 +++ new/osc-tiny-0.10.0/osctiny/tests/test_requests.py 2024-06-27 13:00:41.000000000 +0200 @@ -1,10 +1,13 @@ # -*- coding: utf-8 -*- import re +from unittest import mock from urllib.parse import urlparse, parse_qs from lxml.objectify import ObjectifiedElement +from requests.models import Response import responses +from ..models.request import Action, ActionType, Person, Review, Source, Target, By from .base import OscTest, CallbackFactory @@ -304,6 +307,9 @@ and "changereviewstate" in params.get("cmd", [])): status = 403 body = "Forbidden for url: http://api.example.com/request/30902" + elif request.method == "POST" and re.search("/request/?\?", request.url): + status = 200 + body = """<request id="42"/>""" else: status = 404 body = """ @@ -343,6 +349,91 @@ ) @responses.activate + def test_create(self): + target = Target(project="Foo:Bar:Factory", package="hello-world") + actions = [ + Action( + type=ActionType.SUBMIT, + source=Source(project="Foo:Bar", package="hello-world"), + target=target + ), + Action( + type=ActionType.DELETE, + target=target + ) + ] + reviewers = [ + Review(by=By.USER, name="nemo"), + Review(by=By.GROUP, name="superusers") + ] + + with self.subTest("HTTP Request, only Actions"): + self.assertEqual(42, self.osc.requests.create(actions=actions)) + + with self.subTest("HTTP Request, with Reviewers"): + self.assertEqual(42, self.osc.requests.create(actions=actions, + reviewers=reviewers)) + + mock_response = Response() + mock_response.status_code = 200 + mock_response._content = b"""<request id="42"/>""" + + with self.subTest("XML content, only Actions"), \ + mock.patch.object(self.osc.session, "send", + return_value=mock_response) as mock_session: + self.osc.requests.create(actions=actions) + self.assertEqual( + mock_session.call_args[0][0].body, + b"<?xml version='1.0' encoding='utf-8'?>\n" + b'<request>' + b'<action type="submit">' + b'<target project="Foo:Bar:Factory" package="hello-world"/>' + b'<source project="Foo:Bar" package="hello-world"/>' + b'</action>' + b'<action type="delete">' + b'<target project="Foo:Bar:Factory" package="hello-world"/>' + b'</action>' + b'</request>' + ) + + with self.subTest("XML content, with Reviewers"), \ + mock.patch.object(self.osc.session, "send", + return_value=mock_response) as mock_session: + self.osc.requests.create(actions=actions, reviewers=reviewers) + self.assertEqual( + mock_session.call_args[0][0].body, + b"<?xml version='1.0' encoding='utf-8'?>\n" + b'<request>' + b'<action type="submit">' + b'<target project="Foo:Bar:Factory" package="hello-world"/>' + b'<source project="Foo:Bar" package="hello-world"/>' + b'</action>' + b'<action type="delete">' + b'<target project="Foo:Bar:Factory" package="hello-world"/>' + b'</action>' + b'<review by_user="nemo"/>' + b'<review by_group="superusers"/>' + b'</request>' + ) + + with self.subTest("XML content, set bugowner"), \ + mock.patch.object(self.osc.session, "send", + return_value=mock_response) as mock_session: + self.osc.requests.create(actions=[Action(type=ActionType.SET_BUGOWNER, + target=Target(project="Foo:Bar", package="p"), + person=Person(name="nemo"))]) + self.assertEqual( + mock_session.call_args[0][0].body, + b"<?xml version='1.0' encoding='utf-8'?>\n" + b'<request>' + b'<action type="set_bugowner">' + b'<target project="Foo:Bar" package="p"/>' + b'<person name="nemo"/>' + b'</action>' + b'</request>' + ) + + @responses.activate def test_get_list(self): response = self.osc.requests.get_list() self.assertEqual(response.tag, "directory") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/osc-tiny-0.9.1/setup.py new/osc-tiny-0.10.0/setup.py --- old/osc-tiny-0.9.1/setup.py 2024-05-22 10:58:07.000000000 +0200 +++ new/osc-tiny-0.10.0/setup.py 2024-06-27 13:00:41.000000000 +0200 @@ -26,7 +26,7 @@ setup( name='osc-tiny', - version='0.9.1', + version='0.10.0', description='Client API for openSUSE BuildService', long_description=long_description, long_description_content_type="text/markdown",