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
 

Reply via email to