gnucash future: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Sun Sep 27 00:22:58 EDT 2026
Updated via https://github.com/Gnucash/gnucash/commit/2765929b (commit)
via https://github.com/Gnucash/gnucash/commit/94c1e445 (commit)
via https://github.com/Gnucash/gnucash/commit/0e302675 (commit)
via https://github.com/Gnucash/gnucash/commit/83a193dd (commit)
via https://github.com/Gnucash/gnucash/commit/7755418c (commit)
via https://github.com/Gnucash/gnucash/commit/d8f0e151 (commit)
via https://github.com/Gnucash/gnucash/commit/5ed56e2b (commit)
via https://github.com/Gnucash/gnucash/commit/5888a04a (commit)
via https://github.com/Gnucash/gnucash/commit/3bbf74a7 (commit)
via https://github.com/Gnucash/gnucash/commit/3cf1352c (commit)
via https://github.com/Gnucash/gnucash/commit/0dc41c12 (commit)
via https://github.com/Gnucash/gnucash/commit/fbcb9f5e (commit)
via https://github.com/Gnucash/gnucash/commit/89448ffd (commit)
via https://github.com/Gnucash/gnucash/commit/973a64ce (commit)
via https://github.com/Gnucash/gnucash/commit/5b687ae1 (commit)
via https://github.com/Gnucash/gnucash/commit/f21fec06 (commit)
from https://github.com/Gnucash/gnucash/commit/670f239e (commit)
commit 2765929b8143f6af07a4530a8de6b674e62898e8
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Sep 26 20:18:01 2026 -0700
Remove deprecated guile and python bindings for gnc_price_set_source_string.
diff --git a/bindings/engine.i b/bindings/engine.i
index b089852ac1..9e9ba9dc4c 100644
--- a/bindings/engine.i
+++ b/bindings/engine.i
@@ -345,16 +345,9 @@ functions. */
%typemap(in) char * action;
%include <policy.h>
+%ignore gnc_price_set_source_string;
%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/python/gnucash_core.i b/bindings/python/gnucash_core.i
index 58d2835113..5deebb067a 100644
--- a/bindings/python/gnucash_core.i
+++ b/bindings/python/gnucash_core.i
@@ -317,6 +317,7 @@ static const GncGUID * gncEntryGetGUID(GncEntry *x);
%include <gncIDSearch.h>
// Commodity prices includes and stuff
+%ignore gnc_price_set_source_string;
%include <gnc-pricedb.h>
%include <cap-gains.h>
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index eb21688d6c..edbfee2091 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -767,31 +767,6 @@ 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):
'''
a simple price database for gnucash.
commit 94c1e4458f2a2a5690b0b97c86116ff2b430d2af
Merge: 670f239e35 0e302675ff
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Sep 26 20:13:32 2026 -0700
Merge branch 'stable' into future
commit 0e302675fff4bf6dccf62ba876c7d306b7152d0e
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Sep 26 12:58:12 2026 -0700
Release GnuCash 5.17
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 46c31fb516..bdb8bc9da3 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -21,7 +21,7 @@ if (POLICY CMP0177)
endif()
project (gnucash
- VERSION 5.16
+ VERSION 5.17
)
enable_testing()
diff --git a/NEWS b/NEWS
index c770f904dd..b97544abd4 100644
--- a/NEWS
+++ b/NEWS
@@ -1,5 +1,98 @@
Version history:
------- -------
+5.17 - 27 Sept 2026
+Between 5.16 and 5.17, the following bugfixes were accomplished:
+
+ Bug 799753 - Reconciliation error when reconciling transactions posted
+ after the reconcile date
+ Bug 799772 - Invoice layout: footer should span both columns and header
+ tables should be easier to align/style
+ Bug 799774 - Import transaction from CSV skips fields if CSV file has
+ backslash just before final quote in a quoted field
+ Bug 797872 - Multiple check printing results in nothing sent to print queue
+ after first check
+ Bug 799790 - RFE: Always put Debits before Credits for CSV register export
+ (order transaction splits as shown on screen)
+ Bug 799791 - Unvoid is not available (Bug from the mailing lists): Use the
+ existence of void timestamp instead of void reason to
+ determine if a transaction is voided. A user can void a
+ transaction without providing a reason.
+ Bug 799798 - bringing back the previous tooltip (from 4.x) as a
+ configurable choice: Enable tooltips on line and bar charts to
+ represent individual points as well as all the points on a
+ particular date.
+ Bug 799799 - Enhancement - making the tooltip more readable
+ Bug 899803 - Split context/category to msgctxt field in gnc-glossary.txt to
+ provide better translation experience Translations aren't
+ changed but are marked fuzzy. Keywords that aren't proper
+ nouns are lower cased, consistent with how words appear in
+ a dictionary.
+ Bug 799806 - Locale "C.UTF-8" crashes the Mortgage and Loan Repayment
+ Assistant
+ Bug 799813 - gnucash-cli issue for price updates using Postgressql back-end
+
+The following fixes and improvements were not associated with bug reports:
+
+ Better handling of stocks that have split in the Show Lots dialog: Lot
+ split transactions are hidden and the changes in the amount of the
+ stock are tracked so that the capital gains calculation can determine
+ the correct basis.
+ Reformat chart tooltips to improve readability.
+ Charts: Add global preferences for marker style and size.
+ Charts: Add global preferences for engagement radius, tooltip pointer size,
+ and the position of grouped tooltips.
+ Charts: Add option to show only non-zero values in tooltips.
+ Cash Flow report: Add option to hide the list of involved accounts at the
+ top of the report
+ Multicolumn Reports: Saving the configuration (and this includes quitting
+ GnuCash with a multicolumn report tab open) now saves the multicolumn
+ report even when the user has clicked through to a subreport.
+ Removed MorningstarUK and MorningstarCH from quote sources, no longer
+ supported by Finance::Quote.
+ AqBanking Import: Add option to import pending transactions
+ Qif Import: Fix YDM date parsing.
+ Edit Exchange Rate: Improve the message and comment explaining why the
+ Transfer Dialog isn't shown when Edit Exchange Rate is selected from
+ the context menu in a stock-type register.
+ Sort Id columns in tree views and QofQuery results naturally, meaning that
+ alpha parts of an id are sorted lexically while numeric parts are
+ sorted numerically, instead of always lexically. For example abc1,
+ abc2, abc15 instead of abc1, abc15, abc2.
+ Windows: Force path strings to UTF-8 instead of the locale's DOS code page
+ to ensure non-ASCII paths are handled correctly.
+ Doxygen: Set header version to VCS version if building from VCS
+ C/C++ API Documentation is now built on every push and uploaded to
+ https://gnucash.github.io/gnucash/Documentation/
+ [import-export/test] add tests for gnc_import_parse_date|numeric
+ [import-parse.cpp] compile regex before trying to parse dates
+ [assistant-stock-transaction] After the stock transaction assistant
+ finishes, open the stock account register and jump to the transaction
+ just created.
+ [stylesheets/footer.scm] add markup "stylesheet-footer" to footer to allow
+ easier css styling
+ [html-chart.scm] Multi-column reports: Isolate each sub-reports variables
+ to eliminate cross-talk between subreports.
+ Replace removed Python 2 C API macros for SWIG 4.5.0 compatibility
+ [gnc-euro.cpp] use GncNumeric.convert() instead of gnc_numeric_convert
+ Update supported platforms in README.
+ [engine] qof_session_end: Ensure the backend lock is removed in all cases.
+ [backend/dbi] skip the libdbi integrity probe for read-only sessions
+ [gnc-euro.cpp] store euro rates as GncNumeric instead of floating point numbers to speed up euro conversion and ensure accuracy
+ [report-utilities.scm] convert account value accumulator to c++
+ [test-report-utilities.scm] add test at date boundaries
+
+The following functions are deprecated:
+
+ Python: GncPrice.set_source_string
+ Guile: gnc-price-set-source-string
+
+In both cases code should use the set_source function that takes an enum instead. The functions will be removed in GnuCash 6.0
+
+New and Updated Translations: Chinese (Simplified Han script), Czech, Dutch,
+English (Australia), English (New Zealand), English (United Kingdom), German,
+Hungarian, Italian, Norwegian Bokmål, Portuguese, Portuguese (Brazil), Russian,
+Spanish, Swedish
+
5.16 - 28 June 2026
Between 5.15 and 5.16, the following bugfixes were accomplished:
commit 83a193dd8dd7706348e143bfb7aee65e0ec30f54
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Aug 20 12:12:10 2026 -0700
Bug 797872 - Multiple check printing results in nothing sent to print queue after first check
diff --git a/gnucash/gnome-utils/print-session.c b/gnucash/gnome-utils/print-session.c
index 5932f6af21..0a4987f9c2 100644
--- a/gnucash/gnome-utils/print-session.c
+++ b/gnucash/gnome-utils/print-session.c
@@ -55,7 +55,12 @@ gnc_print_operation_init(GtkPrintOperation *op, const gchar* jobname)
/* Restore print settings */
G_LOCK(print_settings);
if (print_settings)
+ {
+ // Clear page range to prevent carryover from previous print jobs
+ gtk_print_settings_set(print_settings, GTK_PRINT_SETTINGS_PAGE_RANGES, NULL);
+ gtk_print_settings_set(print_settings, GTK_PRINT_SETTINGS_PAGE_SET, NULL);
gtk_print_operation_set_print_settings(op, print_settings);
+ }
G_UNLOCK(print_settings);
/* Restore page setup */
commit 7755418cb80ff8195ca3918018e6d08244638dfc
Merge: d8f0e151a7 5ed56e2b3d
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Sep 26 11:19:33 2026 -0700
Merge Chris Lam's 'acc-templated-split-iterator' into stable.
commit d8f0e151a7333d587879d2ca6833ade4504539a9
Author: John Ralls <jralls at ceridwen.us>
Date: Sat Sep 26 09:04:12 2026 -0700
engine.i: Fix typo.
diff --git a/bindings/engine.i b/bindings/engine.i
index 2c42daec20..db97ed87e9 100644
--- a/bindings/engine.i
+++ b/bindings/engine.i
@@ -362,7 +362,7 @@ functions. */
%feature("shadow") gnc_price_set_source_string %{
(define (gnc_price_set_source_string price str)
- (issue-deprecation_warning
+ (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))
commit 5ed56e2b3de39c839c1c5552b500fdda459e5d16
Author: Christopher Lam <christopher.lck at gmail.com>
Date: Sat Sep 26 20:41:26 2026 +0800
[engine.i] use gnc_account_foreach_split_between_dates
avoids calling callback on splits before start_date
diff --git a/bindings/engine.i b/bindings/engine.i
index 2c42daec20..862d7f672b 100644
--- a/bindings/engine.i
+++ b/bindings/engine.i
@@ -168,6 +168,8 @@ SplitsVec gnc_get_match_commodity_splits (AccountVec accounts, bool use_end_date
time64 end_date, gnc_commodity *comm, bool sort)
{
SplitsVec rv;
+ std::optional<time64> end;
+ if (use_end_date) end = end_date;
auto maybe_accumulate = [&rv, comm](auto s)
{
@@ -179,15 +181,12 @@ SplitsVec gnc_get_match_commodity_splits (AccountVec accounts, bool use_end_date
rv.push_back (s);
};
- std::function<void(Account*)> scan_account;
- if (use_end_date)
- scan_account = [end_date, maybe_accumulate](auto acc)
- { gnc_account_foreach_split_until_date (acc, end_date, maybe_accumulate); };
- else
- scan_account = [maybe_accumulate](auto acc)
- { gnc_account_foreach_split (acc, maybe_accumulate); };
+ std::for_each (accounts.begin(), accounts.end(), [&](auto acc)
+ {
+ gnc_account_foreach_split_between_dates (acc, {}, end, false,
+ maybe_accumulate);
+ });
- std::for_each (accounts.begin(), accounts.end(), scan_account);
if (sort)
std::sort (rv.begin(), rv.end(), [](auto a, auto b){ return xaccSplitOrder (a, b) < 0; });
return rv;
@@ -257,26 +256,14 @@ gnc_accounts_and_all_descendants (AccountVec accounts)
void
gnc_account_foreach_split_between_dates (const Account* account,
SCM start_date, SCM end_date,
- bool include_children, SCM scm_cb)
+ bool incl_descendants, SCM scm_cb)
{
std::optional<time64> start, end;
if (scm_is_exact_integer (start_date)) start = scm_to_int64 (start_date);
if (scm_is_exact_integer (end_date)) end = scm_to_int64 (end_date);
- auto maybe_call = [&](const Split* s)
- {
- if (!start || *start <= xaccTransGetDate (xaccSplitGetParent (s)))
- scm_call_1 (scm_cb, gnc_split_to_scm (s));
- };
- std::function<void(const Account*)> scan_account;
- if (end)
- scan_account = [end, maybe_call](auto acc)
- { gnc_account_foreach_split_until_date (acc, *end, maybe_call); };
- else
- scan_account = [maybe_call](auto acc)
- { gnc_account_foreach_split (acc, maybe_call); };
- scan_account (account);
- if (include_children)
- gnc_account_foreach_descendant (account, scan_account);
+
+ auto cb = [&](auto s) { scm_call_1 (scm_cb, gnc_split_to_scm (s)); };
+ gnc_account_foreach_split_between_dates (account, start, end, incl_descendants, cb);
}
%}
commit 5888a04a8e9c89a6e6b66eb270d17ede456202d5
Author: Christopher Lam <christopher.lck at gmail.com>
Date: Sat Sep 26 20:41:17 2026 +0800
[account.cpp] use gnc_account_foreach_split_between_dates
diff --git a/libgnucash/engine/Account.cpp b/libgnucash/engine/Account.cpp
index 612fbd7832..89f849386b 100644
--- a/libgnucash/engine/Account.cpp
+++ b/libgnucash/engine/Account.cpp
@@ -1140,26 +1140,14 @@ xaccInitAccount (Account * acc, QofBook *book)
void
gnc_account_foreach_split (const Account *acc, std::function<void(Split*)> func)
{
- if (!GNC_IS_ACCOUNT (acc))
- return;
-
- auto& splits{GET_PRIVATE(acc)->splits};
- std::for_each (splits.begin(), splits.end(), func);
+ gnc_account_foreach_split_between_dates (acc, {}, {}, false, func);
}
void
gnc_account_foreach_split_until_date (const Account *acc, time64 end_date,
std::function<void(Split*)> f)
{
- if (!GNC_IS_ACCOUNT (acc))
- return;
-
- auto after_date = [](time64 end_date, auto s) -> bool
- { return (xaccTransGetDate (xaccSplitGetParent (s)) > end_date); };
-
- auto& splits{GET_PRIVATE(acc)->splits};
- auto after_date_iter = std::upper_bound (splits.begin(), splits.end(), end_date, after_date);
- std::for_each (splits.begin(), after_date_iter, f);
+ gnc_account_foreach_split_between_dates (acc, {}, end_date, false, f);
}
commit 3bbf74a7306698533c3b1b278362089f41f5e5df
Author: Christopher Lam <christopher.lck at gmail.com>
Date: Sat Sep 26 20:40:03 2026 +0800
[account.hpp] introduce gnc_account_foreach_split_between_dates
A templated account->splits iterator, will target splits from
start_date to end_date, inclusive of date boundaries. Also optionally
include descendants.
diff --git a/libgnucash/engine/Account.hpp b/libgnucash/engine/Account.hpp
index 9049cb94b7..dbc88a3f0b 100644
--- a/libgnucash/engine/Account.hpp
+++ b/libgnucash/engine/Account.hpp
@@ -34,8 +34,12 @@
#include <vector>
#include <functional>
+#include <algorithm>
+#include <optional>
#include <Account.h>
+#include <SplitP.hpp>
+#include <TransactionP.hpp>
using SplitsVec = std::vector<Split*>;
using AccountVec = std::vector<Account*>;
@@ -44,6 +48,42 @@ const SplitsVec& xaccAccountGetSplits (const Account*);
void gnc_account_foreach_descendant (const Account *, std::function<void(Account*)> func);
+
+static inline SplitsVec::const_iterator
+splits_start (const SplitsVec& splits, std::optional<time64> start_date)
+{
+ if (!start_date) return splits.begin();
+ return std::lower_bound (splits.begin(), splits.end(), *start_date,
+ [](auto s, time64 t){ return s->parent->date_posted < t; });
+}
+
+static inline SplitsVec::const_iterator
+splits_end (const SplitsVec& splits, std::optional<time64> end_date)
+{
+ if (!end_date) return splits.end();
+ return std::upper_bound (splits.begin(), splits.end(), *end_date,
+ [](time64 t, auto s){ return t < s->parent->date_posted; });
+}
+
+template <typename Fn>
+void gnc_account_foreach_split_between_dates (const Account* account,
+ std::optional<time64> start_date,
+ std::optional<time64> end_date,
+ bool include_descendants, Fn&& fn)
+{
+ g_return_if_fail (GNC_IS_ACCOUNT (account));
+ auto scan_account = [&](const Account* acc)
+ {
+ const auto& splits = xaccAccountGetSplits (acc);
+ std::for_each (splits_start (splits, start_date), splits_end (splits, end_date),
+ fn);
+ };
+
+ scan_account (account);
+ if (include_descendants)
+ gnc_account_foreach_descendant (account, scan_account);
+}
+
void gnc_account_foreach_split (const Account*, std::function<void(Split*)>);
void gnc_account_foreach_split_until_date (const Account *acc, time64 end_date,
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
commit fbcb9f5e756321a161a1ecf478a81d866b1d03a8
Merge: 973a64ceda 89448ffd34
Author: John Ralls <jralls at ceridwen.us>
Date: Fri Sep 25 14:38:24 2026 -0700
Merge Noah Noerr's 'bug799796-price-example-getvalue' into stable.
commit 89448ffd3420ae563a4fece75b9e6ec9a9db5927
Author: Noerr <Noerr at users.noreply.github.com>
Date: Wed Sep 23 01:05:21 2026 -0700
[bindings/python] Fix and modernize the price example scripts (Bug 799796)
PR #2187 made GncPrice.get_value() return a GncNumeric wrapper and clone() a
fully wrapped GncPrice. The price example scripts still used the pre-5.15 raw
_gnc_numeric idiom and broke -- reported as Bug 799796 (cloned prices silently
kept the original value).
Example scripts (bindings/python/example_scripts/):
* quotes_historic.py -- reworked to the modern idiom; it now actually runs on
5.x (it had further latent breakage beyond the reported one):
- Construct each price directly with GncPrice(book) + setters instead of
cloning an existing price, so the script no longer needs a pre-existing
price in the book. Look the pricing currency (USD) up from the commodity
table rather than deriving it from a split.
- Build the value with the double->GncNumeric constructor at GnuCash's
standard price precision -- the pricing currency's SCU * COMMODITY_DENOM_MULT
rounded HALF_UP (gnc-pricedb.h "Price policy") -- dropping the
Fraction.from_float().limit_denominator(100000) dance and its import.
- Set the source with the PriceSource enum -- set_source(PriceSource.FINANCE_QUOTE)
(the enum added in #2317). The original set_source("Finance::Quotes::Historic")
passed a string to what has been an enum setter since 2.6, which raises
TypeError on modern bindings.
- Open with Session(url), which defaults to SESSION_NORMAL_OPEN. The old
Session(url, True, False, False) passed the deprecated ignore_lock=True,
silently bypassing the book's lock file.
* priceDB_test.py, price_database_example.py -- num/denom are methods now, so
read v.num() / v.denom().
Prevent silent recurrence (gnucash_core.py):
The write path failed *silently*, which is how this slipped through review and
CI. num/denom are read-only accessor methods, so assigning to them merely
shadows the method and changes nothing. Add a GncNumeric.__setattr__ that raises
AttributeError on assignment to num/denom, pointing at the correct idiom
GncNumeric(num, denom).
Test (bindings/python/tests/test_numeric.py):
New test_num_denom_read_only covering the guard. It needs no backend, so it runs
in the existing python-bindings CTest on all platforms.
The wiki page Stocks/get_prices, which the reporter copied from, is being
updated separately.
diff --git a/bindings/python/example_scripts/priceDB_test.py b/bindings/python/example_scripts/priceDB_test.py
index 1e3cd6b288..dd4e458d47 100644
--- a/bindings/python/example_scripts/priceDB_test.py
+++ b/bindings/python/example_scripts/priceDB_test.py
@@ -41,12 +41,12 @@ for pr in pl:
source = pr.get_source()
time = pr.get_time64()
v=pr.get_value()
- price = float(v.num)/v.denom
+ price = float(v.num())/v.denom()
print(time, source, price)
if len(pl) > 0:
v0 = pl[0].get_value()
- print(arm.get_fullname(), float(v0.num) / float(v0.denom ))
+ print(arm.get_fullname(), float(v0.num()) / float(v0.denom()))
session.end()
session.destroy()
diff --git a/bindings/python/example_scripts/price_database_example.py b/bindings/python/example_scripts/price_database_example.py
index 032f973859..b8af8dd41a 100755
--- a/bindings/python/example_scripts/price_database_example.py
+++ b/bindings/python/example_scripts/price_database_example.py
@@ -82,7 +82,7 @@ for namespace in namespaces:
source = pr.get_source()
time = pr.get_time64()
v=pr.get_value()
- price = float(v.num)/v.denom
+ price = float(v.num())/v.denom()
print("{0} {1:20}{2:10.4f} {3}".format(time,source,price,cur_name))
# I didn't find out how to format the time option...
diff --git a/bindings/python/example_scripts/quotes_historic.py b/bindings/python/example_scripts/quotes_historic.py
index 77781b30ac..5996120c30 100644
--- a/bindings/python/example_scripts/quotes_historic.py
+++ b/bindings/python/example_scripts/quotes_historic.py
@@ -17,10 +17,9 @@
# https://wiki.gnucash.org/wiki/Stocks/get_prices
#
-from gnucash import Session, Account, Split
-import gnucash
+from gnucash import Session, GncNumeric, GncPrice, PriceSource, GNC_HOW_DENOM_FIXED, GNC_HOW_RND_ROUND_HALF_UP
+from gnucash.gnucash_core_c import COMMODITY_DENOM_MULT
import datetime
-from fractions import Fraction
from gnc_convenience import find_account
FILE = "./test.gnucash"
@@ -48,42 +47,35 @@ for i in range(1,len(data)):
stock_price.append(float(data[i].rsplit(',')[5]))
# Initialize Gnucash session
-session = Session(url, True, False, False)
+session = Session(url)
root = session.book.get_root_account()
book = session.book
account = book.get_root_account()
pdb = book.get_price_db()
comm_table = book.get_table()
-ac = find_account(account,'Intel')[0]
+# This example assumes the Intel holding is priced in USD.
+ac = find_account(account, 'Intel')[0]
stock = ac.GetCommodity()
-# Add the prices
-pdb = book.get_price_db()
-if len(ac.GetSplitList())<1:
- print('Need at least one Split to get currency info ... ')
- raise SystemExit
-cur = ac.GetSplitList()[0].GetParent().GetCurrency()
-
-# Get stock data
-pl = pdb.get_prices(stock,cur)
-if len(pl)<1:
- print('Need at least one database entry to clone ...')
- raise SystemExit
-
-pl0 = pl[0]
-for i in range(1,len(pl)):
- pdb.remove_price(pl[i])
+cur = comm_table.lookup("CURRENCY", "USD")
-for i in range(0,len(stock_date)):
- p_new = pl0.clone(book)
- p_new = gnucash.GncPrice(instance=p_new)
- print('Adding',i,stock_date[i],stock_price[i])
+# Construct the price at the precision GnuCash uses for a commodity quoted in a
+# currency: the currency's smallest unit (its SCU -- get_fraction() is 100 for
+# USD, i.e. cents) times COMMODITY_DENOM_MULT (10000), the extra precision a
+# price carries beyond an ordinary amount. See "Price policy" in gnc-pricedb.h.
+price_denom = cur.get_fraction() * COMMODITY_DENOM_MULT
+for i in range(0, len(stock_date)):
+ print('Adding', i, stock_date[i], stock_price[i])
+ # --- Create a fresh price object with all properties set ------------------
+ p_new = GncPrice(book)
+ p_new.set_commodity(stock)
+ p_new.set_currency(cur)
p_new.set_time64(stock_date[i])
- v = p_new.get_value()
- v.num = int(Fraction.from_float(stock_price[i]).limit_denominator(100000).numerator)
- v.denom = int(Fraction.from_float(stock_price[i]).limit_denominator(100000).denominator)
- p_new.set_value(v)
- p_new.set_source("Finance::Quotes::Historic")
+ p_new.set_value(GncNumeric(stock_price[i], price_denom,
+ GNC_HOW_DENOM_FIXED | GNC_HOW_RND_ROUND_HALF_UP))
+ p_new.set_source(PriceSource.FINANCE_QUOTE)
+ p_new.set_typestr("last") # bid / ask / last / nav / unknown
+ # --------------------------------------------------------------------------
pdb.add_price(p_new)
# Clean up
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index acd6c1198c..9bf5a8d95a 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -564,6 +564,16 @@ class GncNumeric(GnuCashCoreClass):
kargs['instance'] = GncNumeric.__args_to_instance(args)
GnuCashCoreClass.__init__(self, [], **kargs)
+ def __setattr__(self, name, value):
+ # num and denom are read-only accessor methods, not writable fields.
+ if name in ('num', 'denom'):
+ raise AttributeError(
+ "Cannot assign to '%s': it is a read-only accessor method on "
+ "GncNumeric, not a writable field, so the assignment would not "
+ "change the value. Construct a new value instead, e.g. "
+ "GncNumeric(numerator, denominator)." % name)
+ super().__setattr__(name, value)
+
@staticmethod
def __args_to_instance(args):
if len(args) == 0:
diff --git a/bindings/python/tests/test_numeric.py b/bindings/python/tests/test_numeric.py
index df16749fd9..654f497e3e 100644
--- a/bindings/python/tests/test_numeric.py
+++ b/bindings/python/tests/test_numeric.py
@@ -98,5 +98,18 @@ class TestGncNumeric(TestCase):
with self.assertRaises(TypeError):
GncNumeric(complex(1, 1))
+ def test_num_denom_read_only(self):
+ # num/denom are read-only accessor methods, not writable fields.
+ # Assigning to them used to silently do nothing and corrupt data
+ # (Bug 799796); it must now raise instead.
+ num = GncNumeric(1, 2)
+ with self.assertRaises(AttributeError):
+ num.num = 5
+ with self.assertRaises(AttributeError):
+ num.denom = 5
+ # The value is unchanged and the accessors still work.
+ self.assertEqual(num.num(), 1)
+ self.assertEqual(num.denom(), 2)
+
if __name__ == '__main__':
main()
commit 973a64ceda5a2f0201472caf9c3a6553c857d2c6
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Sep 24 15:58:38 2026 -0700
Correct the summary and description for the chart-tooltip-position preference schema.
Thanks to Simon Arlott for noticing and reporting it.
diff --git a/gnucash/gschemas/org.gnucash.GnuCash.gschema.xml.in b/gnucash/gschemas/org.gnucash.GnuCash.gschema.xml.in
index 495f8200db..19c3e0c863 100644
--- a/gnucash/gschemas/org.gnucash.GnuCash.gschema.xml.in
+++ b/gnucash/gschemas/org.gnucash.GnuCash.gschema.xml.in
@@ -409,8 +409,8 @@ For example setting this to 2.0 will display reports at twice their typical size
</key>
<key name="chart-tooltip-position" enum="org.gnucash.GnuCash.general.report.TooltipPosition">
<default>'nearest'</default>
- <summary>"Jump" behaviour when there are multiple splits</summary>
- <description>Select how the "Jump" operation should behave when a transaction has multiple splits.</description>
+ <summary>A choice of two locations for a grouped tooltip. </summary>
+ <description>Some line- and barcharts display a tooltip showing numeric values of the points on a particular date. The tooltip can be a grouped one showing all of the points on that particular day or not-grouped, showing only the value at the nearest point. This preference controls the position of grouped tooltips. The average setting places it in the middle of the values on the day, nearest places it at the point nearest to the cursor position. </description>
</key>
<child name="pdf-export" schema="org.gnucash.GnuCash.general.report.pdf-export"/>
</schema>
commit 5b687ae193648337f8b602a1956cb09c97f804c9
Merge: c8317b15a0 f21fec06be
Author: John Ralls <jralls at ceridwen.us>
Date: Thu Sep 24 13:26:12 2026 -0700
Merge Noah Noerr's 'python-pricesource-enum' into stable.
commit f21fec06be967c4017ba7538c7ea28bb6dfddfa7
Author: Noerr <Noerr at users.noreply.github.com>
Date: Thu Sep 24 00:06:16 2026 -0700
[bindings/python] Add a PriceSource enum for gnc_price_set_source()
Setting a price's source from Python meant reaching into gnucash_core_c for the
raw PRICE_SOURCE_* constants. Add a PriceSource(IntEnum) alongside SessionOpenMode,
with values sourced from the C enum (via gnucash_core_c) so they can't drift, so a
script can write price.set_source(PriceSource.FINANCE_QUOTE) with a plain top-level
import.
Add test_price_and_wrapping.TestPriceSourceEnum:
- test_values_match_c_enum asserts the Python enum covers the C PRICE_SOURCE_*
value set exactly, so adding, removing, or renumbering a C value fails CI until
PriceSource is updated to match. (Member names are a Python-side choice --
FINANCE_QUOTE spells out FQ -- so the check is on values.)
- test_set_source_accepts_enum confirms a member flows through set_source() and
round-trips.
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 563ceee0d8..acd6c1198c 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -275,6 +275,25 @@ class SessionOpenMode(IntEnum):
Open the session, taking over any existing lock."""
+class PriceSource(IntEnum):
+ """Price source, mirroring the C PriceSource enum in gnc-pricedb.h.
+
+ Pass a member to GncPrice.set_source().
+ """
+
+ EDIT_DLG = gnucash_core_c.PRICE_SOURCE_EDIT_DLG
+ FINANCE_QUOTE = gnucash_core_c.PRICE_SOURCE_FQ
+ USER_PRICE = gnucash_core_c.PRICE_SOURCE_USER_PRICE
+ XFER_DLG_VAL = gnucash_core_c.PRICE_SOURCE_XFER_DLG_VAL
+ SPLIT_REG = gnucash_core_c.PRICE_SOURCE_SPLIT_REG
+ SPLIT_IMPORT = gnucash_core_c.PRICE_SOURCE_SPLIT_IMPORT
+ STOCK_SPLIT = gnucash_core_c.PRICE_SOURCE_STOCK_SPLIT
+ STOCK_TRANSACTION = gnucash_core_c.PRICE_SOURCE_STOCK_TRANSACTION
+ INVOICE = gnucash_core_c.PRICE_SOURCE_INVOICE
+ TEMP = gnucash_core_c.PRICE_SOURCE_TEMP
+ INVALID = gnucash_core_c.PRICE_SOURCE_INVALID
+
+
class Session(GnuCashCoreClass):
"""A GnuCash book editing session
diff --git a/bindings/python/tests/test_price_and_wrapping.py b/bindings/python/tests/test_price_and_wrapping.py
index b889b7a8c6..acee15b229 100644
--- a/bindings/python/tests/test_price_and_wrapping.py
+++ b/bindings/python/tests/test_price_and_wrapping.py
@@ -14,6 +14,7 @@ from gnucash import (
GncCommodity,
GncNumeric,
GncPrice,
+ PriceSource,
Session,
Split,
Transaction,
@@ -453,5 +454,39 @@ class TestDoubleWrapProtection(TestCase):
self.assertEqual(val.denom(), 2)
+# ---------------------------------------------------------------------------
+# Test: PriceSource enum stays in sync with the C PRICE_SOURCE_* enum
+# ---------------------------------------------------------------------------
+class TestPriceSourceEnum(TestCase):
+ """PriceSource must cover the C PRICE_SOURCE_* enum exactly, so it can't
+ drift if a value is added, removed, or renumbered on the C side. Member
+ names are a Python-side choice (e.g. FINANCE_QUOTE spells out FQ), so the
+ check is on the set of values, which come straight from the C enum."""
+
+ def test_values_match_c_enum(self):
+ from gnucash import gnucash_core_c as c
+ c_values = sorted(getattr(c, n) for n in dir(c)
+ if n.startswith("PRICE_SOURCE_"))
+ py_values = sorted(m.value for m in PriceSource)
+ self.assertEqual(py_values, c_values,
+ "PriceSource is out of sync with the C PRICE_SOURCE_* "
+ "enum -- add/remove members to match.")
+
+ def test_set_source_accepts_enum(self):
+ ses = Session()
+ book = ses.get_book()
+ table = book.get_table()
+ usd = table.lookup("CURRENCY", "USD")
+ stock = GncCommodity(book, "Test Stock", "NASDAQ", "TSTK", "TSTK", 10000)
+ table.insert(stock)
+ price = GncPrice(book)
+ price.set_commodity(stock)
+ price.set_currency(usd)
+ price.set_source(PriceSource.FINANCE_QUOTE)
+ self.assertEqual(price.get_source(), PriceSource.FINANCE_QUOTE)
+ self.assertEqual(price.get_source_string(), "Finance::Quote")
+ ses.end()
+
+
if __name__ == '__main__':
main()
Summary of changes:
NEWS | 93 ++++++++++++++++++++++
bindings/engine.i | 37 +++------
bindings/guile/test/test-engine-extras.scm | 1 -
bindings/python/deprecation.py | 6 +-
bindings/python/example_scripts/priceDB_test.py | 4 +-
.../example_scripts/price_database_example.py | 2 +-
bindings/python/example_scripts/quotes_historic.py | 52 +++++-------
bindings/python/gnucash_core.i | 1 +
bindings/python/gnucash_core.py | 39 +++++++--
bindings/python/tests/test_numeric.py | 13 +++
bindings/python/tests/test_price_and_wrapping.py | 82 +++++++++++++++++++
gnucash/gnome-utils/print-session.c | 5 ++
.../gschemas/org.gnucash.GnuCash.gschema.xml.in | 4 +-
gnucash/import-export/qif-imp/qif-to-gnc.scm | 2 +-
libgnucash/engine/Account.cpp | 16 +---
libgnucash/engine/Account.hpp | 40 ++++++++++
16 files changed, 312 insertions(+), 85 deletions(-)
More information about the gnucash-changes
mailing list