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-08-27 18:53:04
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-cyclopts (Old)
 and      /work/SRC/openSUSE:Factory/.python-cyclopts.new.1265 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-cyclopts"

Thu Aug 27 18:53:04 2026 rev:14 rq:1373922 version:4.23.3

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-cyclopts/python-cyclopts.changes  
2026-08-24 12:16:02.642444500 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-cyclopts.new.1265/python-cyclopts.changes    
    2026-08-27 18:55:53.219062188 +0200
@@ -1,0 +2,17 @@
+Thu Aug 27 05:56:27 UTC 2026 - Martin Pluskal <[email protected]>
+
+- Update to 4.23.3:
+  * Render deprecation notices in help output again: the
+    ".. deprecated::" directive extracted into docstring metadata
+    is reconstructed for every help format, and deprecated
+    commands are now tagged in the command list
+  * Split JSON documents on "\n" rather than str.splitlines() when
+    rendering decode errors, so the reported line agrees with the
+    line number JSONDecodeError computed
+  * Include the commands of flattened subapps (registered with
+    name="*") in the generated documentation
+  * Treat a configuration file with an empty document as an empty
+    mapping, and reject a document root that is not a mapping with
+    a clear error instead of failing later
+
+-------------------------------------------------------------------

Old:
----
  cyclopts-4.23.2.tar.gz

New:
----
  cyclopts-4.23.3.tar.gz

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

Other differences:
------------------
++++++ python-cyclopts.spec ++++++
--- /var/tmp/diff_new_pack.8Z4mlp/_old  2026-08-27 18:55:54.151096240 +0200
+++ /var/tmp/diff_new_pack.8Z4mlp/_new  2026-08-27 18:55:54.154096350 +0200
@@ -18,7 +18,7 @@
 
 %bcond_without libalternatives
 Name:           python-cyclopts
-Version:        4.23.2
+Version:        4.23.3
 Release:        0
 Summary:        Intuitive, easy CLIs based on python type hints
 License:        Apache-2.0

++++++ cyclopts-4.23.2.tar.gz -> cyclopts-4.23.3.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.23.2/PKG-INFO new/cyclopts-4.23.3/PKG-INFO
--- old/cyclopts-4.23.2/PKG-INFO        2020-02-02 01:00:00.000000000 +0100
+++ new/cyclopts-4.23.3/PKG-INFO        2020-02-02 01:00:00.000000000 +0100
@@ -1,6 +1,6 @@
 Metadata-Version: 2.5
 Name: cyclopts
-Version: 4.23.2
+Version: 4.23.3
 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.23.2/cyclopts/_version.py 
new/cyclopts-4.23.3/cyclopts/_version.py
--- old/cyclopts-4.23.2/cyclopts/_version.py    2020-02-02 01:00:00.000000000 
+0100
+++ new/cyclopts-4.23.3/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.23.2'
-__version_tuple__ = version_tuple = (4, 23, 2)
+__version__ = version = '4.23.3'
+__version_tuple__ = version_tuple = (4, 23, 3)
 
 __commit_id__ = commit_id = None
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.23.2/cyclopts/config/_common.py 
new/cyclopts-4.23.3/cyclopts/config/_common.py
--- old/cyclopts-4.23.2/cyclopts/config/_common.py      2020-02-02 
01:00:00.000000000 +0100
+++ new/cyclopts-4.23.3/cyclopts/config/_common.py      2020-02-02 
01:00:00.000000000 +0100
@@ -47,26 +47,41 @@
         """Return a string identifying the configuration source for error 
messages."""
         raise NotImplementedError
 
+    def _ensure_mapping(self, node: Any, traversed: list[str]) -> None:
+        """Raise a :class:`CycloptsError` if a visited configuration node is 
not a mapping.
+
+        Parameters
+        ----------
+        node: Any
+            The configuration node reached so far.
+        traversed: list[str]
+            Keys descended through to reach ``node``; empty for the document 
root.
+        """
+        if isinstance(node, dict):
+            return
+        keyword = "".join(f"[{k}]" for k in traversed)
+        location = f"key {keyword} " if keyword else ""
+        raise CycloptsError(
+            msg=f'Configuration {location}in "{self.source}" must be a 
mapping, but got {type(node).__name__}.'
+        )
+
     def __call__(
         self,
         app: "App",
         commands: tuple[str, ...],
         arguments: ArgumentCollection,
     ):
-        config: dict[str, Any] = self.config.copy()
         traversed: list[str] = []
+        root = self.config
+        self._ensure_mapping(root, traversed)
+        config: dict[str, Any] = root.copy()
         for key in chain(self.root_keys, commands if self.use_commands_as_keys 
else ()):
             try:
                 config = config[key]
             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__}."
-                )
+            self._ensure_mapping(config, traversed)
 
         # Hierarchical config uses current app; flat config uses root app to 
filter sibling commands
         if self.use_commands_as_keys:
@@ -167,7 +182,7 @@
                             msg += ": "
                         msg += exception_msg
                     raise CycloptsError(msg=msg) from e
-                return self._config
+                return self._config or {}
             if not self.search_parents:
                 # Only look at the specified path; do not walk parent 
directories.
                 break
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.23.2/cyclopts/docs/base.py 
new/cyclopts-4.23.3/cyclopts/docs/base.py
--- old/cyclopts-4.23.2/cyclopts/docs/base.py   2020-02-02 01:00:00.000000000 
+0100
+++ new/cyclopts-4.23.3/cyclopts/docs/base.py   2020-02-02 01:00:00.000000000 
+0100
@@ -503,12 +503,16 @@
     Tuple[str, App]
         (command_name, resolved_subapp) for each valid command.
     """
-    if not app._commands:
-        return
+    commands: dict[str, App | CommandSpec] = dict(app._commands)
+    # Merge commands from flattened subapps (registered via name="*"); parent 
commands take precedence.
+    for flattened in app._flattened_subapps:
+        for cmd_name in flattened:
+            if cmd_name not in commands and not _is_builtin_flag(flattened, 
cmd_name):
+                commands[cmd_name] = flattened._get_item(cmd_name, 
recurse_meta=False)
 
     seen: set[int] = set()
 
-    for name, app_or_spec in app._commands.items():
+    for name, app_or_spec in commands.items():
         if _is_builtin_flag(app, name):
             continue
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.23.2/cyclopts/help/help.py 
new/cyclopts-4.23.3/cyclopts/help/help.py
--- old/cyclopts-4.23.2/cyclopts/help/help.py   2020-02-02 01:00:00.000000000 
+0100
+++ new/cyclopts-4.23.3/cyclopts/help/help.py   2020-02-02 01:00:00.000000000 
+0100
@@ -351,6 +351,26 @@
     return "".join(result)
 
 
+def format_deprecated_tag(version: str | None, content: str | None) -> str:
+    """Format a "Deprecated" tag, shared by the ``.. deprecated::`` directive 
and ``DocstringDeprecated`` metadata.
+
+    Parameters
+    ----------
+    version : str | None
+        Version the deprecation applies to, if any.
+    content : str | None
+        Optional explanatory content (e.g. "Use something else instead").
+
+    Returns
+    -------
+    str
+        Formatted tag, e.g. ``"[⚠ Deprecated in v1.0] Use something else 
instead"``.
+    """
+    tag = f"[⚠ Deprecated in v{version}]" if version else "[⚠ Deprecated]"
+    content = (content or "").strip()
+    return f"{tag} {content}" if content else tag
+
+
 def format_doc(app: "App", format: str) -> InlineText | SilentRich:
     raw_doc_string = app.help
 
@@ -363,10 +383,19 @@
     if parsed.short_description:
         components.append(parsed.short_description + "\n")
 
+    # docstring_parser extracts a top-level ``.. deprecated::`` directive into
+    # ``parsed.deprecation`` metadata, so it never reaches the renderer as 
text;
+    # reconstruct it here for every help format.
+    if parsed.deprecation:
+        if components:
+            components.append("\n")
+        components.append(format_deprecated_tag(parsed.deprecation.version, 
parsed.deprecation.description) + "\n")
+
     if parsed.long_description:
-        if parsed.short_description:
+        if components:
             components.append("\n")
         components.append(parsed.long_description + "\n")
+
     return InlineText.from_format(_smart_join(components), format=format, 
force_empty_end=True)
 
 
@@ -604,10 +633,17 @@
 
         sort_key = resolve_callables(app.sort_key, app)
 
+        parsed = docstring_parse(app.help, format)
+        description = parsed.short_description
+        if parsed.deprecation:
+            # Tag-only in the command list; the full deprecation message shows 
on the command's own help page.
+            tag = format_deprecated_tag(parsed.deprecation.version, None)
+            description = f"{tag} {description}" if description else tag
+
         entry = HelpEntry(
             positive_names=tuple(long_names),
             positive_shorts=tuple(short_names),
-            description=InlineText.from_format(docstring_parse(app.help, 
format).short_description, format=format),
+            description=InlineText.from_format(description, format=format),
             sort_key=sort_key,
         )
         if entry not in entries:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.23.2/cyclopts/help/rst_preprocessor.py 
new/cyclopts-4.23.3/cyclopts/help/rst_preprocessor.py
--- old/cyclopts-4.23.2/cyclopts/help/rst_preprocessor.py       2020-02-02 
01:00:00.000000000 +0100
+++ new/cyclopts-4.23.3/cyclopts/help/rst_preprocessor.py       2020-02-02 
01:00:00.000000000 +0100
@@ -212,10 +212,11 @@
     next_index : int
         Next line index to process.
     """
+    from cyclopts.help.help import format_deprecated_tag
+
     paragraphs, next_i = _gather_indented_block(lines, start_index + 1, 
current_indent)
     content = "\n\n".join(paragraphs).strip()
-    tag = f"[⚠ Deprecated in v{directive_arg}]"
-    return f"{tag} {content}" if content else tag, next_i
+    return format_deprecated_tag(directive_arg, content), next_i
 
 
 def _handle_admonition_directive(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/cyclopts-4.23.2/cyclopts/utils.py 
new/cyclopts-4.23.3/cyclopts/utils.py
--- old/cyclopts-4.23.2/cyclopts/utils.py       2020-02-02 01:00:00.000000000 
+0100
+++ new/cyclopts-4.23.3/cyclopts/utils.py       2020-02-02 01:00:00.000000000 
+0100
@@ -404,7 +404,11 @@
     context: int
         Number of surrounding-character context
     """
-    lines = decode_error.doc.splitlines()
+    # ``JSONDecodeError.lineno`` is derived from ``doc.count("\n", 0, pos) + 
1``, so the document
+    # has to be split on ``"\n"`` to stay in step with it. 
``str.splitlines()`` additionally breaks
+    # on ``\v``, ``\f``, ``\u2028`` and friends, which JSON allows raw inside 
strings, and it drops
+    # the empty final line, so it can select the wrong line or index past the 
end.
+    lines = decode_error.doc.split("\n")
     line = lines[decode_error.lineno - 1]
 
     error_index = decode_error.colno - 1  # colno is 1-indexed

Reply via email to