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 #include +#include %} #if defined(SWIGGUILE) //Always C++ %{ @@ -359,6 +360,14 @@ functions. */ %include %include +%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 9bf5a8d95a..eb21688d6c 100644 --- a/bindings/python/gnucash_core.py +++ b/bindings/python/gnucash_core.py @@ -754,12 +754,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. @@ -770,6 +767,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