mirror of
https://github.com/openSUSE/osc.git
synced 2025-01-11 16:36:14 +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 print_msg
|
||||||
from .common import format_msg_project_package_options
|
from .common import format_msg_project_package_options
|
||||||
from .package import ApiPackage
|
from .package import ApiPackage
|
||||||
|
from .package import LocalPackage
|
||||||
from .request import forward_request
|
from .request import forward_request
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
import functools
|
import functools
|
||||||
|
|
||||||
|
from .. import oscerr
|
||||||
from . import api
|
from . import api
|
||||||
|
|
||||||
|
|
||||||
@ -18,6 +19,7 @@ class PackageBase:
|
|||||||
self.files = []
|
self.files = []
|
||||||
directory_node = self._get_directory_node()
|
directory_node = self._get_directory_node()
|
||||||
self._load_from_directory_node(directory_node)
|
self._load_from_directory_node(directory_node)
|
||||||
|
self._meta_node = None
|
||||||
|
|
||||||
def __str__(self):
|
def __str__(self):
|
||||||
return f"{self.project}/{self.name}"
|
return f"{self.project}/{self.name}"
|
||||||
@ -60,6 +62,19 @@ class PackageBase:
|
|||||||
# the name is omitted and we want it present for overall sanity
|
# the name is omitted and we want it present for overall sanity
|
||||||
self.linkinfo.package = self.name
|
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):
|
class ApiPackage(PackageBase):
|
||||||
def __init__(self, apiurl, project, package, rev=None):
|
def __init__(self, apiurl, project, package, rev=None):
|
||||||
@ -75,6 +90,11 @@ class ApiPackage(PackageBase):
|
|||||||
url_query["rev"] = self.__rev
|
url_query["rev"] = self.__rev
|
||||||
return api.get(self.apiurl, url_path, url_query)
|
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):
|
class LocalPackage(PackageBase):
|
||||||
def __init__(self, path):
|
def __init__(self, path):
|
||||||
@ -86,3 +106,6 @@ class LocalPackage(PackageBase):
|
|||||||
|
|
||||||
def _get_directory_node(self):
|
def _get_directory_node(self):
|
||||||
return self.store.read_xml_node("_files", "directory").getroot()
|
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:
|
review_node.find('comment').text:
|
||||||
self.comment = review_node.find('comment').text.strip()
|
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):
|
def get_node_attrs(self):
|
||||||
return ('state', 'by_user', 'by_group', 'by_project', 'by_package', 'who', 'when')
|
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'}
|
prefix_to_elm = {'src': 'source', 'tgt': 'target', 'opt': 'options'}
|
||||||
|
|
||||||
def __init__(self, type, **kwargs):
|
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():
|
if type not in Action.type_args.keys():
|
||||||
raise oscerr.WrongArgs('invalid action type: \'%s\'' % type)
|
raise oscerr.WrongArgs('invalid action type: \'%s\'' % type)
|
||||||
self.type = type
|
self.type = type
|
||||||
@ -2889,6 +2909,60 @@ class Action:
|
|||||||
for i in Action.type_args[type]:
|
for i in Action.type_args[type]:
|
||||||
setattr(self, i, kwargs.get(i))
|
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):
|
def to_xml(self):
|
||||||
"""
|
"""
|
||||||
Serialize object to XML.
|
Serialize object to XML.
|
||||||
@ -2935,7 +3009,7 @@ class Action:
|
|||||||
return ET.tostring(root, encoding=ET_ENCODING)
|
return ET.tostring(root, encoding=ET_ENCODING)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def from_xml(action_node):
|
def from_xml(action_node, apiurl=None):
|
||||||
"""create action from XML"""
|
"""create action from XML"""
|
||||||
if action_node is None or \
|
if action_node is None or \
|
||||||
action_node.get('type') not in Action.type_args.keys() or \
|
action_node.get('type') not in Action.type_args.keys() or \
|
||||||
@ -2960,6 +3034,7 @@ class Action:
|
|||||||
l.append(v)
|
l.append(v)
|
||||||
else:
|
else:
|
||||||
kwargs[k] = v
|
kwargs[k] = v
|
||||||
|
kwargs["apiurl"] = apiurl
|
||||||
return Action(action_node.get('type'), **kwargs)
|
return Action(action_node.get('type'), **kwargs)
|
||||||
|
|
||||||
|
|
||||||
@ -2967,6 +3042,12 @@ class Action:
|
|||||||
class Request:
|
class Request:
|
||||||
"""Represents a request (``<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):
|
def __init__(self):
|
||||||
self._init_attributes()
|
self._init_attributes()
|
||||||
|
|
||||||
@ -2982,6 +3063,7 @@ class Request:
|
|||||||
self.actions = []
|
self.actions = []
|
||||||
self.statehistory = []
|
self.statehistory = []
|
||||||
self.reviews = []
|
self.reviews = []
|
||||||
|
self._issues = None
|
||||||
|
|
||||||
def __eq__(self, other):
|
def __eq__(self, other):
|
||||||
return int(self.reqid) == int(other.reqid)
|
return int(self.reqid) == int(other.reqid)
|
||||||
@ -2989,9 +3071,20 @@ class Request:
|
|||||||
def __lt__(self, other):
|
def __lt__(self, other):
|
||||||
return int(self.reqid) < int(other.reqid)
|
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"""
|
"""read in a request"""
|
||||||
self._init_attributes()
|
self._init_attributes()
|
||||||
|
self.apiurl = apiurl
|
||||||
if not root.get('id'):
|
if not root.get('id'):
|
||||||
raise oscerr.APIError('invalid request: %s\n' % ET.tostring(root, encoding=ET_ENCODING))
|
raise oscerr.APIError('invalid request: %s\n' % ET.tostring(root, encoding=ET_ENCODING))
|
||||||
self.reqid = root.get('id')
|
self.reqid = root.get('id')
|
||||||
@ -3008,7 +3101,7 @@ class Request:
|
|||||||
i.set('type', 'submit')
|
i.set('type', 'submit')
|
||||||
action_nodes.append(i)
|
action_nodes.append(i)
|
||||||
for action in action_nodes:
|
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'):
|
for review in root.findall('review'):
|
||||||
self.reviews.append(ReviewState(review))
|
self.reviews.append(ReviewState(review))
|
||||||
for history_element in root.findall('history'):
|
for history_element in root.findall('history'):
|
||||||
@ -4500,7 +4593,7 @@ def get_request(apiurl: str, reqid):
|
|||||||
root = ET.parse(f).getroot()
|
root = ET.parse(f).getroot()
|
||||||
|
|
||||||
r = Request()
|
r = Request()
|
||||||
r.read(root)
|
r.read(root, apiurl=apiurl)
|
||||||
return r
|
return r
|
||||||
|
|
||||||
|
|
||||||
|
10
osc/store.py
10
osc/store.py
@ -299,3 +299,13 @@ class Store:
|
|||||||
if len(value) != 3:
|
if len(value) != 3:
|
||||||
raise ValueError("A list with exactly 3 items is expected: [repo, arch, vm_type]")
|
raise ValueError("A list with exactly 3 items is expected: [repo, arch, vm_type]")
|
||||||
self.write_list("_last_buildroot", value)
|
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)
|
store2 = Store(self.tmpdir)
|
||||||
self.assertEqual(store2.last_buildroot, ["repo", "arch", "vm_type"])
|
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__":
|
if __name__ == "__main__":
|
||||||
unittest.main()
|
unittest.main()
|
||||||
|
Loading…
Reference in New Issue
Block a user