Script 'mail_helper' called by obssrc
Hello community,
here is the log from the commit of package python-extra-platforms for
openSUSE:Factory checked in at 2026-03-05 17:29:55
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-extra-platforms (Old)
and /work/SRC/openSUSE:Factory/.python-extra-platforms.new.561 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "python-extra-platforms"
Thu Mar 5 17:29:55 2026 rev:16 rq:1336673 version:11.0.2
Changes:
--------
---
/work/SRC/openSUSE:Factory/python-extra-platforms/python-extra-platforms.changes
2026-03-02 17:41:28.770211481 +0100
+++
/work/SRC/openSUSE:Factory/.python-extra-platforms.new.561/python-extra-platforms.changes
2026-03-05 17:32:08.004898194 +0100
@@ -1,0 +2,14 @@
+Thu Mar 5 07:24:05 UTC 2026 - Johannes Kastl
<[email protected]>
+
+- update to 11.0.2:
+ * Raise import time test threshold from 200 ms to 500 ms to
+ accommodate slower architectures like i586. Closes #494
+
+-------------------------------------------------------------------
+Wed Mar 4 12:13:03 UTC 2026 - Johannes Kastl
<[email protected]>
+
+- update to 11.0.1:
+ * Cache AST parse results per module in docstring generation,
+ reducing import extra_platforms from ~120 ms to ~34 ms.
+
+-------------------------------------------------------------------
Old:
----
extra_platforms-11.0.0.tar.gz
New:
----
extra_platforms-11.0.2.tar.gz
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Other differences:
------------------
++++++ python-extra-platforms.spec ++++++
--- /var/tmp/diff_new_pack.Q7G3P0/_old 2026-03-05 17:32:08.860933797 +0100
+++ /var/tmp/diff_new_pack.Q7G3P0/_new 2026-03-05 17:32:08.864933963 +0100
@@ -23,7 +23,7 @@
%bcond_with libalternatives
%endif
Name: python-extra-platforms
-Version: 11.0.0
+Version: 11.0.2
Release: 0
Summary: Detect platforms and group them by family
License: Apache-2.0
++++++ extra_platforms-11.0.0.tar.gz -> extra_platforms-11.0.2.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/changelog.md
new/extra-platforms-11.0.2/changelog.md
--- old/extra-platforms-11.0.0/changelog.md 2026-02-28 13:32:24.000000000
+0100
+++ new/extra-platforms-11.0.2/changelog.md 2026-03-04 16:26:31.000000000
+0100
@@ -1,5 +1,13 @@
# Changelog
+## [11.0.2
(2026-03-04)](https://github.com/kdeldycke/extra-platforms/compare/v11.0.1...v11.0.2)
+
+- Raise import time test threshold from 200 ms to 500 ms to accommodate slower
architectures like i586. Closes #494.
+
+## [11.0.1
(2026-03-02)](https://github.com/kdeldycke/extra-platforms/compare/v11.0.0...v11.0.1)
+
+- Cache AST parse results per module in docstring generation, reducing `import
extra_platforms` from ~120 ms to ~34 ms.
+
## [11.0.0
(2026-02-28)](https://github.com/kdeldycke/extra-platforms/compare/v10.0.0...v11.0.0)
- Switch license from GPL-2.0+ to Apache-2.0. Closes #488.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/citation.cff
new/extra-platforms-11.0.2/citation.cff
--- old/extra-platforms-11.0.0/citation.cff 2026-02-28 13:32:24.000000000
+0100
+++ new/extra-platforms-11.0.2/citation.cff 2026-03-04 16:26:31.000000000
+0100
@@ -1,4 +1,4 @@
-cff-version: 11.0.0
+cff-version: 11.0.2
title: "Extra Platforms"
message: "If you use this software, please cite it as below."
type: software
@@ -8,6 +8,6 @@
email: [email protected]
orcid: "https://orcid.org/0000-0001-9748-9014"
doi: 10.5281/zenodo.13341712
-version: 11.0.0
-date-released: 2026-02-28
+version: 11.0.2
+date-released: 2026-03-04
url: "https://github.com/kdeldycke/extra-platforms"
\ No newline at end of file
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/extra_platforms/__init__.py
new/extra-platforms-11.0.2/extra_platforms/__init__.py
--- old/extra-platforms-11.0.0/extra_platforms/__init__.py 2026-02-28
13:32:24.000000000 +0100
+++ new/extra-platforms-11.0.2/extra_platforms/__init__.py 2026-03-04
16:26:31.000000000 +0100
@@ -389,7 +389,7 @@
"""
-__version__ = "11.0.0"
+__version__ = "11.0.2"
def _initialize_group_detection_functions() -> list[str]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore'
old/extra-platforms-11.0.0/extra_platforms/_docstrings.py
new/extra-platforms-11.0.2/extra_platforms/_docstrings.py
--- old/extra-platforms-11.0.0/extra_platforms/_docstrings.py 2026-02-28
13:32:24.000000000 +0100
+++ new/extra-platforms-11.0.2/extra_platforms/_docstrings.py 2026-03-04
16:26:31.000000000 +0100
@@ -19,6 +19,13 @@
The :func:`_initialize_all_docstrings` function should be called from
``__init__.py`` after all trait and group instances are imported to populate
their ``__doc__`` attributes.
+
+.. note::
+ AST parse results are cached per module to avoid redundant work. Without
+ caching, each of the ~170 trait/group instances triggers a full
+ ``ast.parse()`` of its source module, totaling ~300 parses at import time
+ (~115 ms). With per-module caching this drops to ~7 parses (~2.5 ms),
+ reducing ``import extra_platforms`` from ~120 ms to ~10 ms.
"""
from __future__ import annotations
@@ -34,68 +41,92 @@
from . import Group, Trait
+# Per-module cache of attribute name to docstring. Populated lazily by
+# ``_parse_module_docstrings`` on first access to a given module.
+_module_docstrings_cache: dict[str, dict[str, str]] = {}
-def get_attribute_docstring(module_name: str, attr_name: str) -> str | None:
- """Extract attribute docstring from a module's source file.
- Attribute docstrings are string literals that immediately follow an
- assignment. This function parses the source file using AST to find such
- docstrings.
+def _parse_module_docstrings(module_name: str) -> dict[str, str]:
+ """Parse a module's source and return all attribute docstrings at once.
- .. note::
- Returns ``None`` if source files are unavailable, which happens in
- compiled environments (e.g., Nuitka, PyInstaller, cx_Freeze). This
- graceful degradation allows the library to function without docstrings
- in compiled binaries.
+ Attribute docstrings are string literals that immediately follow an
+ assignment statement at module level.
:param module_name: The full module name (e.g.,
'extra_platforms.platform_data').
- :param attr_name: The attribute name to look for (e.g., 'NOBARA').
- :returns: The attribute docstring if found, or ``None`` if not found or
- source is unavailable.
+ :returns: A mapping of attribute names to their docstrings. Empty dict if
+ the source is unavailable.
"""
module = importlib.import_module(module_name)
source_file = inspect.getsourcefile(module)
if not source_file:
# Source file not available (e.g., compiled module).
- return None
+ return {}
try:
source = Path(source_file).read_text(encoding="utf-8")
except (FileNotFoundError, OSError):
# Source file doesn't exist on disk (e.g., Nuitka-compiled binary).
- return None
+ return {}
tree = ast.parse(source)
- # Look for assignment (or annotated assignment) followed by a string
- # literal.
- for i, node in enumerate(tree.body):
- # Handle both regular assignments and annotated assignments.
+ docstrings: dict[str, str] = {}
+ body = tree.body
+
+ # Walk all top-level statements, looking for assignments followed by a
+ # string literal (the attribute docstring convention).
+ for i, node in enumerate(body):
+ names: list[str] = []
+
if isinstance(node, ast.Assign):
- for target in node.targets:
- if isinstance(target, ast.Name) and target.id == attr_name:
- # Check if the next statement is a string expression.
- if i + 1 < len(tree.body):
- next_node = tree.body[i + 1]
- if isinstance(next_node, ast.Expr) and isinstance(
- next_node.value, ast.Constant
- ):
- if isinstance(next_node.value.value, str):
- return next_node.value.value
+ names.extend(
+ target.id for target in node.targets if isinstance(target,
ast.Name)
+ )
elif isinstance(node, ast.AnnAssign):
# Handle annotated assignments like: x: type = value.
- if isinstance(node.target, ast.Name) and node.target.id ==
attr_name:
- # Check if the next statement is a string expression.
- if i + 1 < len(tree.body):
- next_node = tree.body[i + 1]
- if isinstance(next_node, ast.Expr) and isinstance(
- next_node.value, ast.Constant
- ):
- if isinstance(next_node.value.value, str):
- return next_node.value.value
+ if isinstance(node.target, ast.Name):
+ names.append(node.target.id)
+
+ if not names:
+ continue
+
+ # Check if the next statement is a string expression.
+ if i + 1 < len(body):
+ next_node = body[i + 1]
+ if (
+ isinstance(next_node, ast.Expr)
+ and isinstance(next_node.value, ast.Constant)
+ and isinstance(next_node.value.value, str)
+ ):
+ for name in names:
+ docstrings[name] = next_node.value.value
+
+ return docstrings
- return None
+
+def get_attribute_docstring(module_name: str, attr_name: str) -> str | None:
+ """Extract attribute docstring from a module's source file.
+
+ Attribute docstrings are string literals that immediately follow an
+ assignment. Results are cached per module so each source file is parsed
+ only once regardless of how many attributes are looked up.
+
+ .. note::
+ Returns ``None`` if source files are unavailable, which happens in
+ compiled environments (e.g., Nuitka, PyInstaller, cx_Freeze). This
+ graceful degradation allows the library to function without docstrings
+ in compiled binaries.
+
+ :param module_name: The full module name (e.g.,
+ 'extra_platforms.platform_data').
+ :param attr_name: The attribute name to look for (e.g., 'NOBARA').
+ :returns: The attribute docstring if found, or ``None`` if not found or
+ source is unavailable.
+ """
+ if module_name not in _module_docstrings_cache:
+ _module_docstrings_cache[module_name] =
_parse_module_docstrings(module_name)
+ return _module_docstrings_cache[module_name].get(attr_name)
def _initialize_all_docstrings(
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/extra_platforms/detection.py
new/extra-platforms-11.0.2/extra_platforms/detection.py
--- old/extra-platforms-11.0.0/extra_platforms/detection.py 2026-02-28
13:32:24.000000000 +0100
+++ new/extra-platforms-11.0.2/extra_platforms/detection.py 2026-03-04
16:26:31.000000000 +0100
@@ -57,6 +57,13 @@
<https://docs.python.org/3/library/platform.html#platform.release>`_
- environment variables
+.. todo::
+ ``hostnamectl`` could be used as a fallback detection source when
+ ``/etc/os-release`` is missing (e.g., stripped CloudLinux VMs). This
approach was
+ `proposed upstream in python-distro
+ <https://github.com/python-distro/distro/pull/369>`_ but rejected. The
technique
+ is sound and could be implemented here.
+
.. seealso::
Other source of inspiration for platform detection:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/extra_platforms/trait.py
new/extra-platforms-11.0.2/extra_platforms/trait.py
--- old/extra-platforms-11.0.0/extra_platforms/trait.py 2026-02-28
13:32:24.000000000 +0100
+++ new/extra-platforms-11.0.2/extra_platforms/trait.py 2026-03-04
16:26:31.000000000 +0100
@@ -334,6 +334,11 @@
class Architecture(Trait):
"""A CPU architecture identifies a `processor instruction set
<https://en.wikipedia.org/wiki/Instruction_set_architecture>`_.
+
+ .. seealso::
+ `archspec <https://github.com/archspec/archspec>`_ provides a rich
database of
+ CPU microarchitectures (feature flags, compiler compatibility, family
trees).
+ It could be used to extend our architecture metadata beyond basic ISA
detection.
"""
def info(self) -> dict[str, str | bool | None]:
@@ -354,6 +359,12 @@
class Platform(Trait):
"""A platform can identify multiple distributions or OSes with the same
characteristics.
+
+ .. seealso::
+ Init systems (systemd, upstart, sysvinit, openrc, runit, etc.) are
another
+ dimension of platform characterization that could be detected in the
future.
+ See `python-distro/distro#142
<https://github.com/python-distro/distro/issues/142>`_
+ for prior discussion on init system detection.
"""
def info(self) -> dict[str, str | bool | None | dict[str, str | None]]:
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/pyproject.toml
new/extra-platforms-11.0.2/pyproject.toml
--- old/extra-platforms-11.0.0/pyproject.toml 2026-02-28 13:32:24.000000000
+0100
+++ new/extra-platforms-11.0.2/pyproject.toml 2026-03-04 16:26:31.000000000
+0100
@@ -5,7 +5,7 @@
[project]
# Docs: https://packaging.python.org/en/latest/guides/writing-pyproject-toml/
name = "extra-platforms"
-version = "11.0.0"
+version = "11.0.2"
description = "🔎 Detect architectures, platforms, shells, terminals, CI
systems and agents, grouped by family"
readme = "readme.md"
keywords = [
@@ -245,7 +245,7 @@
[project.urls]
"Changelog" =
"https://github.com/kdeldycke/extra-platforms/blob/main/changelog.md"
"Documentation" = "https://kdeldycke.github.io/extra-platforms"
-"Download" =
"https://github.com/kdeldycke/extra-platforms/releases/tag/v11.0.0"
+"Download" =
"https://github.com/kdeldycke/extra-platforms/releases/tag/v11.0.2"
"Funding" = "https://github.com/sponsors/kdeldycke"
"Homepage" = "https://github.com/kdeldycke/extra-platforms"
"Issues" = "https://github.com/kdeldycke/extra-platforms/issues"
@@ -374,7 +374,7 @@
report.precision = 2
[tool.bumpversion]
-current_version = "11.0.0"
+current_version = "11.0.2"
allow_dirty = true
# Parse versions with an optional .devN suffix (PEP 440).
parse =
"(?P<major>\\d+)\\.(?P<minor>\\d+)\\.(?P<patch>\\d+)(\\.dev(?P<dev>\\d+))?"
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/tests/test_root.py
new/extra-platforms-11.0.2/tests/test_root.py
--- old/extra-platforms-11.0.0/tests/test_root.py 2026-02-28
13:32:24.000000000 +0100
+++ new/extra-platforms-11.0.2/tests/test_root.py 2026-03-04
16:26:31.000000000 +0100
@@ -16,6 +16,7 @@
import ast
import inspect
+import subprocess
import sys
from pathlib import Path
@@ -598,6 +599,34 @@
assert is_any_platform.cache_info().currsize == 0
+def test_import_time():
+ """Guard against import time regressions.
+
+ Spawns a fresh subprocess to measure cold import time, avoiding
+ ``sys.modules`` cache effects from pytest's own imports.
+
+ The 500 ms threshold is deliberately loose to absorb CI runner variability
+ on shared VMs and slower architectures (e.g., i586 where cold import takes
+ ~340 ms). This will not catch small drifts, but reliably prevents
+ reintroducing expensive import-time operations (e.g., the ~120 ms
+ regression from redundant AST parsing that was fixed by caching in
+ ``_docstrings.py``).
+ """
+ result = subprocess.run(
+ [
+ sys.executable,
+ "-c",
+ "import time; t = time.perf_counter(); import extra_platforms; "
+ "print(time.perf_counter() - t)",
+ ],
+ capture_output=True,
+ text=True,
+ check=True,
+ )
+ elapsed_ms = float(result.stdout.strip()) * 1000
+ assert elapsed_ms < 500, f"Import took {elapsed_ms:.1f} ms, expected < 500
ms"
+
+
def test_invalidate_caches_clears_trait_current_property():
"""Test that invalidate_caches clears Trait.current cached_property."""
# Access current property for a few traits to populate their caches.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn'
'--exclude=.svnignore' old/extra-platforms-11.0.0/uv.lock
new/extra-platforms-11.0.2/uv.lock
--- old/extra-platforms-11.0.0/uv.lock 2026-02-28 13:32:24.000000000 +0100
+++ new/extra-platforms-11.0.2/uv.lock 2026-03-04 16:26:31.000000000 +0100
@@ -373,7 +373,7 @@
[[package]]
name = "extra-platforms"
-version = "11.0.0.dev0"
+version = "11.0.2.dev0"
source = { editable = "." }
[package.optional-dependencies]