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]

Reply via email to