Files
gnucash/bindings/python/example_scripts/priceDB_test.py
T
Noerr 89448ffd34 [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.
2026-09-24 22:08:37 -07:00

54 lines
1.7 KiB
Python

#!/usr/bin/env python3
# Test file for price database stuff
# To update the price database call
# $PATH/gnucash-cli --quotes get $PATHTOFILE
# before running this.
# Adding to a calling bash script would be better
# Although calling it from here would be even better!
# OR: export PYTHONPATH=<path-to-gnucash-inst-dir>/lib/python3.7/site-packages:$PYTHONPATH
# You may have to adjust the above path to your local system (lib->lib64, python3.7->...)
# Then: ipython3
# The account file is not saved but always use a disposable copy.
# Change, FILE, CURRENCY and STOCK to those defined in your test account.
## @file
# @brief Test file for price database stuff
# @author Mike Evans
# @ingroup python_bindings_examples
from gnucash import Session
# FILE should be the path to your existing gnucash file/database
# For a file, simply pass the pathname, for a database you can use
# these forms:
# mysql://user:password@host/dbname
# postgres://user:password@host[:port]/dbname (the port is optional)
#
FILE = "PATH_TO_YOUR_TEST_FILE" ## Fail is not saved but use a copy anyway
session = Session(FILE, True, False, False)
root = session.book.get_root_account()
book = session.book
pdb = book.get_price_db()
comm_table = book.get_table()
gbp = comm_table.lookup("CURRENCY", "SOME_CURRENCY")
arm = comm_table.lookup("NASDAQ", "SOME_STOCK")
latest = pdb.lookup_latest(arm,gbp) # from the table, NOT live data
value = latest.get_value()
pl = pdb.get_prices(arm,gbp)
for pr in pl:
source = pr.get_source()
time = pr.get_time64()
v=pr.get_value()
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()))
session.end()
session.destroy()
quit()