gnucash stable: Multiple changes pushed

John Ralls jralls at code.gnucash.org
Fri Sep 25 19:12:43 EDT 2026


Updated	 via  https://github.com/Gnucash/gnucash/commit/3cf1352c (commit)
	 via  https://github.com/Gnucash/gnucash/commit/0dc41c12 (commit)
	from  https://github.com/Gnucash/gnucash/commit/fbcb9f5e (commit)



commit 3cf1352cbb2aa8405bbd6df38c047bf3cee9f938
Merge: fbcb9f5e75 0dc41c1212
Author: John Ralls <jralls at ceridwen.us>
Date:   Fri Sep 25 16:10:42 2026 -0700

    Merge Noah Noerr's 'python-guard-price-set-source-string' into stable.


commit 0dc41c1212028c163c3717235c182fe34d08ed4c
Author: Noerr <Noerr at users.noreply.github.com>
Date:   Wed Sep 23 02:06:17 2026 -0700

    [bindings/python] Deprecate set_source_string; remove it from the Guile bindings
    
    gnc_price_set_source_string maps a source string to the PriceSource enum and
    silently leaves the source unchanged for anything it doesn't recognize -- a
    silent no-op a naive Python or Guile script can hit. Steer callers to
    set_source() with a PriceSource member (added in #2317) instead.
    
    Python bindings:
    - Deprecate GncPrice.set_source_string(): it now emits a DeprecationWarning
      pointing at set_source(PriceSource.<...>). It still works during the
      deprecation period and still guards -- if the string is not recognized (the
      round-tripped source no longer equals the input) it raises ValueError rather
      than silently doing nothing.
    - Fix the deprecated() decorator in deprecation.py: it referenced the undefined
      DeprecationWarnig and inspect.current_frame(), so it raised instead of
      warning (the one existing @deprecated use was latently broken).
    
    Guile bindings:
    - mark gnc-price-set-source-string deprecated in engine.i.
      (Python and Guile use separate SWIG interface files, so Python is
      handled separately). The only in-tree Guile caller is the QIF importer, updated to
      gnc-price-set-source with PRICE-SOURCE-USER-PRICE; the redundant
      set-source-string in test-engine-extras.scm's fixture helper is dropped.
    
    Tests (test_price_and_wrapping.py):
    - TestSetSourceStringGuard covers the guard (still raises on an unrecognized
      string) and the new DeprecationWarning.
    
    Removing set_source_string from the Python bindings is a follow-up.

diff --git a/bindings/engine.i b/bindings/engine.i
index 6610e6f651..2c42daec20 100644
--- a/bindings/engine.i
+++ b/bindings/engine.i
@@ -56,6 +56,7 @@
 
 #include <numeric>
 #include <unordered_set>
+#include <libguile/deprecation.h>
 %}
 #if defined(SWIGGUILE) //Always C++
 %{
@@ -359,6 +360,14 @@ functions. */
 %include <policy.h>
 %include <gnc-pricedb.h>
 
+%feature("shadow") gnc_price_set_source_string %{
+    (define (gnc_price_set_source_string price str)
+      (issue-deprecation_warning
+          "gnc-price-set-source-string is deprecated and will be removed in GnuCash 6."
+          "Use gnc-price-set-source and the appropriate enum value instead.")
+      ($action price str))
+%}
+
 QofSession * qof_session_new (QofBook* book);
 QofBook * qof_session_get_book (QofSession *session);
 // TODO: Unroll/remove
diff --git a/bindings/guile/test/test-engine-extras.scm b/bindings/guile/test/test-engine-extras.scm
index e641c6a073..778fde2304 100644
--- a/bindings/guile/test/test-engine-extras.scm
+++ b/bindings/guile/test/test-engine-extras.scm
@@ -151,7 +151,6 @@
       (gnc-price-set-currency price currency)
       (gnc-price-set-time64 price time64)
       (gnc-price-set-source price PRICE-SOURCE-XFER-DLG-VAL)
-      (gnc-price-set-source-string price "test-price")
       (gnc-price-set-typestr price "test")
       (gnc-price-set-value price value)
       (gnc-price-commit-edit price)
diff --git a/bindings/python/deprecation.py b/bindings/python/deprecation.py
index 46d0a71f0b..288594bffa 100644
--- a/bindings/python/deprecation.py
+++ b/bindings/python/deprecation.py
@@ -24,10 +24,10 @@ def deprecated(message):
         def wrapper(*args, **kwargs):
             warning_msg = 'Call to deprecated function {}. {}'.format(
                 func.__name__, message)
-            frame = inspect.current_frame().f_back
+            frame = inspect.currentframe().f_back
 
-            warn_explicit(message,
-                          category=DeprecationWarnig,
+            warn_explicit(warning_msg,
+                          category=DeprecationWarning,
                           filename=inspect.getfile(frame.f_code),
                           lineno=frame.f_lineno)
             return func(*args, **kwargs)
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index acd6c1198c..ad15f89ff1 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -744,12 +744,9 @@ class GncPrice(GnuCashCoreClass):
       * currency: the denomination of the value of the item being priced.
       * value: the value of the item being priced.
       * time: the time the price was valid.
-      * source: a string describing the source of the quote. These strings will be something like this:
-      "Finance::Quote", "user:misc", "user:foo", etc. If the quote came from a user, as a matter of policy,
-      you *must* prefix the string you give with "user:". For now, the only other reserved values are
-      "Finance::Quote" and "old-file-import". Any string used must be added to the source_list array in
-      dialog-price-edit-db.c so that it can be properly translated. (There are unfortunately many strings
-      in users' databases, so this string must be translated on output instead of always being used in untranslated form).
+      * source: describes how the price was created. The string form used by
+      set_source_string() / get_source_string() must be one of the recognized
+      PriceSource values; set_source_string() raises ValueError otherwise.
       * type: the type of quote - types possible right now are bid, ask, last, nav, and
       unknown.Each price in the database represents an "instantaneous" quote for a given
       commodity with respect to another commodity.
@@ -760,6 +757,30 @@ class GncPrice(GnuCashCoreClass):
     _new_instance = 'gnc_price_create'
 GncPrice.add_methods_with_prefix('gnc_price_')
 
+# Deprecate set_source_string() in favour of set_source() with a PriceSource
+# member. While it exists, guard it: the C setter maps a fixed set of canonical
+# strings to the PriceSource enum and silently leaves the source unchanged for
+# anything else, so a typo or obsolete string would change nothing -- detect
+# that (the round-tripped source no longer equals the input) and raise instead.
+_gnc_price_set_source_string = GncPrice.set_source_string
+def _deprecated_set_source_string(self, source):
+    """Deprecated: use set_source() with a PriceSource member.
+
+    Still raises ValueError if `source` is not a recognized PriceSource string
+    (the underlying C setter would otherwise leave the source unchanged)."""
+    _gnc_price_set_source_string(self, source)
+    if self.get_source_string() != source:
+        raise ValueError(
+            "%r is not a recognized price source string, so set_source_string() "
+            "left the source unchanged. Use set_source() with a PriceSource "
+            "member, e.g. set_source(PriceSource.FINANCE_QUOTE)." % (source,))
+# Report the deprecation under the public method name, not the wrapper's.
+_deprecated_set_source_string.__name__ = 'set_source_string'
+_deprecated_set_source_string.__qualname__ = 'GncPrice.set_source_string'
+GncPrice.set_source_string = deprecated(
+    "use set_source() with a PriceSource member, e.g. "
+    "price.set_source(PriceSource.FINANCE_QUOTE)")(_deprecated_set_source_string)
+
 
 class GncPriceDB(GnuCashCoreClass):
     '''
diff --git a/bindings/python/tests/test_price_and_wrapping.py b/bindings/python/tests/test_price_and_wrapping.py
index acee15b229..cb60831ba3 100644
--- a/bindings/python/tests/test_price_and_wrapping.py
+++ b/bindings/python/tests/test_price_and_wrapping.py
@@ -488,5 +488,52 @@ class TestPriceSourceEnum(TestCase):
         ses.end()
 
 
+# ---------------------------------------------------------------------------
+# Test: GncPrice.set_source_string -- deprecated, but still guards (raises on
+# an unrecognized source) during the deprecation period
+# ---------------------------------------------------------------------------
+class TestSetSourceStringGuard(PriceSession):
+    """set_source_string() is deprecated in favour of set_source(PriceSource.*),
+    but while it exists it must fail loud on an unrecognized source string
+    instead of silently leaving the source unchanged (the C setter no-ops).
+    These tests ignore the DeprecationWarning and check the guard behaviour;
+    the warning itself is covered by test_emits_deprecation_warning."""
+
+    def setUp(self):
+        super().setUp()
+        # These tests deliberately call the deprecated method.
+        self._warn_ctx = warnings.catch_warnings()
+        self._warn_ctx.__enter__()
+        warnings.simplefilter("ignore", DeprecationWarning)
+
+    def tearDown(self):
+        self._warn_ctx.__exit__(None, None, None)
+        super().tearDown()
+
+    def test_canonical_source_is_accepted(self):
+        self.price1.set_source_string("Finance::Quote")
+        self.assertEqual(self.price1.get_source_string(), "Finance::Quote")
+
+    def test_unknown_source_on_fresh_price_raises(self):
+        price = GncPrice(self.book)
+        with self.assertRaises(ValueError):
+            price.set_source_string("not-a-real-source")
+
+    def test_unknown_source_does_not_clobber_existing(self):
+        # The C setter no-ops on an unknown string; on a price that already has
+        # a valid source that leaves the old value in place (so a bare "is the
+        # source INVALID now?" check would miss it). The guard must still raise.
+        self.price1.set_source_string("Finance::Quote")
+        with self.assertRaises(ValueError):
+            self.price1.set_source_string("totally-bogus")
+        self.assertEqual(self.price1.get_source_string(), "Finance::Quote")
+
+    def test_emits_deprecation_warning(self):
+        with warnings.catch_warnings():
+            warnings.simplefilter("error", DeprecationWarning)
+            with self.assertRaises(DeprecationWarning):
+                self.price1.set_source_string("Finance::Quote")
+
+
 if __name__ == '__main__':
     main()
diff --git a/gnucash/import-export/qif-imp/qif-to-gnc.scm b/gnucash/import-export/qif-imp/qif-to-gnc.scm
index 41a1164a77..b2d1ffa844 100644
--- a/gnucash/import-export/qif-imp/qif-to-gnc.scm
+++ b/gnucash/import-export/qif-imp/qif-to-gnc.scm
@@ -1438,7 +1438,7 @@
     (gnc-price-begin-edit gnc-price)
     (gnc-price-set-commodity gnc-price commodity)
     (gnc-price-set-currency gnc-price default-currency)
-    (gnc-price-set-source-string gnc-price "user:price")
+    (gnc-price-set-source gnc-price PRICE-SOURCE-USER-PRICE)
 
     ;; other options for type are "last" or "nav" which are
     ;;  the last known price for a stock or the net asset value



Summary of changes:
 bindings/engine.i                                |  9 +++++
 bindings/guile/test/test-engine-extras.scm       |  1 -
 bindings/python/deprecation.py                   |  6 +--
 bindings/python/gnucash_core.py                  | 33 ++++++++++++++---
 bindings/python/tests/test_price_and_wrapping.py | 47 ++++++++++++++++++++++++
 gnucash/import-export/qif-imp/qif-to-gnc.scm     |  2 +-
 6 files changed, 87 insertions(+), 11 deletions(-)



More information about the gnucash-changes mailing list