Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package openSUSE-release-tools for openSUSE:Factory checked in at 2023-07-11 15:57:20 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/openSUSE-release-tools (Old) and /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.8922 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "openSUSE-release-tools" Tue Jul 11 15:57:20 2023 rev:481 rq:1098068 version:20230710.395857d Changes: -------- --- /work/SRC/openSUSE:Factory/openSUSE-release-tools/openSUSE-release-tools.changes 2023-07-10 16:40:12.378652145 +0200 +++ /work/SRC/openSUSE:Factory/.openSUSE-release-tools.new.8922/openSUSE-release-tools.changes 2023-07-11 15:57:43.837320517 +0200 @@ -1,0 +2,10 @@ +Mon Jul 10 09:04:07 UTC 2023 - opensuse-releaset...@opensuse.org + +- Update to version 20230710.395857d: + * Remove unused variable + * Convert namedtuples into a typed NamedTuples + * Raise a concrete exception type, not the generic exception base class + * ReviewBot: convert REVIEW_CHOICES into an enum + * Add some type hints + +------------------------------------------------------------------- Old: ---- openSUSE-release-tools-20230710.b1b5536.obscpio New: ---- openSUSE-release-tools-20230710.395857d.obscpio ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ openSUSE-release-tools.spec ++++++ --- /var/tmp/diff_new_pack.6QrM5K/_old 2023-07-11 15:57:44.593324915 +0200 +++ /var/tmp/diff_new_pack.6QrM5K/_new 2023-07-11 15:57:44.597324938 +0200 @@ -20,7 +20,7 @@ %define source_dir openSUSE-release-tools %define announcer_filename factory-package-news Name: openSUSE-release-tools -Version: 20230710.b1b5536 +Version: 20230710.395857d Release: 0 Summary: Tools to aid in staging and release work for openSUSE/SUSE License: GPL-2.0-or-later AND MIT ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.6QrM5K/_old 2023-07-11 15:57:44.645325217 +0200 +++ /var/tmp/diff_new_pack.6QrM5K/_new 2023-07-11 15:57:44.645325217 +0200 @@ -1,7 +1,7 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/openSUSE/openSUSE-release-tools.git</param> - <param name="changesrevision">b1b55368dad500ff183bcb023bf78ac0988aa3f3</param> + <param name="changesrevision">395857d45b31eba373e9b5eb74a16673ae2249b1</param> </service> </servicedata> ++++++ openSUSE-release-tools-20230710.b1b5536.obscpio -> openSUSE-release-tools-20230710.395857d.obscpio ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20230710.b1b5536/ReviewBot.py new/openSUSE-release-tools-20230710.395857d/ReviewBot.py --- old/openSUSE-release-tools-20230710.b1b5536/ReviewBot.py 2023-07-10 09:34:29.000000000 +0200 +++ new/openSUSE-release-tools-20230710.395857d/ReviewBot.py 2023-07-10 11:02:13.000000000 +0200 @@ -1,10 +1,11 @@ #!/usr/bin/python3 +from enum import Enum, unique import os import sys import re import logging -from typing import List +from typing import Generator, List, Optional, Tuple, Union import cmdln from collections import namedtuple from collections import OrderedDict @@ -66,6 +67,16 @@ return None +@unique +class ReviewChoices(Enum): + NORMAL = 'normal' + NO = 'no' + ACCEPT = 'accept' + ACCEPT_ONPASS = 'accept-onpass' + FALLBACK_ONFAIL = 'fallback-onfail' + FALLBACK_ALWAYS = 'fallback-always' + + class ReviewBot(object): """ A generic obs request reviewer @@ -76,7 +87,10 @@ """ DEFAULT_REVIEW_MESSAGES = {'accepted': 'ok', 'declined': 'review failed'} - REVIEW_CHOICES = ('normal', 'no', 'accept', 'accept-onpass', 'fallback-onfail', 'fallback-always') + REVIEW_CHOICES: Tuple[ReviewChoices, ...] = ( + ReviewChoices.NORMAL, ReviewChoices.NO, ReviewChoices.ACCEPT, + ReviewChoices.ACCEPT_ONPASS, ReviewChoices.FALLBACK_ONFAIL, ReviewChoices.FALLBACK_ALWAYS + ) COMMENT_MARKER_REGEX = re.compile(r'<!-- (?P<bot>[^ ]+) state=(?P<state>[^ ]+)(?: result=(?P<result>[^ ]+))? -->') @@ -98,7 +112,7 @@ self.review_group = group self.requests: List[osc.core.Request] = [] self.review_messages = ReviewBot.DEFAULT_REVIEW_MESSAGES - self._review_mode = 'normal' + self._review_mode: ReviewChoices = ReviewChoices.NORMAL self.fallback_user = None self.fallback_group = None self.comment_api = CommentAPI(self.apiurl) @@ -151,13 +165,13 @@ return self.staging_apis[project] @property - def review_mode(self): + def review_mode(self) -> ReviewChoices: return self._review_mode @review_mode.setter - def review_mode(self, value): + def review_mode(self, value: ReviewChoices): if value not in self.REVIEW_CHOICES: - raise Exception("invalid review option: %s" % value) + raise ValueError("invalid review option: %s" % value) self._review_mode = value def set_request_ids(self, ids): @@ -219,7 +233,7 @@ return return_value @memoize(session=True) - def request_override_check_users(self, project): + def request_override_check_users(self, project: str) -> List[str]: """Determine users allowed to override review in a comment command.""" config = Config.get(self.apiurl, project) @@ -235,7 +249,7 @@ return users - def request_override_check(self, force=False): + def request_override_check(self, force: bool = False) -> Optional[bool]: """Check for a comment command requesting review override.""" if not force and not self.override_allow: return None @@ -251,8 +265,8 @@ self.review_messages['declined'] = message return False - def request_commands(self, command, who_allowed=None, request=None, action=None, - include_description=True): + def request_commands(self, command: str, who_allowed=None, request=None, action=None, + include_description=True) -> Generator[Tuple[List[str], Optional[str]], None, None]: if not request: request = self.request if not action: @@ -545,7 +559,7 @@ self.review_messages['accepted'] += ': ' + message return self.request_default_return - def check_source_submission(self, src_project, src_package, src_rev, target_project, target_package): + def check_source_submission(self, src_project: str, src_package: str, src_rev: str, target_project: str, target_package: str) -> None: """ default implemention does nothing """ self.logger.info("%s/%s@%s -> %s/%s" % (src_project, src_package, src_rev, target_project, target_package)) return None @@ -812,7 +826,12 @@ return False - def request_age_wait(self, age_min=None, request=None, target_project=None): + def request_age_wait( + self, + age_min: Optional[Union[str, int, float]] = None, + request=None, + target_project: Optional[str] = None + ) -> bool: if not request: request = self.request diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20230710.b1b5536/check_source.py new/openSUSE-release-tools-20230710.395857d/check_source.py --- old/openSUSE-release-tools-20230710.b1b5536/check_source.py 2023-07-10 09:34:29.000000000 +0200 +++ new/openSUSE-release-tools-20230710.395857d/check_source.py 2023-07-10 11:02:13.000000000 +0200 @@ -9,6 +9,8 @@ import subprocess import sys import tempfile +from typing import Optional, Set +from cmdln import CmdlnOptionParser from lxml import etree as ET @@ -42,12 +44,12 @@ self.skip_add_reviews = False - def target_project_config(self, project): + def target_project_config(self, project: str) -> None: # Load project config and allow for remote entries. config = Config.get(self.apiurl, project) self.single_action_require = str2bool(config.get('check-source-single-action-require', 'False')) - self.ignore_devel = not str2bool(config.get('devel-project-enforce', 'False')) + self.ignore_devel: bool = not str2bool(config.get('devel-project-enforce', 'False')) self.in_air_rename_allow = str2bool(config.get('check-source-in-air-rename-allow', 'False')) self.add_review_team = str2bool(config.get('check-source-add-review-team', 'True')) self.review_team = config.get('review-team') @@ -57,11 +59,11 @@ self.devel_whitelist = config.get('devel-whitelist', '').split() self.skip_add_reviews = False self.ensure_source_exist_in_baseproject = str2bool(config.get('check-source-ensure-source-exist-in-baseproject', 'False')) - self.devel_baseproject = config.get('check-source-devel-baseproject', '') + self.devel_baseproject: str = config.get('check-source-devel-baseproject', '') self.allow_source_in_sle = str2bool(config.get('check-source-allow-source-in-sle', 'True')) self.sle_project_to_check = config.get('check-source-sle-project', '') self.allow_valid_source_origin = str2bool(config.get('check-source-allow-valid-source-origin', 'False')) - self.valid_source_origins = set(config.get('check-source-valid-source-origins', '').split(' ')) + self.valid_source_origins: Set[str] = set(config.get('check-source-valid-source-origins', '').split(' ')) self.add_devel_project_review = str2bool(config.get('check-source-add-devel-project-review', 'False')) self.allowed_scm_submission_sources = config.get('allowed-scm-submission-sources', '').split() @@ -78,7 +80,7 @@ # It might make sense to supersede maintbot, but for now. self.skip_add_reviews = True - def is_good_name(self, package, target_package): + def is_good_name(self, package: Optional[str], target_package: Optional[str]) -> bool: self.logger.debug(f"is_good_name {package} <-> {target_package}") if target_package is None: # if the name doesn't matter, existance is all @@ -114,7 +116,14 @@ return ret - def check_source_submission(self, source_project, source_package, source_revision, target_project, target_package): + def check_source_submission( + self, + source_project: str, + source_package: str, + source_revision: str, + target_project: str, + target_package: str + ) -> bool: super(CheckSource, self).check_source_submission(source_project, source_package, source_revision, target_project, target_package) self.target_project_config(target_project) @@ -762,7 +771,7 @@ ReviewBot.CommandLineInterface.__init__(self, args, kwargs) self.clazz = CheckSource - def get_optparser(self): + def get_optparser(self) -> CmdlnOptionParser: parser = ReviewBot.CommandLineInterface.get_optparser(self) parser.add_option('--skip-add-reviews', action='store_true', default=False, diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20230710.b1b5536/origin-manager.py new/openSUSE-release-tools-20230710.395857d/origin-manager.py --- old/openSUSE-release-tools-20230710.b1b5536/origin-manager.py 2023-07-10 09:34:29.000000000 +0200 +++ new/openSUSE-release-tools-20230710.395857d/origin-manager.py 2023-07-10 11:02:13.000000000 +0200 @@ -1,5 +1,6 @@ #!/usr/bin/python3 +from typing import Literal, Optional, Tuple, Union from osclib.core import devel_project_get from osclib.core import package_source_hash from osclib.core import package_kind @@ -77,7 +78,7 @@ return True - def check_source_submission(self, src_project, src_package, src_rev, tgt_project, tgt_package): + def check_source_submission(self, src_project, src_package, src_rev, tgt_project, tgt_package) -> Optional[bool]: kind = package_kind(self.apiurl, tgt_project, tgt_package) if not (kind is None or kind == 'source'): self.review_messages['accepted'] = 'skipping {} package since not source'.format(kind) @@ -118,7 +119,16 @@ source_hash_new, source_hash_old) return self.policy_result_handle(tgt_project, tgt_package, origin_info_new, origin_info_old, result) - def config_validate(self, target_project): + def config_validate(self, target_project: str) -> Tuple[bool, bool]: + """Check the settings ``OSRT:OriginConfig`` of the target project and + return a tuple of booleans. The first boolean indicates whether the + action should proceed and the second whether the config is valid. + + This function checks whether the value + ``OSRT:OriginConfig.fallback-group`` is present and whether + ``OSRT:OriginConfig.review-user`` matches the configured review_user. + + """ config = config_load(self.apiurl, target_project) if not config: # No perfect solution for lack of a config. For normal projects a @@ -138,7 +148,11 @@ return True, True - def devel_project_simulate_check(self, source_project, target_project): + def devel_project_simulate_check( + self, + source_project: str, + target_project: str + ) -> Union[Tuple[str, str], Tuple[Literal[False], Literal[None]]]: config = config_load(self.apiurl, target_project) origin_list = config_origin_list(config, self.apiurl, target_project, skip_workarounds=True) @@ -157,7 +171,11 @@ return False, None - def devel_project_simulate_check_command(self, source_project, target_project): + def devel_project_simulate_check_command( + self, + source_project: str, + target_project: str + ) -> Union[Tuple[str, Optional[str]], Tuple[Literal[False], Literal[None]]]: who_allowed = self.request_override_check_users(target_project) if self.request.creator not in who_allowed: who_allowed.append(self.request.creator) @@ -168,7 +186,7 @@ return False, None - def policy_result_handle(self, project, package, origin_info_new, origin_info_old, result): + def policy_result_handle(self, project, package, origin_info_new, origin_info_old, result: PolicyResult) -> Optional[bool]: if result.wait and not result.accept: result.comments.append(f'Decision may be overridden via `@{self.review_user} override`.') diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20230710.b1b5536/osclib/comments.py new/openSUSE-release-tools-20230710.395857d/osclib/comments.py --- old/openSUSE-release-tools-20230710.b1b5536/osclib/comments.py 2023-07-10 09:34:29.000000000 +0200 +++ new/openSUSE-release-tools-20230710.395857d/osclib/comments.py 2023-07-10 11:02:13.000000000 +0200 @@ -1,28 +1,49 @@ from datetime import datetime +from typing import TYPE_CHECKING, Any, Dict, Generator, List, Literal, Optional, Tuple, TypedDict, Union from dateutil.parser import parse as date_parse import re -from lxml import etree as ET - -from osc.core import http_DELETE -from osc.core import http_GET -from osc.core import http_POST +if TYPE_CHECKING: + import xml.etree.ElementTree as ET +else: + from lxml import etree as ET + +from osc.connection import http_DELETE +from osc.connection import http_GET +from osc.connection import http_POST from osc.core import makeurl +class _BaseComment(TypedDict): + who: Optional[str] + when: datetime + parent: Optional[Any] + comment: Optional[str] + + +class Comment(_BaseComment): + id: Optional[str] + + +class RequestAsComment(_BaseComment): + id: Literal['-1'] + + class CommentAPI(object): COMMENT_MARKER_REGEX = re.compile(r'<!-- (?P<bot>[^ ]+)(?P<info>(?: [^= ]+=[^ ]+)*) -->') def __init__(self, apiurl): self.apiurl = apiurl - def _prepare_url(self, request_id=None, project_name=None, - package_name=None, query=None): + def _prepare_url(self, request_id=None, project_name: Optional[str] = None, + package_name: Optional[str] = None, + query: Optional[Union[List[str], Dict[str, str]]] = None + ) -> str: """Prepare the URL to get/put comments in OBS. :param request_id: Request where to refer the comment. :param project_name: Project name where to refer comment. :param package_name: Package name where to refer the comment. - :returns: Formated URL for the request. + :returns: Formatted URL for the request. """ url = None if request_id: @@ -36,21 +57,20 @@ raise ValueError('Please, set request_id, project_name or / and package_name to add a comment.') return url - def _comment_as_dict(self, comment_element): + def _comment_as_dict(self, comment_element: ET.Element) -> Comment: """Convert an XML element comment into a dictionary. :param comment_element: XML element that store a comment. :returns: A Python dictionary object. """ - comment = { + return { 'who': comment_element.get('who'), - 'when': datetime.strptime(comment_element.get('when'), '%Y-%m-%d %H:%M:%S %Z'), + 'when': datetime.strptime(comment_element.get('when', ''), '%Y-%m-%d %H:%M:%S %Z'), 'id': comment_element.get('id'), 'parent': comment_element.get('parent', None), 'comment': comment_element.text, } - return comment - def request_as_comment_dict(self, request): + def request_as_comment_dict(self, request) -> RequestAsComment: return { 'who': request.creator, 'when': date_parse(request.statehistory[0].when), @@ -60,7 +80,7 @@ } def get_comments(self, request_id=None, project_name=None, - package_name=None): + package_name=None) -> Dict[str, Comment]: """Get the list of comments of an object in OBS. :param request_id: Request where to get comments. @@ -106,7 +126,13 @@ return c, info return None, None - def command_find(self, comments, user, command=None, who_allowed=None): + def command_find( + self, + comments: Dict[str, Comment], + user: str, + command: Optional[str] = None, + who_allowed=None + ) -> Generator[Tuple[List[str], Optional[str]], None, None]: """ Find comment commands with the optional conditions. diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20230710.b1b5536/osclib/conf.py new/openSUSE-release-tools-20230710.395857d/osclib/conf.py --- old/openSUSE-release-tools-20230710.b1b5536/osclib/conf.py 2023-07-10 09:34:29.000000000 +0200 +++ new/openSUSE-release-tools-20230710.395857d/osclib/conf.py 2023-07-10 11:02:13.000000000 +0200 @@ -1,4 +1,4 @@ -from __future__ import print_function +from typing import Dict, Optional from osc import OscConfigParser from collections import OrderedDict @@ -172,14 +172,17 @@ # -def str2bool(v): +def str2bool(v: Optional[str]) -> bool: return (v is not None and v.lower() in ("yes", "true", "t", "1")) class Config(object): - """Helper class to configuration file.""" + """Helper class for reading the osc configuration file and fetching the + remote config from the ``OSRT:config`` attribute in the target project. - def __init__(self, apiurl, project): + """ + + def __init__(self, apiurl: str, project: str) -> None: self.project = project self.remote_values = self.fetch_remote(apiurl) @@ -191,7 +194,7 @@ @staticmethod @memoize(session=True) # Allow reset by memoize_session_reset() for ReviewBot. - def get(apiurl, project): + def get(apiurl: str, project: str): """Cached version for directly accessing project config.""" # Properly handle loading the config for interconnect projects. from osclib.core import project_remote_apiurl @@ -204,7 +207,7 @@ def conf(self): return conf - def populate_conf(self): + def populate_conf(self) -> None: """Add sane default into the configuration and layer (defaults, remote, ~/.oscrc).""" defaults = {} default_ordered = OrderedDict(sorted(DEFAULT.items(), key=lambda i: int(i[1].get('_priority', 99)))) @@ -244,12 +247,16 @@ else: return defaults - def fetch_remote(self, apiurl): + def fetch_remote(self, apiurl: str) -> Optional[Dict[str, str]]: + """Fetch the configuration from the ``OSRT`` attribute namespace for the + current project from the OBS instance with the given apiurl. + + """ from osclib.core import attribute_value_load config = attribute_value_load(apiurl, self.project, 'Config') if config: cp = OscConfigParser.OscConfigParser() - config = u'[remote]\n' + config + config = u'[remote]\n' + str(config) cp.read_string(config) return dict(cp.items('remote')) diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20230710.b1b5536/osclib/core.py new/openSUSE-release-tools-20230710.395857d/osclib/core.py --- old/openSUSE-release-tools-20230710.b1b5536/osclib/core.py 2023-07-10 09:34:29.000000000 +0200 +++ new/openSUSE-release-tools-20230710.395857d/osclib/core.py 2023-07-10 11:02:13.000000000 +0200 @@ -4,18 +4,18 @@ import re import socket import logging +from typing import List, Literal, Optional, Tuple, Union from lxml import etree as ET from urllib.error import HTTPError -from typing import Optional -from osc.core import create_submit_request +from osc.core import ReviewState, create_submit_request from osc.core import get_binarylist from osc.core import get_commitlog from osc.core import get_dependson -from osc.core import http_DELETE -from osc.core import http_GET -from osc.core import http_POST -from osc.core import http_PUT +from osc.connection import http_DELETE +from osc.connection import http_GET +from osc.connection import http_POST +from osc.connection import http_PUT from osc.core import makeurl from osc.core import owner from osc.core import search as osc_core_search @@ -279,7 +279,13 @@ @memoize(session=True) -def devel_project_get(apiurl: str, target_project: str, target_package: str): +def devel_project_get(apiurl: str, target_project: str, target_package: str) -> Union[Tuple[str, str], Tuple[Literal[None], Literal[None]]]: + """Fetch the devel project & package name of the supplied ``target_project`` + and ``target_package`` and return the devel project and devel package name + as a tuple. If no devel project has been defined, then return ``None, + None``. + + """ try: meta = ET.fromstringlist(show_package_meta(apiurl, target_project, target_package)) node = meta.find('devel') @@ -468,7 +474,22 @@ yield package -def attribute_value_load(apiurl: str, project: str, name: str, namespace='OSRT', package: Optional[str] = None): +def attribute_value_load( + apiurl: str, + project: str, + name: str, + namespace: str = 'OSRT', + package: Optional[str] = None) -> Optional[Union[str, Literal[True]]]: + """Fetch an attribute from the OBS instance with the given ``apiurl`` for + the given ``project`` (and optionally for a package if a package name is + provided). The attribute is expected to have the name ``$namespace:$name``, + with the namespace defaulting to ``OSRT``. + + Attributes are stored as xml and can be either strings or are interpreted as + ``True`` if only the element is present but has no other children or + entries. + + """ path = list(filter(None, ['source', project, package, '_attribute', namespace + ':' + name])) url = makeurl(apiurl, path) @@ -506,7 +527,7 @@ value: str, namespace='OSRT', package: Optional[str] = None -): +) -> None: root = ET.Element('attributes') attribute = ET.SubElement(root, 'attribute') @@ -525,7 +546,7 @@ raise e -def attribute_value_delete(apiurl: str, project: str, name: str, namespace='OSRT', package: Optional[str] = None): +def attribute_value_delete(apiurl: str, project: str, name: str, namespace='OSRT', package: Optional[str] = None) -> None: http_DELETE(makeurl( apiurl, list(filter(None, ['source', project, package, '_attribute', namespace + ':' + name])))) @@ -661,7 +682,12 @@ return datetime.utcnow() - package_source_changed(apiurl, project, package) -def entity_exists(apiurl, project, package=None): +def entity_exists(apiurl: str, project: str, package: Optional[str] = None) -> bool: + """Check whether the project or the project/package exists on the OBS + instance behind the supplied apiurl and return True if it exists or False + otherwise. + + """ try: http_GET(makeurl(apiurl, list(filter(None, ['source', project, package])) + ['_meta'])) except HTTPError as e: @@ -673,7 +699,9 @@ return True -def package_kind(apiurl, project, package): +def package_kind( + apiurl, project, package +) -> Optional[Literal['meta', 'multibuild_subpackage', 'patchinfo', 'maintenance_update', 'multispec_subpackage', 'source']]: if package.startswith('00') or package.startswith('_'): return 'meta' @@ -900,7 +928,7 @@ return None -def reviews_remaining(request, incident_psuedo=False): +def reviews_remaining(request: Request, incident_psuedo=False) -> List[ReviewState]: reviews = [] for review in request.reviews: if review.state != 'accepted': @@ -939,7 +967,7 @@ return trackers -def issue_tracker_by_url(apiurl, tracker_url): +def issue_tracker_by_url(apiurl: str, tracker_url: str) -> Optional[str]: url = makeurl(apiurl, ['issue_trackers']) root = ET.parse(http_GET(url)).getroot() if not tracker_url.endswith('/'): @@ -952,7 +980,7 @@ return tracker.find('label').text.replace('@@@', identifier) -def request_remote_identifier(apiurl, apiurl_remote, request_id): +def request_remote_identifier(apiurl: str, apiurl_remote: str, request_id: str) -> str: if apiurl_remote == apiurl: return 'request#{}'.format(request_id) @@ -1007,7 +1035,7 @@ action.src_package == 'patchinfo' or action.src_package.startswith('patchinfo.'))) -def request_action_key(action): +def request_action_key(action: Action) -> str: identifier = [] if action.type in ['add_role', 'change_devel', 'maintenance_release', 'set_bugowner', 'submit']: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/openSUSE-release-tools-20230710.b1b5536/osclib/origin.py new/openSUSE-release-tools-20230710.395857d/osclib/origin.py --- old/openSUSE-release-tools-20230710.b1b5536/osclib/origin.py 2023-07-10 09:34:29.000000000 +0200 +++ new/openSUSE-release-tools-20230710.395857d/osclib/origin.py 2023-07-10 11:02:13.000000000 +0200 @@ -1,6 +1,8 @@ from copy import deepcopy -from collections import namedtuple import logging +from typing import Any, Dict, Generator, List, Literal, NamedTuple, Optional, Tuple, TypedDict, Union + +from osc.core import ReviewState from osclib.conf import Config from osclib.conf import str2bool from osclib.core import attribute_value_load @@ -38,7 +40,26 @@ 'fallback-group': '<config:origin-manager-fallback-group>', 'fallback-workaround': {}, } -POLICY_DEFAULTS = { + + +class Policy(TypedDict): + additional_reviews: List[Any] + automatic_updates: bool + automatic_updates_initial: bool + automatic_updates_supersede: bool + automatic_updates_delay: int + automatic_updates_frequency: int + maintainer_review_always: bool + maintainer_review_initial: bool + pending_submission_allow: bool + pending_submission_consider: bool + pending_submission_allowed_reviews: List[str] + # Submit pending requests with a set of allowed reviews, but still wait for + # the above reviews before being accepted. + pending_submission_allowed_reviews_update: List[str] + + +POLICY_DEFAULTS: Policy = { 'additional_reviews': [], 'automatic_updates': True, 'automatic_updates_initial': False, @@ -63,9 +84,22 @@ ], } -OriginInfo = namedtuple('OriginInfo', ['project', 'pending']) -PendingRequestInfo = namedtuple('PendingRequestInfo', ['identifier', 'reviews_remaining']) -PolicyResult = namedtuple('PolicyResult', ['wait', 'accept', 'reviews', 'comments']) + +class PendingRequestInfo(NamedTuple): + identifier: str + reviews_remaining: List[ReviewState] + + +class OriginInfo(NamedTuple): + project: str + pending: PendingRequestInfo + + +class PolicyResult(NamedTuple): + wait: bool + accept: bool + reviews: Dict[str, str] + comments: List[str] def origin_info_str(self): @@ -84,7 +118,13 @@ return config_resolve(apiurl, project, yaml.safe_load(config)) -def config_origin_generator(origins, apiurl=None, project=None, package=None, skip_workarounds=False): +def config_origin_generator( + origins, + apiurl: Optional[str] = None, + project: Optional[str] = None, + package: Optional[str] = None, + skip_workarounds=False +) -> Generator[Tuple[str, Any], None, None]: for origin_item in origins: for origin, values in origin_item.items(): is_workaround = origin_workaround_check(origin) @@ -92,7 +132,7 @@ break if (origin == '<devel>' or origin == '<devel>~') and apiurl and project and package: - devel_project, devel_package = origin_devel_project(apiurl, project, package) + devel_project, _ = origin_devel_project(apiurl, project, package) if not devel_project: break origin = devel_project @@ -103,7 +143,7 @@ break # Only support single value inside list item. -def config_resolve(apiurl, project, config): +def config_resolve(apiurl: str, project: str, config): defaults = POLICY_DEFAULTS.copy() defaults_workarounds = POLICY_DEFAULTS.copy() @@ -222,17 +262,17 @@ values.update(values_apply) -def origin_workaround_check(origin): +def origin_workaround_check(origin: str) -> bool: return origin.endswith('~') -def origin_workaround_ensure(origin): +def origin_workaround_ensure(origin: str) -> str: if not origin_workaround_check(origin): return origin + '~' return origin -def origin_workaround_strip(origin): +def origin_workaround_strip(origin: str) -> str: if origin_workaround_check(origin): return origin[:-1] return origin @@ -240,7 +280,7 @@ @memoize(session=True) def origin_find(apiurl, target_project, package, source_hash=None, current=False, - pending_allow=True, fallback=True): + pending_allow=True, fallback=True) -> Optional[OriginInfo]: config = config_load(apiurl, target_project) if not source_hash: @@ -290,7 +330,7 @@ return False -def project_source_pending(apiurl, project, package, source_hash): +def project_source_pending(apiurl, project, package, source_hash) -> Union[PendingRequestInfo, Literal[False]]: apiurl_remote, project_remote = project_remote_apiurl(apiurl, project) request_actions = request_action_list_source(apiurl_remote, project_remote, package, states=['new', 'review'], include_release=True) @@ -409,7 +449,7 @@ def policy_evaluate(apiurl, project, package, origin_info_new, origin_info_old, - source_hash_new, source_hash_old): + source_hash_new, source_hash_old) -> PolicyResult: if origin_info_new is None: config = config_load(apiurl, project) origins = config_origin_list(config, apiurl, project, package, True) @@ -510,7 +550,7 @@ return inputs -def policy_input_evaluate(policy, inputs): +def policy_input_evaluate(policy, inputs) -> PolicyResult: result = PolicyResult(False, True, {}, []) if inputs['new_package']: ++++++ openSUSE-release-tools.obsinfo ++++++ --- /var/tmp/diff_new_pack.6QrM5K/_old 2023-07-11 15:57:45.449329894 +0200 +++ /var/tmp/diff_new_pack.6QrM5K/_new 2023-07-11 15:57:45.453329918 +0200 @@ -1,5 +1,5 @@ name: openSUSE-release-tools -version: 20230710.b1b5536 -mtime: 1688974469 -commit: b1b55368dad500ff183bcb023bf78ac0988aa3f3 +version: 20230710.395857d +mtime: 1688979733 +commit: 395857d45b31eba373e9b5eb74a16673ae2249b1