Python bindings -- many methods return raw SWIG pointers instead of wrapped Python objects
array_hourly_0u at icloud.com
array_hourly_0u at icloud.com
Fri Mar 6 16:15:09 EST 2026
Sorry about that previous subject line. :-P
Hi All,
"PR1" in the below plan is addressed with:
https://github.com/Gnucash/gnucash/pull/2186
And I decided to do both "PR2" and "PR3" as:
https://github.com/Gnucash/gnucash/pull/2187
I look forward to your thoughts.
Cheers,
> Noah
> On Feb 9, 2026, at 11:22 AM, array_hourly_0u at icloud.com wrote:
> John,
>> Thanks for the guidance on SWIG typemaps -- while new to me, after
>> some learning, I think that's the right mechanism and I appreciate you
>> pointing me in that direction. The Python bindings barely use input
>> typemaps today, so there's a real opportunity to improve the
>> infrastructure here.
>> After studying the existing typemap patterns in the codebase (the
>> GncOwner input/output typemaps in gnucash_core.i, the time64
>> multi-type acceptance in time64.i, and the GList output typemap in
>> base-typemaps.i), I believe we can do this with no breaking changes
>> and no need for deprecation. The approach is a %typemap(in) for each
>> pointer type that tries the normal SWIG pointer conversion first, and
>> only if that fails, checks for a .instance attribute (which is how the
>> Python wrapper classes store their underlying SWIG pointer). The
>> normal code path has zero overhead -- the fallback only triggers when
>> someone passes a wrapper object to a gnucash_core_c function (and we
>> could introduce a deprecation warning in this pathway).
>> I'm estimating three PRs:
>> PR 1 -- SWIG typemap compatibility layer. A %define macro
>> (GNC_ACCEPT_WRAPPER) in gnucash_core.i that generates input typemaps
>> for each core pointer type (GNCPrice *, gnc_commodity *, Account *,
>> Split *, etc.). This is pure infrastructure -- no behavior changes, no
>> risk. Includes tests verifying that wrapper objects are accepted by
>> gnucash_core_c functions.
>> PR 2 -- Fix the return-type wrapping. Add the missing
>> methods_return_instance dict entries for GncPrice, GncPriceDB,
>> GncCommodity, Account, and GncLot. With the typemaps from PR 1 in
>> place, both old-style (gnucash_core_c direct calls) and new-style
>> (wrapper methods) code works. Includes tests for each newly-wrapped
>> method.
>> PR 3 -- Clean up example scripts. Remove the type(x).__name__ ==
>> 'SwigPyObject' workarounds from the shipped examples
>> (gnc_convenience.py, gncinvoicefkt.py, str_methods.py).
>> PRs 1 and 2 could both target 5.15 (except I have no idea the timeline
>> for that). I'll start with PR 1.
>> 5.16 or later: Optionally remove the fallback to pointer, or just
>> leave it forever since it costs nothing
>> Cheers,
>> Noah
>
>
>
>
>
>
>
>> ----------------------------------------------------------------------
>>
>> Message: 1
>> Date: Sat, 7 Feb 2026 13:56:12 -0800
>> From: array_hourly_0u at icloud.com
>> To: gnucash-devel at gnucash.org
>> Subject: Python bindings -- many methods return raw SWIG pointers
>> instead of wrapped Python objects
>> Message-ID: <D4307C66-C28B-46F0-939B-42856175604F at icloud.com>
>> Content-Type: text/plain; charset="UTF-8"
>>
>> Hi All,
>> I've been a happy GnuCash user for 13 years and am recently finding
>> more time and interest to take on some more advanced use cases and
>> also contribute to the GnuCash project. (and more bandwidth thanks to
>> AI assisted coding)
>>
>> My projects all involve use of the official API python bindings. So
>> you'll see some bug reports, PR, and other chatter from me about that.
>>
>> Here's a matter that's within my skillet to fix, but the best approach
>> is debatable given one route involves breaking changes to the existing
>> python bindings. So I wanted to seek other's opinions.
>>
>> Background
>> -----------------------------
>>
>> The Python bindings use a two-layer architecture: SWIG auto-generates
>> a low-level C API (gnucash_core_c), and gnucash_core.py wraps selected
>> methods so they return proper Python objects (GncPrice, GncCommodity,
>> etc.) instead of raw SWIG pointers. This wrapping is done via
>> methods_return_instance() dicts that map method names to their return
>> types.
>>
>> The problem is that these dicts are incomplete. Many methods that
>> return pointers to GnuCash objects are exposed on the Python classes
>> (via add_methods_with_prefix) but never registered for return-type
>> wrapping. They silently return raw SwigPyObject pointers that have no
>> Python methods and can only be used via gnucash_core_c C-level
>> function calls.
>>
>> This is confusing because the methods *appear* to work -- they're
>> callable and return non-None values -- but the return values are
>> unusable through the documented Python API. There's no error, no
>> warning, and no documentation indicating which methods are wrapped and
>> which aren't. The only way to discover the problem is to inspect
>> type() on the return value.
>>
>> I've done a systematic audit of all classes in gnucash_core.py and
>> gnucash_business.py, cross-referencing the C functions exposed by SWIG
>> against the methods_return_instance dicts, and empirically verified
>> each finding. Below are the results.
>>
>>
>> Affected Classes and Methods
>> -----------------------------
>>
>> 1. GncPrice -- no wrapping dict exists at all
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>
>> GncPrice is the worst case. bindings/python/gnucash_core.py line 741
>> calls add_methods_with_prefix('gnc_price_'), which exposes all
>> gnc_price_* functions as methods. But there is no
>> methods_return_instance call for GncPrice anywhere in that file -- not
>> a single method has its return type wrapped.
>>
>> Method Currently returns Should return
>> ---------------- -------------------------------- ----------------
>> get_commodity() SwigPyObject (raw gnc_commodity *) GncCommodity
>> get_currency() SwigPyObject (raw gnc_commodity *) GncCommodity
>> clone(book) SwigPyObject (raw GNCPrice *) GncPrice
>> get_value() _gnc_numeric (SWIG proxy) GncNumeric
>>
>> get_value() is a partial case -- the _gnc_numeric SWIG proxy is usable
>> via GncNumeric(instance=val), but this is inconsistent with Split and
>> Transaction where equivalent methods (GetAmount, GetValue, GetBalance,
>> etc.) are all wrapped to return GncNumeric directly.
>>
>> Suggested fix:
>>
>> GncPrice.add_methods_with_prefix('gnc_price_')
>>
>> gnc_price_dict = {
>> 'get_commodity': GncCommodity,
>> 'get_currency': GncCommodity,
>> 'clone': GncPrice,
>> 'get_value': GncNumeric,
>> }
>> methods_return_instance(GncPrice, gnc_price_dict)
>>
>>
>> 2. GncPriceDB -- PriceDB_dict incomplete
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>
>> The existing PriceDB_dict wraps lookup_latest,
>> lookup_nearest_in_time64, lookup_nearest_before_t64, and some
>> convert_balance methods. But several methods that return the same
>> types are missing.
>>
>> Missing single-value wrappers (should return GncPrice):
>>
>> Method Currently returns
>> Should return
>> ---------------------------------------- ------------------------
>> -----------
>> nth_price(commodity, n) SwigPyObject (GNCPrice *)
>> GncPrice
>> lookup_day_t64(commodity, currency, date) SwigPyObject (GNCPrice *)
>> GncPrice
>>
>> Missing single-value wrapper (should return GncNumeric):
>>
>> Method Currently returns
>> Should return
>> ----------------------------------------------- --------------------
>> -----------
>> convert_balance_nearest_before_price_t64(...) _gnc_numeric (raw)
>> GncNumeric
>>
>> Its siblings convert_balance_latest_price and
>> convert_balance_nearest_price_t64 are correctly wrapped.
>>
>> Missing list wrappers (should return list of GncPrice):
>>
>> Method Currently returns
>> Should return
>> --------------------------------------------------
>> --------------------- ----------------
>> lookup_latest_any_currency(commodity)
>> list[SwigPyObject] list[GncPrice]
>> lookup_nearest_before_any_currency_t64(comm, date)
>> list[SwigPyObject] list[GncPrice]
>> lookup_nearest_in_time_any_currency_t64(comm, date)
>> list[SwigPyObject] list[GncPrice]
>>
>> Note: get_latest_price, get_nearest_price, and get_nearest_before_price
>> are NOT bugs -- their C functions return gnc_numeric directly (the
>> price value, not a GNCPrice *), so the raw _gnc_numeric return is the
>> correct C type. They should arguably be wrapped to GncNumeric for
>> consistency, but that's a lower priority.
>>
>> Suggested fix:
>>
>> PriceDB_dict = {
>> 'lookup_latest': GncPrice,
>> 'lookup_nearest_in_time64': GncPrice,
>> 'lookup_nearest_before_t64': GncPrice,
>> 'nth_price': GncPrice,
>> 'lookup_day_t64': GncPrice,
>> 'convert_balance_latest_price': GncNumeric,
>> 'convert_balance_nearest_price_t64': GncNumeric,
>> 'convert_balance_nearest_before_price_t64': GncNumeric,
>> 'get_latest_price': GncNumeric,
>> 'get_nearest_price': GncNumeric,
>> 'get_nearest_before_price': GncNumeric,
>> }
>> methods_return_instance(GncPriceDB, PriceDB_dict)
>>
>> methods_return_instance_lists(GncPriceDB, {
>> 'get_prices': GncPrice, # already
>> done
>> 'lookup_latest_any_currency': GncPrice, # new
>> 'lookup_nearest_before_any_currency_t64': GncPrice, # new
>> 'lookup_nearest_in_time_any_currency_t64': GncPrice, # new
>> })
>>
>>
>> 3. GncCommodity -- two missing wrappers
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>
>> GncCommodity.clone is correctly wrapped (gnucash_core.py line 979),
>> but two other methods that return object pointers are not.
>>
>> Method Currently returns Should
>> return
>> ------------------ -----------------------------------------
>> ----------------------
>> obtain_twin(book) SwigPyObject (raw gnc_commodity *)
>> GncCommodity
>> get_namespace_ds() SwigPyObject (raw gnc_commodity_namespace *)
>> GncCommodityNamespace
>>
>> Additionally, get_quote_source() and get_default_quote_source() return
>> raw gnc_quote_source * pointers. However, gnc_quote_source has no
>> Python wrapper class, so these are a deeper design gap rather than a
>> simple omission -- there's nothing to wrap *to*. Currently the only
>> way to use them is via
>> gnucash_core_c.gnc_quote_source_get_internal_name(ptr)
>> etc. A proper fix would require creating a new GncQuoteSource wrapper
>> class.
>>
>>
>> 4. Account -- one missing wrapper
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>
>> Method Currently returns Should
>> return
>> ------------------------ --------------------------------
>> ------------
>> get_currency_or_parent() SwigPyObject (raw gnc_commodity *)
>> GncCommodity
>>
>> The existing account_dict wraps GetCommodity -> GncCommodity but
>> misses get_currency_or_parent, which returns the same C type.
>>
>>
>> 5. GncLot -- two missing wrappers
>> ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>>
>> Method Currently returns Should return
>> ---------------------- --------------------- ---------------
>> get_balance_before(sp) raw _gnc_numeric GncNumeric
>> get_split_list() list[SwigPyObject] list[Split]
>>
>> The existing gnclot_dict wraps get_balance -> GncNumeric but misses
>> get_balance_before. And get_split_list needs
>> method_function_returns_instance_list like Account.GetSplitList and
>> Transaction.GetSplitList already have.
>>
>>
>> The Breaking Change Problem
>> ---------------------------
>>
>> Every fix listed above changes a method's return type from a raw SWIG
>> pointer to a wrapped Python object. These are breaking changes: any
>> existing code that passes these return values to gnucash_core_c
>> C-level functions will break, because those functions expect the raw
>> SWIG pointer, not a wrapper.
>>
>> For example, current workaround code looks like:
>>
>> from gnucash import gnucash_core_c as gc
>>
>> raw_price = pricedb.nth_price(commodity, 0)
>> gc.gnc_price_get_currency(raw_price) # works today
>> gc.gnc_price_get_time64(raw_price)
>>
>> After wrapping nth_price -> GncPrice:
>>
>> price = pricedb.nth_price(commodity, 0) # now returns GncPrice
>> gc.gnc_price_get_currency(price) # BREAKS
>> price.get_currency() # new correct usage
>>
>> The workaround-after-the-fix is to use .instance to extract the raw
>> pointer:
>>
>> gc.gnc_price_get_currency(price.instance) # works
>>
>> How many users are affected?
>>
>> Anyone using these methods today has already discovered the raw-pointer
>> problem through trial and error and written gnucash_core_c workarounds.
>> These workarounds are undocumented and fragile. The "break" moves users
>> from an undocumented workaround to the intended API.
>>
>> That said, the Python bindings have been in this state for years, and
>> scripts using the C-level workarounds do exist in the wild (Stack
>> Overflow answers, wiki examples, personal scripts).
>>
>>
>> Possible Approaches
>> -------------------
>>
>> I'd like the developers' input on how to handle this. Some options:
>>
>> Option A: Fix everything, accept the break
>>
>> Add all missing entries to the methods_return_instance dicts.
>> Document the change in release notes. This is the cleanest long-term
>> outcome but breaks existing workaround code silently (no error --
>> just wrong types passed to C functions, likely causing segfaults or
>> TypeError).
>>
>> Option B: Fix everything, add a compatibility shim
>>
>> Modify method_function_returns_instance (in function_class.py) so
>> that wrapped objects are transparently accepted by gnucash_core_c
>> functions. This could be done by making the wrapper classes implement
>> __swig_convert__ or by patching process_list_convert_to_instance to
>> unwrap at call boundaries. This would make both old and new code
>> work, but adds complexity to the wrapping layer.
>>
>> Option C: Fix only the most impactful methods, leave the rest
>>
>> Prioritize the methods most likely to be encountered by users:
>> - GncPrice.get_commodity(), .get_currency()
>> - GncPriceDB.nth_price()
>> - Account.get_currency_or_parent()
>>
>> Leave edge cases like GncLot.get_balance_before() and
>> GncCommodity.obtain_twin() for later.
>>
>> Option D: Deprecation warnings first, fix later
>>
>> Add runtime deprecation warnings when unwrapped methods are called,
>> pointing users to the upcoming change. Fix the return types in the
>> next major release.
>>
>> ---
>>
>> My preference is Option A with clear release notes because the
>> compatibility shim may be too complex. The current state is a usability
>> trap -- methods look like they work but return unusable objects -- and
>> fixing it benefits all future users even if it inconveniences the few
>> who have written workarounds.
>>
>> I'm happy to submit patches for whichever approach the project prefers.
>>
>>
>> Appendix: Methodology
>> ---------------------
>>
>> All findings were verified empirically on GnuCash 5.14 built from
>> source (Python 3.11, Debian Bookworm, -DWITH_PYTHON=ON
>> -DWITH_GNUCASH=OFF). Each method was called against a test GnuCash
>> SQLite file containing accounts, commodities, and prices. Return types
>> were checked with type() and compared against the C function signatures
>> in gnc-pricedb.h, gnc-commodity.h, Account.h, and gnc-lot.h.
>>
>> The full C API surface was enumerated via dir(gnucash_core_c) and
>> cross-referenced against the methods_return_instance dicts in
>> gnucash_core.py (lines 769-776, 960-974, 984-992, 1011-1020,
>> 1029-1044, 1056-1074, 1085-1114) and gnucash_business.py.
>>
>>
>> Sincerely,
>>
>> Noah Reddell
>>
>>
>> ------------------------------
>>
>> Message: 2
>> Date: Sat, 7 Feb 2026 15:40:07 -0800
>> From: John Ralls <jralls at ceridwen.us>
>> To: array_hourly_0u at icloud.com
>> Cc: gnucash-devel at gnucash.org
>> Subject: Re: Python bindings -- many methods return raw SWIG pointers
>> instead of wrapped Python objects
>> Message-ID: <7597CAE6-455F-4A25-81E0-14B993E148A1 at ceridwen.us>
>> Content-Type: text/plain; charset=utf-8
>>
>>
>>
>> > On Feb 7, 2026, at 13:56, array_hourly_0u at icloud.com wrote:
>> >
>> > Hi All,
>> > I've been a happy GnuCash user for 13 years and am recently finding
>> > more time and interest to take on some more advanced use cases and
>> > also contribute to the GnuCash project. (and more bandwidth thanks to
>> > AI assisted coding)
>> >
>> > My projects all involve use of the official API python bindings. So
>> > you'll see some bug reports, PR, and other chatter from me about that.
>> >
>> > Here's a matter that's within my skillet to fix, but the best approach
>> > is debatable given one route involves breaking changes to the existing
>> > python bindings. So I wanted to seek other's opinions.
>> >
>> > Background
>> > -----------------------------
>> >
>> > The Python bindings use a two-layer architecture: SWIG auto-generates
>> > a low-level C API (gnucash_core_c), and gnucash_core.py wraps selected
>> > methods so they return proper Python objects (GncPrice, GncCommodity,
>> > etc.) instead of raw SWIG pointers. This wrapping is done via
>> > methods_return_instance() dicts that map method names to their return
>> > types.
>> >
>> > The problem is that these dicts are incomplete. Many methods that
>> > return pointers to GnuCash objects are exposed on the Python classes
>> > (via add_methods_with_prefix) but never registered for return-type
>> > wrapping. They silently return raw SwigPyObject pointers that have no
>> > Python methods and can only be used via gnucash_core_c C-level
>> > function calls.
>> >
>> > This is confusing because the methods *appear* to work -- they're
>> > callable and return non-None values -- but the return values are
>> > unusable through the documented Python API. There's no error, no
>> > warning, and no documentation indicating which methods are wrapped and
>> > which aren't. The only way to discover the problem is to inspect
>> > type() on the return value.
>> >
>> > I've done a systematic audit of all classes in gnucash_core.py and
>> > gnucash_business.py, cross-referencing the C functions exposed by SWIG
>> > against the methods_return_instance dicts, and empirically verified
>> > each finding. Below are the results.
>> >
>> >
>> > Affected Classes and Methods
>> > -----------------------------
>> >
>> > 1. GncPrice -- no wrapping dict exists at all
>> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >
>> > GncPrice is the worst case. bindings/python/gnucash_core.py line 741
>> > calls add_methods_with_prefix('gnc_price_'), which exposes all
>> > gnc_price_* functions as methods. But there is no
>> > methods_return_instance call for GncPrice anywhere in that file -- not
>> > a single method has its return type wrapped.
>> >
>> > Method Currently returns Should return
>> > ---------------- -------------------------------- ----------------
>> > get_commodity() SwigPyObject (raw gnc_commodity *) GncCommodity
>> > get_currency() SwigPyObject (raw gnc_commodity *) GncCommodity
>> > clone(book) SwigPyObject (raw GNCPrice *) GncPrice
>> > get_value() _gnc_numeric (SWIG proxy) GncNumeric
>> >
>> > get_value() is a partial case -- the _gnc_numeric SWIG proxy is usable
>> > via GncNumeric(instance=val), but this is inconsistent with Split and
>> > Transaction where equivalent methods (GetAmount, GetValue, GetBalance,
>> > etc.) are all wrapped to return GncNumeric directly.
>> >
>> > Suggested fix:
>> >
>> > GncPrice.add_methods_with_prefix('gnc_price_')
>> >
>> > gnc_price_dict = {
>> > 'get_commodity': GncCommodity,
>> > 'get_currency': GncCommodity,
>> > 'clone': GncPrice,
>> > 'get_value': GncNumeric,
>> > }
>> > methods_return_instance(GncPrice, gnc_price_dict)
>> >
>> >
>> > 2. GncPriceDB -- PriceDB_dict incomplete
>> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >
>> > The existing PriceDB_dict wraps lookup_latest,
>> > lookup_nearest_in_time64, lookup_nearest_before_t64, and some
>> > convert_balance methods. But several methods that return the same
>> > types are missing.
>> >
>> > Missing single-value wrappers (should return GncPrice):
>> >
>> > Method Currently returns
>> > Should return
>> > ---------------------------------------- ------------------------
>> > -----------
>> > nth_price(commodity, n) SwigPyObject (GNCPrice *)
>> GncPrice
>> > lookup_day_t64(commodity, currency, date) SwigPyObject (GNCPrice *)
>> GncPrice
>> >
>> > Missing single-value wrapper (should return GncNumeric):
>> >
>> > Method Currently returns
>> > Should return
>> > ----------------------------------------------- --------------------
>> > -----------
>> > convert_balance_nearest_before_price_t64(...) _gnc_numeric (raw)
>> > GncNumeric
>> >
>> > Its siblings convert_balance_latest_price and
>> > convert_balance_nearest_price_t64 are correctly wrapped.
>> >
>> > Missing list wrappers (should return list of GncPrice):
>> >
>> > Method Currently returns
>> > Should return
>> > --------------------------------------------------
>> > --------------------- ----------------
>> > lookup_latest_any_currency(commodity)
>> > list[SwigPyObject] list[GncPrice]
>> > lookup_nearest_before_any_currency_t64(comm, date)
>> > list[SwigPyObject] list[GncPrice]
>> > lookup_nearest_in_time_any_currency_t64(comm, date)
>> > list[SwigPyObject] list[GncPrice]
>> >
>> > Note: get_latest_price, get_nearest_price, and get_nearest_before_price
>> > are NOT bugs -- their C functions return gnc_numeric directly (the
>> > price value, not a GNCPrice *), so the raw _gnc_numeric return is the
>> > correct C type. They should arguably be wrapped to GncNumeric for
>> > consistency, but that's a lower priority.
>> >
>> > Suggested fix:
>> >
>> > PriceDB_dict = {
>> > 'lookup_latest': GncPrice,
>> > 'lookup_nearest_in_time64': GncPrice,
>> > 'lookup_nearest_before_t64': GncPrice,
>> > 'nth_price': GncPrice,
>> > 'lookup_day_t64': GncPrice,
>> > 'convert_balance_latest_price': GncNumeric,
>> > 'convert_balance_nearest_price_t64': GncNumeric,
>> > 'convert_balance_nearest_before_price_t64': GncNumeric,
>> > 'get_latest_price': GncNumeric,
>> > 'get_nearest_price': GncNumeric,
>> > 'get_nearest_before_price': GncNumeric,
>> > }
>> > methods_return_instance(GncPriceDB, PriceDB_dict)
>> >
>> > methods_return_instance_lists(GncPriceDB, {
>> > 'get_prices': GncPrice, # already
>> done
>> > 'lookup_latest_any_currency': GncPrice, # new
>> > 'lookup_nearest_before_any_currency_t64': GncPrice, # new
>> > 'lookup_nearest_in_time_any_currency_t64': GncPrice, # new
>> > })
>> >
>> >
>> > 3. GncCommodity -- two missing wrappers
>> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >
>> > GncCommodity.clone is correctly wrapped (gnucash_core.py line 979),
>> > but two other methods that return object pointers are not.
>> >
>> > Method Currently returns Should
>> return
>> > ------------------ -----------------------------------------
>> > ----------------------
>> > obtain_twin(book) SwigPyObject (raw gnc_commodity *)
>> GncCommodity
>> > get_namespace_ds() SwigPyObject (raw gnc_commodity_namespace *)
>> > GncCommodityNamespace
>> >
>> > Additionally, get_quote_source() and get_default_quote_source() return
>> > raw gnc_quote_source * pointers. However, gnc_quote_source has no
>> > Python wrapper class, so these are a deeper design gap rather than a
>> > simple omission -- there's nothing to wrap *to*. Currently the only
>> > way to use them is via
>> gnucash_core_c.gnc_quote_source_get_internal_name(ptr)
>> > etc. A proper fix would require creating a new GncQuoteSource wrapper
>> > class.
>> >
>> >
>> > 4. Account -- one missing wrapper
>> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >
>> > Method Currently returns Should
>> return
>> > ------------------------ --------------------------------
>> ------------
>> > get_currency_or_parent() SwigPyObject (raw gnc_commodity *)
>> GncCommodity
>> >
>> > The existing account_dict wraps GetCommodity -> GncCommodity but
>> > misses get_currency_or_parent, which returns the same C type.
>> >
>> >
>> > 5. GncLot -- two missing wrappers
>> > ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
>> >
>> > Method Currently returns Should return
>> > ---------------------- --------------------- ---------------
>> > get_balance_before(sp) raw _gnc_numeric GncNumeric
>> > get_split_list() list[SwigPyObject] list[Split]
>> >
>> > The existing gnclot_dict wraps get_balance -> GncNumeric but misses
>> > get_balance_before. And get_split_list needs
>> > method_function_returns_instance_list like Account.GetSplitList and
>> > Transaction.GetSplitList already have.
>> >
>> >
>> > The Breaking Change Problem
>> > ---------------------------
>> >
>> > Every fix listed above changes a method's return type from a raw SWIG
>> > pointer to a wrapped Python object. These are breaking changes: any
>> > existing code that passes these return values to gnucash_core_c
>> > C-level functions will break, because those functions expect the raw
>> > SWIG pointer, not a wrapper.
>> >
>> > For example, current workaround code looks like:
>> >
>> > from gnucash import gnucash_core_c as gc
>> >
>> > raw_price = pricedb.nth_price(commodity, 0)
>> > gc.gnc_price_get_currency(raw_price) # works today
>> > gc.gnc_price_get_time64(raw_price)
>> >
>> > After wrapping nth_price -> GncPrice:
>> >
>> > price = pricedb.nth_price(commodity, 0) # now returns GncPrice
>> > gc.gnc_price_get_currency(price) # BREAKS
>> > price.get_currency() # new correct usage
>> >
>> > The workaround-after-the-fix is to use .instance to extract the raw
>> > pointer:
>> >
>> > gc.gnc_price_get_currency(price.instance) # works
>> >
>> > How many users are affected?
>> >
>> > Anyone using these methods today has already discovered the raw-pointer
>> > problem through trial and error and written gnucash_core_c workarounds.
>> > These workarounds are undocumented and fragile. The "break" moves users
>> > from an undocumented workaround to the intended API.
>> >
>> > That said, the Python bindings have been in this state for years, and
>> > scripts using the C-level workarounds do exist in the wild (Stack
>> > Overflow answers, wiki examples, personal scripts).
>> >
>> >
>> > Possible Approaches
>> > -------------------
>> >
>> > I'd like the developers' input on how to handle this. Some options:
>> >
>> > Option A: Fix everything, accept the break
>> >
>> > Add all missing entries to the methods_return_instance dicts.
>> > Document the change in release notes. This is the cleanest long-term
>> > outcome but breaks existing workaround code silently (no error --
>> > just wrong types passed to C functions, likely causing segfaults or
>> > TypeError).
>> >
>> > Option B: Fix everything, add a compatibility shim
>> >
>> > Modify method_function_returns_instance (in function_class.py) so
>> > that wrapped objects are transparently accepted by gnucash_core_c
>> > functions. This could be done by making the wrapper classes implement
>> > __swig_convert__ or by patching process_list_convert_to_instance to
>> > unwrap at call boundaries. This would make both old and new code
>> > work, but adds complexity to the wrapping layer.
>> >
>> > Option C: Fix only the most impactful methods, leave the rest
>> >
>> > Prioritize the methods most likely to be encountered by users:
>> > - GncPrice.get_commodity(), .get_currency()
>> > - GncPriceDB.nth_price()
>> > - Account.get_currency_or_parent()
>> >
>> > Leave edge cases like GncLot.get_balance_before() and
>> > GncCommodity.obtain_twin() for later.
>> >
>> > Option D: Deprecation warnings first, fix later
>> >
>> > Add runtime deprecation warnings when unwrapped methods are called,
>> > pointing users to the upcoming change. Fix the return types in the
>> > next major release.
>> >
>> > ---
>> >
>> > My preference is Option A with clear release notes because the
>> > compatibility shim may be too complex. The current state is a usability
>> > trap -- methods look like they work but return unusable objects -- and
>> > fixing it benefits all future users even if it inconveniences the few
>> > who have written workarounds.
>> >
>> > I'm happy to submit patches for whichever approach the project prefers.
>> >
>> >
>> > Appendix: Methodology
>> > ---------------------
>> >
>> > All findings were verified empirically on GnuCash 5.14 built from
>> > source (Python 3.11, Debian Bookworm, -DWITH_PYTHON=ON
>> > -DWITH_GNUCASH=OFF). Each method was called against a test GnuCash
>> > SQLite file containing accounts, commodities, and prices. Return types
>> > were checked with type() and compared against the C function signatures
>> > in gnc-pricedb.h, gnc-commodity.h, Account.h, and gnc-lot.h.
>> >
>> > The full C API surface was enumerated via dir(gnucash_core_c) and
>> > cross-referenced against the methods_return_instance dicts in
>> > gnucash_core.py (lines 769-776, 960-974, 984-992, 1011-1020,
>> > 1029-1044, 1056-1074, 1085-1114) and gnucash_business.py.
>> >
>>
>>
>> Thanks for being willing to take this on.
>>
>> Unfortunately we have no idea how many python bindings users there are
>> nor how sophisticated any of them are about working around the bindings?
>> limitations. Our general policy is that we wouldn?t remove API without at
>> least a release or two worth of notice via deprecation warnings, so if we
>> got deprecations in the upcoming 5.15 release we?d wait until the end of
>> the year to remove the API. It?s also not ideal to deprecate API before the
>> replacement is available making your option B the best choice.
>>
>> The breakage problem seems to me to be what SWIG typemaps are for, and it
>> looks to me like the python bindings don?t make much use of typemaps. Did
>> you consider that and if not can you?
>>
>> Regards,
>> John Ralls
>>
>>
>>
>> ------------------------------
>>
>> Subject: Digest Footer
>>
>> _______________________________________________
>> gnucash-devel mailing list
>> gnucash-devel at gnucash.org
>> https://lists.gnucash.org/mailman/listinfo/gnucash-devel
>>
>>
>> ------------------------------
>>
>> End of gnucash-devel Digest, Vol 20, Issue 7
>> ********************************************
>>
>
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <http://lists.gnucash.org/pipermail/gnucash-devel/attachments/20260306/01c4a715/attachment-0001.htm>
More information about the gnucash-devel
mailing list