Script 'mail_helper' called by obssrc
Hello community,

here is the log from the commit of package python-testfixtures for 
openSUSE:Factory checked in at 2026-07-08 17:32:17
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Comparing /work/SRC/openSUSE:Factory/python-testfixtures (Old)
 and      /work/SRC/openSUSE:Factory/.python-testfixtures.new.1982 (New)
++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++

Package is "python-testfixtures"

Wed Jul  8 17:32:17 2026 rev:33 rq:1364092 version:12.2.0

Changes:
--------
--- /work/SRC/openSUSE:Factory/python-testfixtures/python-testfixtures.changes  
2026-06-19 17:22:03.183471423 +0200
+++ 
/work/SRC/openSUSE:Factory/.python-testfixtures.new.1982/python-testfixtures.changes
        2026-07-08 17:32:25.600871442 +0200
@@ -1,0 +2,9 @@
+Mon Jul  6 06:42:48 UTC 2026 - Dirk Müller <[email protected]>
+
+- update to 12.2.0:
+  * Added :meth:`LogCapture.check_empty`.
+  * Added :meth:`LogCapture.disabled` context manager.
+  * Added level and predicate parameters to
+    :meth:`LogCapture.check`.
+
+-------------------------------------------------------------------

Old:
----
  testfixtures-12.1.0.tar.gz

New:
----
  testfixtures-12.2.0.tar.gz

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

Other differences:
------------------
++++++ python-testfixtures.spec ++++++
--- /var/tmp/diff_new_pack.bLDPd7/_old  2026-07-08 17:32:27.068922479 +0200
+++ /var/tmp/diff_new_pack.bLDPd7/_new  2026-07-08 17:32:27.072922617 +0200
@@ -34,7 +34,7 @@
 
 %{?sle15_python_module_pythons}
 Name:           python-testfixtures%{psuffix}
-Version:        12.1.0
+Version:        12.2.0
 Release:        0
 Summary:        A collection of helpers and mock objects for unit tests and 
doc tests
 License:        MIT

++++++ testfixtures-12.1.0.tar.gz -> testfixtures-12.2.0.tar.gz ++++++
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/CHANGELOG.rst 
new/testfixtures-12.2.0/CHANGELOG.rst
--- old/testfixtures-12.1.0/CHANGELOG.rst       2026-06-15 17:22:45.000000000 
+0200
+++ new/testfixtures-12.2.0/CHANGELOG.rst       2026-06-20 11:53:06.000000000 
+0200
@@ -3,6 +3,15 @@
 Changes
 =======
 
+12.2.0 (20 Jun 2026)
+--------------------
+
+- Added :meth:`LogCapture.check_empty`.
+
+- Added :meth:`LogCapture.disabled` context manager.
+
+- Added ``level`` and ``predicate`` parameters to :meth:`LogCapture.check`.
+
 12.1.0 (15 Jun 2026)
 --------------------
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/PKG-INFO 
new/testfixtures-12.2.0/PKG-INFO
--- old/testfixtures-12.1.0/PKG-INFO    2026-06-15 17:22:45.000000000 +0200
+++ new/testfixtures-12.2.0/PKG-INFO    2026-06-20 11:53:06.000000000 +0200
@@ -1,6 +1,6 @@
 Metadata-Version: 2.4
 Name: testfixtures
-Version: 12.1.0
+Version: 12.2.0
 Summary: A collection of helpers and mock objects for unit tests and doc tests.
 Project-URL: Homepage, https://github.com/Simplistix/testfixtures
 Project-URL: Documentation, http://testfixtures.readthedocs.org/en/latest/
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/docs/logging.rst 
new/testfixtures-12.2.0/docs/logging.rst
--- old/testfixtures-12.1.0/docs/logging.rst    2026-06-15 17:22:45.000000000 
+0200
+++ new/testfixtures-12.2.0/docs/logging.rst    2026-06-20 11:53:06.000000000 
+0200
@@ -127,6 +127,37 @@
 ...     order_matters=False
 ... )
 
+Pass ``level`` to exclude entries below it, such as debugging output:
+
+>>> import logging
+>>> log.check(
+...     ('INFO', 'start of block number 1'),
+...     ('ERROR', 'error occurred'),
+...     level=logging.INFO,
+... )
+
+Pass ``predicate`` to filter on each captured 
:class:`~testfixtures.logcapture.Entry`,
+including its ``raw`` framework object; only those for which it returns true 
are compared:
+
+>>> log.check(
+...     ('ERROR', 'error occurred'),
+...     predicate=lambda entry: entry.exception is not None,
+... )
+
+Both can be combined; entries excluded by either are left un-checked, so
+:meth:`~testfixtures.LogCapture.ensure_checked` still reports them.
+
+To assert that nothing was logged, 
:meth:`~testfixtures.LogCapture.check_empty` is
+clearer than :meth:`~testfixtures.LogCapture.check` with no expected entries. 
It
+takes the same ``level`` and ``predicate``, so you can assert that nothing was
+logged above a level:
+
+.. code-block:: python
+
+    with LogCapture() as quiet:
+        getLogger().debug('just debugging')
+    quiet.check_empty(level=logging.INFO)
+
 Inspecting
 ~~~~~~~~~~
 
@@ -235,6 +266,18 @@
 
 >>> logs.check(('INFO', 'something we care about'))
 
+To ignore noisy setup, use the :meth:`~testfixtures.LogCapture.disabled` 
context
+manager, which uninstalls the capture for the duration of the ``with`` block:
+
+.. code-block:: python
+
+    with LogCapture(LoggingSource()) as logs:
+        with logs.disabled():
+            logger.warning('noisy setup output')
+        logger.info('what we care about')
+
+    logs.check(('INFO', 'what we care about'))
+
 You can also capture different attributes by specifying their names; if the 
attribute is
 callable, as with ``getMessage`` here, it will be called:
 
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/pyproject.toml 
new/testfixtures-12.2.0/pyproject.toml
--- old/testfixtures-12.1.0/pyproject.toml      2026-06-15 17:22:45.000000000 
+0200
+++ new/testfixtures-12.2.0/pyproject.toml      2026-06-20 11:53:06.000000000 
+0200
@@ -1,6 +1,6 @@
 [project]
 name = "testfixtures"
-version = "12.1.0"
+version = "12.2.0"
 description = "A collection of helpers and mock objects for unit tests and doc 
tests."
 readme = "README.rst"
 authors = [
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/src/testfixtures/logcapture.py 
new/testfixtures-12.2.0/src/testfixtures/logcapture.py
--- old/testfixtures-12.1.0/src/testfixtures/logcapture.py      2026-06-15 
17:22:45.000000000 +0200
+++ new/testfixtures-12.2.0/src/testfixtures/logcapture.py      2026-06-20 
11:53:06.000000000 +0200
@@ -2,12 +2,13 @@
 import logging
 import warnings
 from collections import defaultdict
+from contextlib import contextmanager
 from dataclasses import dataclass
 from logging import LogRecord
 from pprint import pformat
 from types import TracebackType
 from typing import (
-    Any, Callable, Sequence, TypeAlias, TypeVar, List, Tuple, Self, Protocol, 
overload
+    Any, Callable, Iterator, Sequence, TypeAlias, TypeVar, List, Tuple, Self, 
Protocol, overload
 )
 from warnings import warn
 
@@ -191,6 +192,7 @@
                 names = (names,)
             self._sources = [LoggingSource(attributes, level, names=names, 
propagate=propagate)]
         self.recursive_check = recursive_check
+        self._disabled = False
         if ensure_checks_above is None:
             self.ensure_checks_above = self.default_ensure_checks_above
         else:
@@ -255,6 +257,20 @@
     def _collect_entry(self, entry: Entry) -> None:
         self.entries.append(entry)
 
+    @contextmanager
+    def disabled(self) -> Iterator[None]:
+        """
+        A context manager that stops capturing for the duration of the 
``with`` block by
+        uninstalling the capture and reinstalling it on exit. Anything logged 
while it is
+        active is handled as if the capture were not present, which is useful 
for ignoring
+        noisy setup in a test.
+        """
+        self.uninstall()
+        try:
+            yield
+        finally:
+            self.install()
+
     def install(self) -> Self | None:
         """
         Install this :class:`LogCapture`, enabling all configured sources to 
begin capturing.
@@ -298,7 +314,14 @@
         tuples = (r if isinstance(r, tuple) else (r,) for r in self.actual())
         return '\n'.join(' '.join(str(e) for e in t) for t in tuples)
 
-    def check(self, *expected: Any, order_matters: bool = True, raises: bool = 
True) -> str | None:
+    def check(
+        self,
+        *expected: Any,
+        order_matters: bool = True,
+        raises: bool = True,
+        level: int | None = None,
+        predicate: Callable[[Entry], bool] | None = None,
+    ) -> str | None:
         """
         This will compare the captured entries with the expected
         entries provided and raise an :class:`AssertionError` if they
@@ -317,16 +340,36 @@
         :param raises: If ``False``, the message that would be raised in the
                        :class:`AssertionError` will be returned instead of the
                        exception being raised.
+
+        :param level:
+
+          A keyword-only parameter that, if provided, excludes any captured 
entry
+          with a numeric :attr:`~testfixtures.logcapture.Entry.level` below it 
from
+          the comparison. Entries with a ``level`` of ``None`` are always 
included.
+
+        :param predicate:
+
+          A keyword-only parameter that, if provided, is called with each 
captured
+          :class:`~testfixtures.logcapture.Entry`; only those for which it 
returns
+          a true value are included in the comparison.
         """
         __tracebackhide__ = True
 
+        actual = []
+        for entry in self.entries:
+            if level is not None and entry.level is not None and entry.level < 
level:
+                continue
+            if predicate is not None and not predicate(entry):
+                continue
+            entry.checked = True
+            actual.append(entry.actual)
+
         result = None
         if order_matters:
             result = compare(
-                expected, actual=self.actual(), 
recursive=self.recursive_check, raises=False
+                expected, actual=actual, recursive=self.recursive_check, 
raises=False
             )
         else:
-            actual = self.actual()
             expected_ = SequenceComparison(
                 *expected, ordered=False, partial=False, 
recursive=self.recursive_check
             )
@@ -334,9 +377,32 @@
                 result = expected_.failed
         if result and raises:
             raise AssertionError(result)
-        self.mark_all_checked()
         return result
 
+    def check_empty(
+        self,
+        level: int | None = None,
+        predicate: Callable[[Entry], bool] | None = None,
+    ) -> None:
+        """
+        Assert that nothing was logged, raising an :class:`AssertionError` if 
it was.
+        This is a clearer way of writing :meth:`check` with no expected 
entries.
+
+        :param level:
+
+          If provided, only entries with a numeric
+          :attr:`~testfixtures.logcapture.Entry.level` at or above it are 
considered.
+          Entries with a ``level`` of ``None`` are always considered.
+
+        :param predicate:
+
+          If provided, it is called with each captured
+          :class:`~testfixtures.logcapture.Entry`; only those for which it 
returns
+          a true value are considered.
+        """
+        __tracebackhide__ = True
+        self.check(level=level, predicate=predicate)
+
     def raise_first_exception(self, start_index: int = 0) -> None:
         """
         Raise the first captured exception from ``start_index`` onwards.
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/tests/test_logcapture.py 
new/testfixtures-12.2.0/tests/test_logcapture.py
--- old/testfixtures-12.1.0/tests/test_logcapture.py    2026-06-15 
17:22:45.000000000 +0200
+++ new/testfixtures-12.2.0/tests/test_logcapture.py    2026-06-20 
11:53:06.000000000 +0200
@@ -1,10 +1,10 @@
 import atexit
-from logging import getLogger, ERROR, Filter, shutdown
+from logging import getLogger, INFO, WARNING, ERROR, Filter, shutdown
 from textwrap import dedent
 from unittest import TestCase
 
-from testfixtures import Replacer, LogCapture, compare, Replace, ShouldWarn
-from testfixtures.logcapture import LoggingSource
+from testfixtures import Replacer, LogCapture, compare, Replace, ShouldRaise, 
ShouldWarn
+from testfixtures.logcapture import Entry, LoggingSource
 from testfixtures.mock import Mock, call
 from testfixtures.shouldraise import ShouldAssert
 
@@ -19,6 +19,20 @@
         return True
 
 
+class LevellessSource:
+    # A minimal CaptureSource for a framework that has no comparable numeric
+    # level, so its entries are captured with level=None.
+
+    def install(self, collector):
+        self.collector = collector
+
+    def uninstall(self):
+        self.collector = None
+
+    def log(self, *actual):
+        self.collector(Entry(raw=actual, actual=actual, level=None))
+
+
 class TestLogCapture(TestCase):
 
     def test_simple(self):
@@ -390,6 +404,137 @@
         shutdown()
 
 
+class TestCheck:
+
+    def test_raises_false_no_difference(self):
+        with LogCapture() as log:
+            root.info('hello')
+        compare(log.check(('root', 'INFO', 'hello'), raises=False), 
expected=None)
+
+    def test_raises_false_with_difference(self):
+        with LogCapture() as log:
+            root.info('hello')
+        compare(log.check(('root', 'INFO', 'world'), raises=False), expected=(
+            "sequence not as expected:\n\n"
+            "same:\n()\n\n"
+            "expected:\n(('root', 'INFO', 'world'),)\n\n"
+            "actual:\n(('root', 'INFO', 'hello'),)"
+        ))
+
+    def test_level(self):
+        with LogCapture() as log:
+            root.debug('noisy')
+            root.info('one')
+            root.error('two')
+        log.check(
+            ('root', 'INFO', 'one'),
+            ('root', 'ERROR', 'two'),
+            level=INFO,
+        )
+
+    def test_level_order_doesnt_matter(self):
+        with LogCapture() as log:
+            root.debug('noisy')
+            root.error('two')
+            root.info('one')
+        log.check(
+            ('root', 'INFO', 'one'),
+            ('root', 'ERROR', 'two'),
+            level=INFO,
+            order_matters=False,
+        )
+
+    def test_level_keeps_none_level(self):
+        source = LevellessSource()
+        with LogCapture(source) as log:
+            source.log('custom', 'a message')
+        log.check(('custom', 'a message'), level=ERROR)
+
+    def test_predicate(self):
+        with LogCapture() as log:
+            root.info('one')
+            root.error('two')
+        log.check(
+            ('root', 'ERROR', 'two'),
+            predicate=lambda entry: entry.level is not None and entry.level >= 
ERROR,
+        )
+
+    def test_level_and_predicate_compose(self):
+        with LogCapture() as log:
+            root.debug('skip: too low')
+            root.info('keep')
+            root.warning('skip: predicate')
+        log.check(
+            ('root', 'INFO', 'keep'),
+            level=INFO,
+            predicate=lambda entry: 'skip' not in entry.actual[2],
+        )
+
+    def test_excluded_entries_not_marked_checked(self):
+        log_capture = LogCapture(ensure_checks_above=ERROR)
+        root.info('info')
+        root.error('boom')
+        log_capture.uninstall()
+        log_capture.check(
+            ('root', 'INFO', 'info'),
+            predicate=lambda entry: entry.level is not None and entry.level < 
ERROR,
+        )
+        with ShouldAssert("Not asserted ERROR log(s): [('root', 'ERROR', 
'boom')]"):
+            log_capture.ensure_checked()
+
+
+class TestCheckEmpty:
+
+    def test_empty(self):
+        with LogCapture() as log:
+            pass
+        log.check_empty()
+
+    def test_not_empty(self):
+        with LogCapture() as log:
+            root.info('boom')
+        with ShouldAssert(
+            "sequence not as expected:\n\n"
+            "same:\n()\n\n"
+            "expected:\n()\n\n"
+            "actual:\n(('root', 'INFO', 'boom'),)"
+        ):
+            log.check_empty()
+
+    def test_level_excludes_lower(self):
+        with LogCapture() as log:
+            root.debug('noisy')
+            root.info('chatter')
+        log.check_empty(level=WARNING)
+
+    def test_level_not_empty(self):
+        with LogCapture() as log:
+            root.debug('noisy')
+            root.error('boom')
+        with ShouldAssert(
+            "sequence not as expected:\n\n"
+            "same:\n()\n\n"
+            "expected:\n()\n\n"
+            "actual:\n(('root', 'ERROR', 'boom'),)"
+        ):
+            log.check_empty(level=WARNING)
+
+    def test_predicate_excludes_all(self):
+        with LogCapture() as log:
+            root.info('chatter')
+            root.warning('still fine')
+        log.check_empty(predicate=lambda entry: entry.level is not None and 
entry.level >= ERROR)
+
+    def test_level_and_predicate_compose(self):
+        with LogCapture() as log:
+            root.debug('debug noise')
+            root.error('expected error')
+        log.check_empty(
+            level=WARNING,
+            predicate=lambda entry: 'expected' not in entry.actual[2],
+        )
+
+
 class TestCheckPresent:
 
     def test_order_matters_ok(self):
@@ -673,21 +818,6 @@
                 order_matters=False
             )
 
-    def test_check_raises_false_no_difference(self):
-        with LogCapture() as log:
-            root.info('hello')
-        compare(log.check(('root', 'INFO', 'hello'), raises=False), 
expected=None)
-
-    def test_check_raises_false_with_difference(self):
-        with LogCapture() as log:
-            root.info('hello')
-        compare(log.check(('root', 'INFO', 'world'), raises=False), expected=(
-            "sequence not as expected:\n\n"
-            "same:\n()\n\n"
-            "expected:\n(('root', 'INFO', 'world'),)\n\n"
-            "actual:\n(('root', 'INFO', 'hello'),)"
-        ))
-
     def test_check_present_raises_false_no_difference(self):
         with LogCapture() as log:
             root.info('hello')
@@ -702,3 +832,25 @@
             "expected:\n[('root', 'INFO', 'world')]\n\n"
             "actual:\n[]"
         ))
+
+
+class TestDisabled:
+
+    def test_disabled(self):
+        with LogCapture() as log:
+            root.info('before')
+            with log.disabled():
+                root.info('noise')
+            root.info('after')
+        log.check(
+            ('root', 'INFO', 'before'),
+            ('root', 'INFO', 'after'),
+        )
+
+    def test_disabled_restores_on_exception(self):
+        with LogCapture() as log:
+            with ShouldRaise(ValueError('boom')):
+                with log.disabled():
+                    raise ValueError('boom')
+            root.info('after')
+        log.check(('root', 'INFO', 'after'))
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/tests/test_loguru.py 
new/testfixtures-12.2.0/tests/test_loguru.py
--- old/testfixtures-12.1.0/tests/test_loguru.py        2026-06-15 
17:22:45.000000000 +0200
+++ new/testfixtures-12.2.0/tests/test_loguru.py        2026-06-20 
11:53:06.000000000 +0200
@@ -99,6 +99,26 @@
             logger.warning('captured')
         log.check(('WARNING', 'captured'))
 
+    def test_check_level(self):
+        with LogCapture(LoguruSource()) as log:
+            logger.debug('noisy')
+            logger.info('useful')
+            logger.error('boom')
+        log.check(
+            ('INFO', 'useful'),
+            ('ERROR', 'boom'),
+            level=logging.INFO,
+        )
+
+    def test_check_predicate_on_raw(self):
+        with LogCapture(LoguruSource()) as log:
+            logger.bind(user='admin').warning('deleted a record')
+            logger.warning('cache expired')
+        log.check(
+            ('WARNING', 'deleted a record'),
+            predicate=lambda entry: 'user' in entry.raw['extra'],
+        )
+
     def test_str(self):
         with LogCapture(LoguruSource()) as log:
             logger.info('hello world')
diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' 
'--exclude=.svnignore' old/testfixtures-12.1.0/tests/test_structlog.py 
new/testfixtures-12.2.0/tests/test_structlog.py
--- old/testfixtures-12.1.0/tests/test_structlog.py     2026-06-15 
17:22:45.000000000 +0200
+++ new/testfixtures-12.2.0/tests/test_structlog.py     2026-06-20 
11:53:06.000000000 +0200
@@ -82,6 +82,28 @@
             structlog.get_logger().warning('captured')
         log.check(('WARNING', 'captured'))
 
+    def test_check_level(self):
+        logger = structlog.get_logger()
+        with LogCapture(StructlogSource()) as log:
+            logger.debug('noisy')
+            logger.info('useful')
+            logger.error('boom')
+        log.check(
+            ('INFO', 'useful'),
+            ('ERROR', 'boom'),
+            level=logging.INFO,
+        )
+
+    def test_check_predicate_on_raw(self):
+        logger = structlog.get_logger()
+        with LogCapture(StructlogSource()) as log:
+            logger.info('request handled', path='/api/users')
+            logger.info('request handled', path='/health')
+        log.check(
+            ('INFO', 'request handled'),
+            predicate=lambda entry: entry.raw.get('path') != '/health',
+        )
+
     def test_broken_attribute_method(self):
         def bad(event_dict):
             raise ValueError('boom')

Reply via email to