Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions cumulusci/utils/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,14 @@
import requests
import sarge

if not hasattr(sarge.Capture, "flush"):
# sarge==0.1.7.post1 ships Capture without flush(); on Python 3.13+ the
# interpreter-shutdown logging path calls flush() on captured streams and
# surfaces a cosmetic AttributeError after refresh_oauth_token. Upstream
# has the fix on master but no release. Defensive, idempotent shim.
# See SFDO-Tooling/CumulusCI#3852.
sarge.Capture.flush = lambda self: None

from cumulusci.core.exceptions import CumulusCIException
from .xml import ( # noqa
elementtree_parse_file,
Expand Down
34 changes: 34 additions & 0 deletions cumulusci/utils/tests/test_sarge_patch.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
"""Regression test for SFDO-Tooling/CumulusCI#3852.

``sarge==0.1.7.post1`` (the version pinned via ``pyproject.toml``) ships a
``sarge.Capture`` class with no ``flush()`` method. On Python 3.13+ the
interpreter-shutdown logging path calls ``.flush()`` on the captured stream
objects CumulusCI hands to logging handlers, which surfaces a cosmetic
``AttributeError: 'Capture' object has no attribute 'flush'`` after
``refresh_oauth_token`` runs.

The upstream sarge fix (``def flush(self): pass``) is unreleased, so
``cumulusci.utils`` installs a defensive shim at import time. This test
guards that shim.
"""

import sarge

import cumulusci.utils # noqa: F401 -- importing applies the Capture.flush shim


def test_sarge_capture_has_flush_after_importing_cumulusci_utils():
assert hasattr(sarge.Capture, "flush"), (
"sarge.Capture is missing flush(); CumulusCI must patch it to avoid "
"AttributeError during interpreter-shutdown logging on Python 3.13+ "
"(see SFDO-Tooling/CumulusCI#3852)."
)
assert callable(sarge.Capture.flush)


def test_sarge_capture_instance_flush_is_a_no_op():
capture = sarge.Capture()
try:
assert capture.flush() is None
finally:
capture.close()
Loading