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, [email protected] 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: [email protected]
> To: [email protected]
> Subject: Python bindings -- many methods return raw SWIG pointers
>         instead of wrapped Python objects
> Message-ID: <[email protected]>
> 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 <[email protected]>
> To: [email protected]
> Cc: [email protected]
> Subject: Re: Python bindings -- many methods return raw SWIG pointers
>         instead of wrapped Python objects
> Message-ID: <[email protected]>
> Content-Type: text/plain;       charset=utf-8
>
>
>
> > On Feb 7, 2026, at 13:56, [email protected] 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
> [email protected]
> https://lists.gnucash.org/mailman/listinfo/gnucash-devel
>
>
> ------------------------------
>
> End of gnucash-devel Digest, Vol 20, Issue 7
> ********************************************
>
_______________________________________________
gnucash-devel mailing list
[email protected]
https://lists.gnucash.org/mailman/listinfo/gnucash-devel

Reply via email to