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