1
0
mirror of https://github.com/openSUSE/osc.git synced 2024-12-24 00:56:15 +01:00

Merge pull request #1262 from dmach/improve-requests

Improve handling requests in the python API
This commit is contained in:
Daniel Mach 2023-02-14 20:27:53 +01:00 committed by GitHub
commit b0078c5d2e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
5 changed files with 154 additions and 4 deletions

View File

@ -13,4 +13,5 @@ from .api_source import release
from .common import print_msg
from .common import format_msg_project_package_options
from .package import ApiPackage
from .package import LocalPackage
from .request import forward_request

View File

@ -1,5 +1,6 @@
import functools
from .. import oscerr
from . import api
@ -18,6 +19,7 @@ class PackageBase:
self.files = []
directory_node = self._get_directory_node()
self._load_from_directory_node(directory_node)
self._meta_node = None
def __str__(self):
return f"{self.project}/{self.name}"
@ -60,6 +62,19 @@ class PackageBase:
# the name is omitted and we want it present for overall sanity
self.linkinfo.package = self.name
def _get_meta_node(self):
raise NotImplementedError()
def get_meta_value(self, option):
if not self._meta_node:
self._meta_node = self._get_meta_node()
if not self._meta_node:
return None
node = api.find_node(self._meta_node, "package", option)
if node is None or not node.text:
raise oscerr.APIError(f"Couldn't get '{option}' from package _meta")
return node.text
class ApiPackage(PackageBase):
def __init__(self, apiurl, project, package, rev=None):
@ -75,6 +90,11 @@ class ApiPackage(PackageBase):
url_query["rev"] = self.__rev
return api.get(self.apiurl, url_path, url_query)
def _get_meta_node(self):
url_path = ["source", self.project, self.name, "_meta"]
url_query = {}
return api.get(self.apiurl, url_path, url_query)
class LocalPackage(PackageBase):
def __init__(self, path):
@ -86,3 +106,6 @@ class LocalPackage(PackageBase):
def _get_directory_node(self):
return self.store.read_xml_node("_files", "directory").getroot()
def _get_meta_node(self):
return self.store._meta_node

View File

@ -2749,6 +2749,23 @@ class ReviewState(AbstractState):
review_node.find('comment').text:
self.comment = review_node.find('comment').text.strip()
def __repr__(self):
result = super().__repr__()
result += "("
result += f"{self.state}"
if self.who:
result += f" by {self.who}"
for by in ("user", "group", "project", "package"):
by_value = getattr(self, f"by_{by}", None)
if by_value:
result += f" [{by} {by_value}])"
break
result += ")"
return result
def get_node_attrs(self):
return ('state', 'by_user', 'by_group', 'by_project', 'by_package', 'who', 'when')
@ -2879,6 +2896,9 @@ class Action:
prefix_to_elm = {'src': 'source', 'tgt': 'target', 'opt': 'options'}
def __init__(self, type, **kwargs):
self.apiurl = kwargs.pop("apiurl", None)
self._src_pkg_object = None
self._tgt_pkg_object = None
if type not in Action.type_args.keys():
raise oscerr.WrongArgs('invalid action type: \'%s\'' % type)
self.type = type
@ -2889,6 +2909,60 @@ class Action:
for i in Action.type_args[type]:
setattr(self, i, kwargs.get(i))
def __repr__(self):
result = super().__repr__()
result += "("
result += f"type={self.type}"
src_pkg = self.src_pkg_object
if src_pkg:
result += f" source={src_pkg.project}/{src_pkg.name}"
elif getattr(self, "src_project", None):
result += f" source={self.src_project}"
tgt_pkg = self.tgt_pkg_object
if tgt_pkg:
result += f" target={tgt_pkg.project}/{tgt_pkg.name}"
elif getattr(self, "tgt_project", None):
result += f" target={self.tgt_project}"
result += ")"
return result
@property
def src_pkg_object(self):
if not getattr(self, "src_project", None) or not getattr(self, "src_package", None):
return None
if not self._src_pkg_object:
src_rev = getattr(self, "src_rev", None)
self._src_pkg_object = _private.ApiPackage(self.apiurl, self.src_project, self.src_package, src_rev)
return self._src_pkg_object
@property
def tgt_pkg_object(self):
if not self._tgt_pkg_object:
if self.type == "maintenance_incident":
# the target project for maintenance incidents is virtual and cannot be queried
# the actual target project is in the "releaseproject" attribute
#
# tgt_releaseproject is always set for a maintenance_incident
# pylint: disable=no-member
tgt_project = self.tgt_releaseproject
# the target package is not specified
# we need to extract it from source package's _meta
src_package_meta_releasename = self.src_pkg_object.get_meta_value("releasename")
tgt_package = src_package_meta_releasename.split(".")[0]
else:
if not getattr(self, "tgt_project", None) or not getattr(self, "tgt_package", None):
return None
# tgt_project and tgt_package are checked above
# pylint: disable=no-member
tgt_project = self.tgt_project
tgt_package = self.tgt_package
self._tgt_pkg_object = _private.ApiPackage(self.apiurl, tgt_project, tgt_package)
return self._tgt_pkg_object
def to_xml(self):
"""
Serialize object to XML.
@ -2935,7 +3009,7 @@ class Action:
return ET.tostring(root, encoding=ET_ENCODING)
@staticmethod
def from_xml(action_node):
def from_xml(action_node, apiurl=None):
"""create action from XML"""
if action_node is None or \
action_node.get('type') not in Action.type_args.keys() or \
@ -2960,6 +3034,7 @@ class Action:
l.append(v)
else:
kwargs[k] = v
kwargs["apiurl"] = apiurl
return Action(action_node.get('type'), **kwargs)
@ -2967,6 +3042,12 @@ class Action:
class Request:
"""Represents a request (``<request />``)"""
@classmethod
def from_api(cls, apiurl: str, req_id: int):
# TODO: deprecate get_request() or move its content here
req_id = str(req_id)
return get_request(apiurl, req_id)
def __init__(self):
self._init_attributes()
@ -2982,6 +3063,7 @@ class Request:
self.actions = []
self.statehistory = []
self.reviews = []
self._issues = None
def __eq__(self, other):
return int(self.reqid) == int(other.reqid)
@ -2989,9 +3071,20 @@ class Request:
def __lt__(self, other):
return int(self.reqid) < int(other.reqid)
def read(self, root):
@property
def id(self):
return self.reqid
@property
def issues(self):
if self._issues is None:
self._issues = get_request_issues(self.apiurl, self.id)
return self._issues
def read(self, root, apiurl=None):
"""read in a request"""
self._init_attributes()
self.apiurl = apiurl
if not root.get('id'):
raise oscerr.APIError('invalid request: %s\n' % ET.tostring(root, encoding=ET_ENCODING))
self.reqid = root.get('id')
@ -3008,7 +3101,7 @@ class Request:
i.set('type', 'submit')
action_nodes.append(i)
for action in action_nodes:
self.actions.append(Action.from_xml(action))
self.actions.append(Action.from_xml(action, self.apiurl))
for review in root.findall('review'):
self.reviews.append(ReviewState(review))
for history_element in root.findall('history'):
@ -4500,7 +4593,7 @@ def get_request(apiurl: str, reqid):
root = ET.parse(f).getroot()
r = Request()
r.read(root)
r.read(root, apiurl=apiurl)
return r

View File

@ -299,3 +299,13 @@ class Store:
if len(value) != 3:
raise ValueError("A list with exactly 3 items is expected: [repo, arch, vm_type]")
self.write_list("_last_buildroot", value)
@property
def _meta_node(self):
if not self.exists("_meta"):
return None
if self.is_package:
root = self.read_xml_node("_meta", "package").getroot()
else:
root = self.read_xml_node("_meta", "project").getroot()
return root

View File

@ -203,6 +203,29 @@ class TestStore(unittest.TestCase):
store2 = Store(self.tmpdir)
self.assertEqual(store2.last_buildroot, ["repo", "arch", "vm_type"])
def test_meta_node(self):
self.store.write_string(
"_meta",
"""<package name="test-pkgA" project="projectA">
<title>title</title>
<description>desc</description>
<releasename>name</releasename>
<build>
<enable repository="repo1"/>
<enable repository="repo2"/>
</build>
</package>""",
)
node = self.store._meta_node
self.assertNotEqual(node, None)
# try to read the _meta via a package class
from osc._private import LocalPackage
self.store.files = []
pkg = LocalPackage(self.tmpdir)
self.assertEqual(pkg.get_meta_value("releasename"), "name")
if __name__ == "__main__":
unittest.main()