Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-cyclopts for openSUSE:Factory 
checked in at 2026-07-26 11:30:03
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cyclopts (Old)
 and      /work/SRC/openSUSE:Factory/.python-cyclopts.new.2004 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-cyclopts"

Sun Jul 26 11:30:03 2026 rev:7 rq:1367772 version:4.22.2

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-cyclopts/python-cyclopts.changes  
2026-07-22 19:08:37.732299208 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-cyclopts.new.2004/python-cyclopts.changes    
    2026-07-26 11:32:40.171384865 +0200
@@ -1,0 +2,11 @@
+Sat Jul 25 19:01:36 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to version 4.22.2:
+  * Fix spurious MissingArgumentError for explicitly-supplied
+    empty mappings
+  * Honor an attached =value on Parameter.count=True flags
+    instead of silently dropping it
+  * Report a clean error when a config node expected to be a
+    table is not one
+
+-------------------------------------------------------------------

Old:
----
  cyclopts-4.22.1.tar.gz

New:
----
  cyclopts-4.22.2.tar.gz

++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Other differences:
------------------
++++++ python-cyclopts.spec ++++++
--- /var/tmp/diff_new_pack.Imcunn/_old  2026-07-26 11:32:40.743404573 +0200
+++ /var/tmp/diff_new_pack.Imcunn/_new  2026-07-26 11:32:40.747404711 +0200
@@ -17,7 +17,7 @@
 
 
 Name:           python-cyclopts
-Version:        4.22.1
+Version:        4.22.2
 Release:        0
 Summary:        Intuitive, easy CLIs based on python type hints
 License:        Apache-2.0

++++++ cyclopts-4.22.1.tar.gz -> cyclopts-4.22.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.22.1/PKG-INFO new/cyclopts-4.22.2/PKG-INFO
--- old/cyclopts-4.22.1/PKG-INFO        2020-02-02 01:00:00.000000000 +0100
+++ new/cyclopts-4.22.2/PKG-INFO        2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: cyclopts
-Version: 4.22.1
+Version: 4.22.2
 Summary: Intuitive, easy CLIs based on type hints.
 Project-URL: Homepage, https://github.com/BrianPugh/cyclopts
 Project-URL: Repository, https://github.com/BrianPugh/cyclopts
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.22.1/cyclopts/_version.py 
new/cyclopts-4.22.2/cyclopts/_version.py
--- old/cyclopts-4.22.1/cyclopts/_version.py    2020-02-02 01:00:00.000000000 
+0100
+++ new/cyclopts-4.22.2/cyclopts/_version.py    2020-02-02 01:00:00.000000000 
+0100
@@ -18,7 +18,7 @@
 commit_id: str | None
 __commit_id__: str | None
 
-__version__ = version = '4.22.1'
-__version_tuple__ = version_tuple = (4, 22, 1)
+__version__ = version = '4.22.2'
+__version_tuple__ = version_tuple = (4, 22, 2)
 
 __commit_id__ = commit_id = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.22.1/cyclopts/argument/_argument.py 
new/cyclopts-4.22.2/cyclopts/argument/_argument.py
--- old/cyclopts-4.22.1/cyclopts/argument/_argument.py  2020-02-02 
01:00:00.000000000 +0100
+++ new/cyclopts-4.22.2/cyclopts/argument/_argument.py  2020-02-02 
01:00:00.000000000 +0100
@@ -626,6 +626,17 @@
         if not self.parse:
             raise ValueError
 
+        if self.parameter.count and self.tokens:
+            # An explicit ``=value`` (marked by a non-empty ``value``) sets 
the count
+            # and must be the flag's only occurrence; plain repeats may still 
sum.
+            explicit = next((x for x in (token, *self.tokens) if x.value and 
x.implicit_value is not UNSET), None)
+            if explicit is not None:
+                raise RepeatArgumentError(
+                    token=token,
+                    msg=f'"{explicit.keyword}={explicit.value}" sets the count 
explicitly '
+                    "and cannot be combined with other occurrences of the 
flag.",
+                )
+
         if any(x.address == token.address for x in self.tokens):
             if self.parameter.allow_repeating is False:
                 raise RepeatArgumentError(token=token)
@@ -775,6 +786,9 @@
         else:
             data = {}
             out = UNSET
+            # An explicitly-supplied empty mapping (e.g. ``x = {}`` in a 
config file, or
+            # ``--x={}`` on the cli) means "instantiate from defaults", not 
"missing".
+            explicit_empty_mapping = False
 
             if self._enum_flag_type:
                 out = self._enum_flag_type(0)
@@ -784,26 +798,36 @@
                 out |= reduce(operator.or_, converted_flags) if 
isinstance(converted_flags, list) else converted_flags
 
             if self._should_attempt_json_dict():
-                while self.tokens:
-                    token = self.tokens.pop(0)
+                json_tokens, self.tokens = self.tokens, []
+                for token in json_tokens:
                     try:
                         parsed_json = json.loads(token.value)
                     except json.JSONDecodeError as e:
                         raise CoercionError(token=token, 
target_type=self.hint) from e
                     _validate_json_extra_keys(parsed_json, self.hint, token)
-                    update_argument_collection(
-                        {self.name.lstrip("-"): parsed_json},
-                        token.source,
-                        self.children_recursive,
-                        root_keys=(),
-                        allow_unknown=False,
-                    )
+                    if parsed_json:
+                        update_argument_collection(
+                            {self.name.lstrip("-"): parsed_json},
+                            token.source,
+                            self.children_recursive,
+                            root_keys=(),
+                            allow_unknown=False,
+                        )
+                    else:
+                        explicit_empty_mapping = True
+                        # Placeholder so ``has_tokens`` still reports this 
argument as supplied.
+                        self.tokens.append(token.evolve(value="", 
implicit_value={}))
 
             if self._use_pydantic_type_adapter:
                 return self._convert_pydantic()
 
             if self.tokens and not self._enum_flag_type:
                 positional_tokens = [token for token in self.tokens if not 
token.keys]
+                if any(isinstance(token.implicit_value, dict) for token in 
positional_tokens):
+                    explicit_empty_mapping = True
+                    positional_tokens = [
+                        token for token in positional_tokens if not 
isinstance(token.implicit_value, dict)
+                    ]
                 if positional_tokens:
                     return safe_converter(self.hint, tuple(positional_tokens))
 
@@ -838,7 +862,7 @@
                 out |= enum_flag_from_dict(self._enum_flag_type, data, 
self.parameter.name_transform)
                 if not out:
                     out = UNSET
-            elif data:
+            elif data or explicit_empty_mapping:
                 target_hint = self.hint
                 if self._union_branches:
                     # ``instantiate_from_dict`` cannot build a bare ``Union``; 
pick the branch
@@ -1189,6 +1213,10 @@
         out = {}
         if self._accepts_keywords:
             for token in self.tokens:
+                if not token.keys and isinstance(token.implicit_value, dict):
+                    # An explicitly-supplied empty mapping (e.g. ``x = {}`` in 
a config file);
+                    # contributes no keys, but ``has_tokens`` already marks 
the argument as supplied.
+                    continue
                 node = out
                 for key in token.keys[:-1]:
                     node = node.setdefault(key, {})
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.22.1/cyclopts/argument/utils.py 
new/cyclopts-4.22.2/cyclopts/argument/utils.py
--- old/cyclopts-4.22.1/cyclopts/argument/utils.py      2020-02-02 
01:00:00.000000000 +0100
+++ new/cyclopts-4.22.2/cyclopts/argument/utils.py      2020-02-02 
01:00:00.000000000 +0100
@@ -379,7 +379,7 @@
     if parent_keys is None:
         parent_keys = ()
 
-    if isinstance(d, dict):
+    if isinstance(d, dict) and d:
         for key, value in d.items():
             current_keys = parent_keys + (key,)
             if isinstance(value, dict):
@@ -387,7 +387,9 @@
             else:
                 yield current_keys, value
     else:
-        yield (), d
+        # An empty dict is a leaf: an explicitly-supplied empty mapping (e.g. 
``x = {}``)
+        # must produce a token, like an empty list does.
+        yield parent_keys, d
 
 
 def to_cli_option_name(*keys: str) -> str:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.22.1/cyclopts/bind.py 
new/cyclopts-4.22.2/cyclopts/bind.py
--- old/cyclopts-4.22.1/cyclopts/bind.py        2020-02-02 01:00:00.000000000 
+0100
+++ new/cyclopts-4.22.2/cyclopts/bind.py        2020-02-02 01:00:00.000000000 
+0100
@@ -225,7 +225,34 @@
                 cli_values.append(attached_value)
 
             if match.argument.parameter.count:
-                match.argument.append(CliToken(keyword=match.matched_token, 
implicit_value=1))
+                if cli_values:
+                    # An attached ``=value`` (e.g. --verbose=3) explicitly 
sets the count.
+                    # Plain ``int()`` parse: a count is a number of 
occurrences, so
+                    # fractional values like "1.5" are rejected rather than 
rounded
+                    # (unlike general int coercion via ``_int``), as are 
negatives.
+                    # The non-empty ``value`` marks the token as an explicit
+                    # assignment, which ``Argument.append`` forbids combining 
with
+                    # other occurrences.
+                    value = cli_values[-1]
+                    try:
+                        count_value = int(value)
+                    except ValueError:
+                        raise CoercionError(
+                            token=CliToken(keyword=match.matched_token, 
value=value),
+                            argument=match.argument,
+                            target_type=int,
+                        ) from None
+                    if count_value < 0:
+                        raise CoercionError(
+                            token=CliToken(keyword=match.matched_token, 
value=value),
+                            argument=match.argument,
+                            msg="Count values must be non-negative.",
+                        )
+                    match.argument.append(
+                        CliToken(keyword=match.matched_token, value=value, 
implicit_value=count_value)
+                    )
+                else:
+                    
match.argument.append(CliToken(keyword=match.matched_token, implicit_value=1))
             elif match.implicit_value is not UNSET:
                 # A flag was parsed
                 if cli_values:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.22.1/cyclopts/config/_common.py 
new/cyclopts-4.22.2/cyclopts/config/_common.py
--- old/cyclopts-4.22.1/cyclopts/config/_common.py      2020-02-02 
01:00:00.000000000 +0100
+++ new/cyclopts-4.22.2/cyclopts/config/_common.py      2020-02-02 
01:00:00.000000000 +0100
@@ -54,11 +54,19 @@
         arguments: ArgumentCollection,
     ):
         config: dict[str, Any] = self.config.copy()
-        try:
-            for key in chain(self.root_keys, commands if 
self.use_commands_as_keys else ()):
+        traversed: list[str] = []
+        for key in chain(self.root_keys, commands if self.use_commands_as_keys 
else ()):
+            try:
                 config = config[key]
-        except KeyError:
-            return
+            except KeyError:
+                return
+            traversed.append(key)
+            if not isinstance(config, dict):
+                keyword = "".join(f"[{k}]" for k in traversed)
+                raise CycloptsError(
+                    msg=f'Configuration key {keyword} in "{self.source}" must 
be a mapping, '
+                    f"but got {type(config).__name__}."
+                )
 
         # Hierarchical config uses current app; flat config uses root app to 
filter sibling commands
         if self.use_commands_as_keys:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.22.1/cyclopts/exceptions.py 
new/cyclopts-4.22.2/cyclopts/exceptions.py
--- old/cyclopts-4.22.1/cyclopts/exceptions.py  2020-02-02 01:00:00.000000000 
+0100
+++ new/cyclopts-4.22.2/cyclopts/exceptions.py  2020-02-02 01:00:00.000000000 
+0100
@@ -639,6 +639,9 @@
     """The repeated token."""
 
     def _segments(self) -> "Iterator[tuple[str, str] | Text]":
+        if self.msg is not None:
+            yield self._resolved_msg()
+            return
         # Invariant: positional duplication is routed to UnusedCliTokensError 
by the binder,
         # so any token reaching this error path was matched by keyword.
         assert self.token.keyword is not None

Reply via email to