mirror of
https://github.com/openSUSE/osc.git
synced 2024-12-24 17:16:12 +01:00
Merge pull request #1262 from dmach/improve-requests
Improve handling requests in the python API
This commit is contained in:
commit
b0078c5d2e
@ -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
|
||||
|
@ -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
|
||||
|
101
osc/core.py
101
osc/core.py
@ -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
|
||||
|
||||
|
||||
|
10
osc/store.py
10
osc/store.py
@ -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
|
||||
|
@ -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()
|
||||
|
Loading…
Reference in New Issue
Block a user