gnucash master: Multiple changes pushed
John Ralls
jralls at code.gnucash.org
Mon Jul 6 15:59:08 EDT 2020
Updated via https://github.com/Gnucash/gnucash/commit/b0b23895 (commit)
via https://github.com/Gnucash/gnucash/commit/22f91c40 (commit)
via https://github.com/Gnucash/gnucash/commit/40cfb70f (commit)
via https://github.com/Gnucash/gnucash/commit/e23bf0bc (commit)
via https://github.com/Gnucash/gnucash/commit/7c8e0a28 (commit)
via https://github.com/Gnucash/gnucash/commit/3e842a7b (commit)
via https://github.com/Gnucash/gnucash/commit/b9c6fc28 (commit)
via https://github.com/Gnucash/gnucash/commit/0434acbe (commit)
via https://github.com/Gnucash/gnucash/commit/485d8a65 (commit)
via https://github.com/Gnucash/gnucash/commit/44e61f4d (commit)
via https://github.com/Gnucash/gnucash/commit/5833c5af (commit)
via https://github.com/Gnucash/gnucash/commit/17d606e1 (commit)
via https://github.com/Gnucash/gnucash/commit/c222503f (commit)
via https://github.com/Gnucash/gnucash/commit/ee77b713 (commit)
via https://github.com/Gnucash/gnucash/commit/b073dbc5 (commit)
via https://github.com/Gnucash/gnucash/commit/4e280b95 (commit)
via https://github.com/Gnucash/gnucash/commit/48072f5a (commit)
via https://github.com/Gnucash/gnucash/commit/ee3342d2 (commit)
from https://github.com/Gnucash/gnucash/commit/4ee573e2 (commit)
commit b0b238958e2b8c36426688762341f3288d3919ff
Merge: 4ee573e23 22f91c407
Author: John Ralls <jralls at ceridwen.us>
Date: Mon Jul 6 12:45:07 2020 -0700
Merge Christoph Holtermann's 'python-sessionOpenMode' into master.
commit 22f91c407ee52fcba649d2f608f900a7be6f99fc
Author: c-holtermann <mail at c-holtermann.net>
Date: Sat Jul 4 22:26:35 2020 +0200
use same order in comment as in definition of SessionOpenMode enum
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 72837ba14..50eb6c41a 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -191,14 +191,14 @@ class Session(GnuCashCoreClass):
uri and if none is found, create it. If the file or database exists post a
QOF_BACKED_STORE_EXISTS and return.
@par
+ `SESSION_NEW_OVERWRITE`: Create a new file or database at the provided uri,
+ deleting any existing file or database.
+ @par
`SESSION_READ_ONLY`: Find an existing file or database and open it without
disturbing the lock if it exists or setting one if not. This will also set a
flag on the book that will prevent many elements from being edited and will
prevent the backend from saving any edits.
@par
- `SESSION_NEW_OVERWRITE`: Create a new file or database at the provided uri,
- deleting any existing file or database.
- @par
`SESSION_BREAK_LOCK`: Find an existing file or database, lock it, and open
it. If there is already a lock replace it with a new one for this session.
diff --git a/libgnucash/engine/qofsession.h b/libgnucash/engine/qofsession.h
index 4bab7f0fd..e01d95a65 100644
--- a/libgnucash/engine/qofsession.h
+++ b/libgnucash/engine/qofsession.h
@@ -164,14 +164,14 @@ void qof_session_swap_data (QofSession *session_1, QofSession *session_2);
* uri and if none is found, create it. If the file or database exists post a
* QOF_BACKED_STORE_EXISTS and return.
* @par
+ * `SESSION_NEW_OVERWRITE`: Create a new file or database at the provided uri,
+ * deleting any existing file or database.
+ * @par
* `SESSION_READ_ONLY`: Find an existing file or database and open it without
* disturbing the lock if it exists or setting one if not. This will also set a
* flag on the book that will prevent many elements from being edited and will
* prevent the backend from saving any edits.
* @par
- * `SESSION_NEW_OVERWRITE`: Create a new file or database at the provided uri,
- * deleting any existing file or database.
- * @par
* `SESSION_BREAK_LOCK`: Find an existing file or database, lock it, and open
* it. If there is already a lock replace it with a new one for this session.
*
commit 40cfb70fb722501278d87bc283588d70a578b583
Author: c-holtermann <mail at c-holtermann.net>
Date: Sat Jul 4 22:22:16 2020 +0200
fix SessionOpenMode explanation for SESSION_NORMAL_OPEN
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 2ef37911d..72837ba14 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -184,7 +184,7 @@ class Session(GnuCashCoreClass):
@note SessionOpenMode replaces deprecated ignore_lock, is_new and force_new.
@par SessionOpenMode
- `SESSION_NORMAL`: Find an existing file or database at the provided uri and
+ `SESSION_NORMAL_OPEN`: Find an existing file or database at the provided uri and
open it if it is unlocked. If it is locked post a QOF_BACKEND_LOCKED error.
@par
`SESSION_NEW_STORE`: Check for an existing file or database at the provided
@@ -196,7 +196,7 @@ class Session(GnuCashCoreClass):
flag on the book that will prevent many elements from being edited and will
prevent the backend from saving any edits.
@par
- `SESSION_OVERWRITE`: Create a new file or database at the provided uri,
+ `SESSION_NEW_OVERWRITE`: Create a new file or database at the provided uri,
deleting any existing file or database.
@par
`SESSION_BREAK_LOCK`: Find an existing file or database, lock it, and open
commit e23bf0bc1c7e35c66c7be2d8250e7f6073eb3b8b
Author: c-holtermann <mail at c-holtermann.net>
Date: Sat Jul 4 22:16:13 2020 +0200
fix SessionOpenMode explanation for SESSION_NEW_OVERWRITE
diff --git a/libgnucash/engine/qofsession.h b/libgnucash/engine/qofsession.h
index 781622952..4bab7f0fd 100644
--- a/libgnucash/engine/qofsession.h
+++ b/libgnucash/engine/qofsession.h
@@ -169,7 +169,7 @@ void qof_session_swap_data (QofSession *session_1, QofSession *session_2);
* flag on the book that will prevent many elements from being edited and will
* prevent the backend from saving any edits.
* @par
- * `SESSION_OVERWRITE`: Create a new file or database at the provided uri,
+ * `SESSION_NEW_OVERWRITE`: Create a new file or database at the provided uri,
* deleting any existing file or database.
* @par
* `SESSION_BREAK_LOCK`: Find an existing file or database, lock it, and open
commit 7c8e0a28fc7ef0be313a6e92910fbd9e691fa204
Author: c-holtermann <mail at c-holtermann.net>
Date: Sat Jun 20 13:21:41 2020 +0200
better display for doxygen, typo and consistent naming
diff --git a/libgnucash/engine/qofsession.h b/libgnucash/engine/qofsession.h
index 5836d879c..781622952 100644
--- a/libgnucash/engine/qofsession.h
+++ b/libgnucash/engine/qofsession.h
@@ -154,24 +154,28 @@ void qof_session_swap_data (QofSession *session_1, QofSession *session_2);
* assumed. Customized backends can choose to search other
* application-specific directories or URI schemes as well.
*
- * @param mode The SessionMode.
+ * @param mode The SessionOpenMode.
*
- * ==== SessionMode ====
- * `SESSION_NORMAL`: Find an existing file or database at the provided uri and
+ * @par ==== SessionOpenMode ====
+ * `SESSION_NORMAL_OPEN`: Find an existing file or database at the provided uri and
* open it if it is unlocked. If it is locked post a QOF_BACKEND_LOCKED error.
+ * @par
* `SESSION_NEW_STORE`: Check for an existing file or database at the provided
* uri and if none is found, create it. If the file or database exists post a
* QOF_BACKED_STORE_EXISTS and return.
+ * @par
* `SESSION_READ_ONLY`: Find an existing file or database and open it without
* disturbing the lock if it exists or setting one if not. This will also set a
* flag on the book that will prevent many elements from being edited and will
* prevent the backend from saving any edits.
+ * @par
* `SESSION_OVERWRITE`: Create a new file or database at the provided uri,
* deleting any existing file or database.
- * `SESSION_BREAK_LOCK1: Find an existing file or database, lock it, and open
+ * @par
+ * `SESSION_BREAK_LOCK`: Find an existing file or database, lock it, and open
* it. If there is already a lock replace it with a new one for this session.
*
- * ==== Errors ====
+ * @par ==== Errors ====
* This function signals failure by queuing errors. After it completes use
* qof_session_get_error() and test that the value is `ERROR_BACKEND_NONE` to
* determine that the session began successfully.
commit 3e842a7bf6e3b5479c9e110554c0399b461373a6
Author: c-holtermann <mail at c-holtermann.net>
Date: Sat Jun 20 10:35:31 2020 +0200
use urllib.parse.urlparse to check for xml on python Session init
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index f5647c012..2ef37911d 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -29,6 +29,8 @@
# @ingroup python_bindings
from enum import IntEnum
+from urllib.parse import urlparse
+
from gnucash import gnucash_core_c
from gnucash import _sw_core_utils
@@ -228,7 +230,8 @@ class Session(GnuCashCoreClass):
# Any existing store obviously has to be loaded
# More background: https://bugs.gnucash.org/show_bug.cgi?id=726891
is_new = mode in (SessionOpenMode.SESSION_NEW_STORE, SessionOpenMode.SESSION_NEW_OVERWRITE)
- if book_uri[:3] != "xml" or not is_new:
+ scheme = urlparse(book_uri).scheme
+ if not (is_new and scheme == 'xml'):
self.load()
except GnuCashBackendException as backend_exception:
self.end()
commit b9c6fc28767c130c7bc52f68c3dbaee88fe77f41
Author: c-holtermann <mail at c-holtermann.net>
Date: Thu Jun 11 17:52:02 2020 +0200
add some unittests for python Session
test arguments, deprecated as well as new mode arguments
test creating a session with a new xml file using __init__()
and begin(). Test raising exception when opening nonexistent
file without respective mode setting.
diff --git a/bindings/python/tests/test_session.py b/bindings/python/tests/test_session.py
index 7751e37e6..8127e2e6a 100644
--- a/bindings/python/tests/test_session.py
+++ b/bindings/python/tests/test_session.py
@@ -10,12 +10,57 @@
from unittest import TestCase, main
-from gnucash import Session
+from gnucash import (
+ Session,
+ SessionOpenMode
+)
+
+from gnucash.gnucash_core import GnuCashBackendException
class TestSession(TestCase):
def test_create_empty_session(self):
self.ses = Session()
+ def test_session_deprecated_arguments(self):
+ """use deprecated arguments ignore_lock, is_new, force_new"""
+ self.ses = Session(ignore_lock=False, is_new=True, force_new=False)
+
+ def test_session_mode(self):
+ """use mode argument"""
+ self.ses = Session(mode=SessionOpenMode.SESSION_NORMAL_OPEN)
+
+ def test_session_with_new_file(self):
+ """create Session with new xml file"""
+ from tempfile import TemporaryDirectory
+ from urllib.parse import urlunparse
+ with TemporaryDirectory() as tempdir:
+ uri = urlunparse(("xml", tempdir, "tempfile", "", "", ""))
+ with Session(uri, SessionOpenMode.SESSION_NEW_STORE) as ses:
+ pass
+
+ # try to open nonexistent file without NEW mode - should raise Exception
+ uri = urlunparse(("xml", tempdir, "tempfile2", "", "", ""))
+ with Session() as ses:
+ with self.assertRaises(GnuCashBackendException):
+ ses.begin(uri, mode=SessionOpenMode.SESSION_NORMAL_OPEN)
+
+ # try to open nonexistent file without NEW mode - should raise Exception
+ # use deprecated arg is_new
+ uri = urlunparse(("xml", tempdir, "tempfile2", "", "", ""))
+ with Session() as ses:
+ with self.assertRaises(GnuCashBackendException):
+ ses.begin(uri, is_new=False)
+
+ uri = urlunparse(("xml", tempdir, "tempfile3", "", "", ""))
+ with Session() as ses:
+ ses.begin(uri, mode=SessionOpenMode.SESSION_NEW_STORE)
+
+ # test using deprecated args
+ uri = urlunparse(("xml", tempdir, "tempfile4", "", "", ""))
+ with Session() as ses:
+ ses.begin(uri, is_new=True)
+
+
def test_app_utils_get_current_session(self):
from gnucash import _sw_app_utils
self.ses_instance = _sw_app_utils.gnc_get_current_session()
commit 0434acbe1035ed679d23242a412c48a680ac5a07
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 19 20:45:21 2020 +0200
reformat two python example scripts with black
use black python code formatter on latex_invoices.py and gncinvoice_jinja.py
diff --git a/bindings/python/example_scripts/gncinvoice_jinja.py b/bindings/python/example_scripts/gncinvoice_jinja.py
index bfa7c09a5..f13675a69 100755
--- a/bindings/python/example_scripts/gncinvoice_jinja.py
+++ b/bindings/python/example_scripts/gncinvoice_jinja.py
@@ -45,10 +45,12 @@ except ImportError as import_error:
print(import_error)
sys.exit(2)
+
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
+
def main(argv=None):
if argv is None:
argv = sys.argv
@@ -69,27 +71,27 @@ def main(argv=None):
try:
opts, args = getopt.getopt(argv[1:], "fhliI:t:o:OP:", ["help"])
except getopt.error as msg:
- raise Usage(msg)
+ raise Usage(msg)
for opt in opts:
if opt[0] in ["-f"]:
print("ignoring lock")
ignore_lock = True
- if opt[0] in ["-h","--help"]:
+ if opt[0] in ["-h", "--help"]:
raise Usage("Help:")
if opt[0] in ["-I"]:
invoice_id = opt[1]
- print ("using invoice ID '" + str(invoice_id) + "'.")
+ print("using invoice ID '" + str(invoice_id) + "'.")
if opt[0] in ["-i"]:
- print ("Using ipshell")
+ print("Using ipshell")
with_ipshell = True
if opt[0] in ["-o"]:
filename_output = opt[1]
print("using output file", filename_output)
if opt[0] in ["-O"]:
if filename_output:
- print ("given output filename will be overwritten,")
- print ("creating output filename from Invoice data.")
+ print("given output filename will be overwritten,")
+ print("creating output filename from Invoice data.")
filename_from_invoice = True
if opt[0] in ["-t"]:
filename_template = opt[1]
@@ -99,13 +101,13 @@ def main(argv=None):
print("listing invoices")
if opt[0] in ["-P"]:
output_path = opt[1]
- print ("output path is", output_path + ".")
+ print("output path is", output_path + ".")
# Check for correct input
- if len(args)>1:
- print("opts:",opts,"args:",args)
+ if len(args) > 1:
+ print("opts:", opts, "args:", args)
raise Usage("Only one input possible !")
- if len(args)==0:
+ if len(args) == 0:
raise Usage("No input given !")
input_url = args[0]
@@ -123,16 +125,16 @@ def main(argv=None):
except Usage as err:
if err.msg == "Help:":
- retcode=0
+ retcode = 0
else:
print("Error:", err.msg, file=sys.stderr)
print("for help use --help", file=sys.stderr)
- retcode=2
+ retcode = 2
print()
print("Usage:")
print()
- print("Invoke with",prog_name,"gnucash_url.")
+ print("Invoke with", prog_name, "gnucash_url.")
print("where input is")
print(" filename")
print("or file://filename")
@@ -173,9 +175,9 @@ def main(argv=None):
invoice_list = get_all_invoices(book)
if list_invoices:
- for number,invoice in enumerate(invoice_list):
- print(str(number)+")")
- print(invoice)
+ for number, invoice in enumerate(invoice_list):
+ print(str(number) + ")")
+ print(invoice)
if not (no_output):
@@ -191,7 +193,6 @@ def main(argv=None):
print("Using the following invoice:")
print(invoice)
-
path_template = os.path.dirname(filename_template)
filename_template_basename = os.path.basename(filename_template)
@@ -199,25 +200,37 @@ def main(argv=None):
env = jinja2.Environment(loader=loader)
template = env.get_template(filename_template_basename)
- #company = gnucash_business.Company(book.instance)
+ # company = gnucash_business.Company(book.instance)
- output = template.render(invoice=invoice, locale=locale) #, company=company)
+ output = template.render(invoice=invoice, locale=locale) # , company=company)
if filename_from_invoice:
- filename_date = invoice.GetDatePosted().strftime("%Y-%m-%d") # something like 2014-11-01
+ filename_date = invoice.GetDatePosted().strftime(
+ "%Y-%m-%d"
+ ) # something like 2014-11-01
filename_owner_name = str(invoice.GetOwner().GetName())
filename_invoice_id = str(invoice.GetID())
- filename_output = filename_date + "_" + filename_owner_name + "_" + filename_invoice_id + ".tex"
+ filename_output = (
+ filename_date
+ + "_"
+ + filename_owner_name
+ + "_"
+ + filename_invoice_id
+ + ".tex"
+ )
if output_path:
- filename_output = os.path.join(output_path, os.path.basename(filename_output))
+ filename_output = os.path.join(
+ output_path, os.path.basename(filename_output)
+ )
- print ("Writing output", filename_output, ".")
- with open(filename_output, 'w') as f:
+ print("Writing output", filename_output, ".")
+ with open(filename_output, "w") as f:
f.write(output)
if with_ipshell:
import IPython
+
IPython.embed()
diff --git a/bindings/python/example_scripts/latex_invoices.py b/bindings/python/example_scripts/latex_invoices.py
index c29922e05..829021adf 100644
--- a/bindings/python/example_scripts/latex_invoices.py
+++ b/bindings/python/example_scripts/latex_invoices.py
@@ -57,13 +57,24 @@ try:
import str_methods
from gncinvoicefkt import *
from IPython import version_info as IPython_version_info
- if IPython_version_info[0]>=1:
+
+ if IPython_version_info[0] >= 1:
from IPython.terminal.ipapp import TerminalIPythonApp
else:
from IPython.frontend.terminal.ipapp import TerminalIPythonApp
- from gnucash.gnucash_business import Customer, Employee, Vendor, Job, \
- Address, Invoice, Entry, TaxTable, TaxTableEntry, GNC_AMT_TYPE_PERCENT, \
- GNC_DISC_PRETAX
+ from gnucash.gnucash_business import (
+ Customer,
+ Employee,
+ Vendor,
+ Job,
+ Address,
+ Invoice,
+ Entry,
+ TaxTable,
+ TaxTableEntry,
+ GNC_AMT_TYPE_PERCENT,
+ GNC_DISC_PRETAX,
+ )
from gnucash import SessionOpenMode
import locale
except ImportError as import_error:
@@ -71,99 +82,102 @@ except ImportError as import_error:
print(import_error)
sys.exit(2)
+
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
-def invoice_to_lco(invoice):
- """returns a string which forms a lco-file for use with LaTeX"""
-
- lco_out=u"\ProvidesFile{data.lco}[]\n"
-
- def write_variable(ukey, uvalue, replace_linebreak=True):
-
- outstr = u""
- if uvalue.endswith("\n"):
- uvalue=uvalue[0:len(uvalue)-1]
-
- if not ukey in [u"fromaddress",u"toaddress",u"date"]:
- outstr += u'\\newkomavar{'
- outstr += ukey
- outstr += u"}\n"
-
- outstr += u"\\setkomavar{"
- outstr += ukey
- outstr += u"}{"
- if replace_linebreak:
- outstr += uvalue.replace(u"\n",u"\\\\")+"}"
- return outstr
-
- # Write owners address
- add_str=u""
- owner = invoice.GetOwner()
- if owner.GetName() != "":
- add_str += owner.GetName().decode("UTF-8")+"\n"
-
- addr = owner.GetAddr()
- if addr.GetName() != "":
- add_str += addr.GetName().decode("UTF-8")+"\n"
- if addr.GetAddr1() != "":
- add_str += addr.GetAddr1().decode("UTF-8")+"\n"
- if addr.GetAddr2() != "":
- add_str += addr.GetAddr2().decode("UTF-8")+"\n"
- if addr.GetAddr3() != "":
- add_str += addr.GetAddr3().decode("UTF-8")+"\n"
- if addr.GetAddr4() != "":
- add_str += addr.GetAddr4().decode("UTF-8")+"\n"
-
- lco_out += write_variable("toaddress2",add_str)
- # Invoice number
- inr_str = invoice.GetID()
- lco_out += write_variable("rechnungsnummer",inr_str)
-
- # date
- date = invoice.GetDatePosted()
- udate = date.strftime("%d.%m.%Y")
- lco_out += write_variable("date",udate)+"\n"
-
- # date due
- date_due = invoice.GetDateDue()
- udate_due = date_due.strftime("%d.%m.%Y")
- lco_out += write_variable("date_due",udate_due)+"\n"
-
-
- # Write the entries
- ent_str = u""
- locale.setlocale(locale.LC_ALL,"de_DE")
- for n,ent in enumerate(invoice.GetEntries()):
-
- line_str = u""
-
- if type(ent) != Entry:
- ent=Entry(instance=ent) # Add to method_returns_list
-
- descr = ent.GetDescription()
- price = ent.GetInvPrice().to_double()
- n = ent.GetQuantity()
+def invoice_to_lco(invoice):
+ """returns a string which forms a lco-file for use with LaTeX"""
- uprice = locale.currency(price).rstrip(" EUR")
- un = unicode(int(float(n.num())/n.denom())) # choose best way to format numbers according to locale
+ lco_out = u"\ProvidesFile{data.lco}[]\n"
- line_str = u"\Artikel{"
- line_str += un
- line_str += u"}{"
- line_str += descr.decode("UTF-8")
- line_str += u"}{"
- line_str += uprice
- line_str += u"}"
+ def write_variable(ukey, uvalue, replace_linebreak=True):
- #print(line_str)
- ent_str += line_str
+ outstr = u""
+ if uvalue.endswith("\n"):
+ uvalue = uvalue[0 : len(uvalue) - 1]
- lco_out += write_variable("entries",ent_str)
+ if not ukey in [u"fromaddress", u"toaddress", u"date"]:
+ outstr += u"\\newkomavar{"
+ outstr += ukey
+ outstr += u"}\n"
- return lco_out
+ outstr += u"\\setkomavar{"
+ outstr += ukey
+ outstr += u"}{"
+ if replace_linebreak:
+ outstr += uvalue.replace(u"\n", u"\\\\") + "}"
+ return outstr
+
+ # Write owners address
+ add_str = u""
+ owner = invoice.GetOwner()
+ if owner.GetName() != "":
+ add_str += owner.GetName().decode("UTF-8") + "\n"
+
+ addr = owner.GetAddr()
+ if addr.GetName() != "":
+ add_str += addr.GetName().decode("UTF-8") + "\n"
+ if addr.GetAddr1() != "":
+ add_str += addr.GetAddr1().decode("UTF-8") + "\n"
+ if addr.GetAddr2() != "":
+ add_str += addr.GetAddr2().decode("UTF-8") + "\n"
+ if addr.GetAddr3() != "":
+ add_str += addr.GetAddr3().decode("UTF-8") + "\n"
+ if addr.GetAddr4() != "":
+ add_str += addr.GetAddr4().decode("UTF-8") + "\n"
+
+ lco_out += write_variable("toaddress2", add_str)
+
+ # Invoice number
+ inr_str = invoice.GetID()
+ lco_out += write_variable("rechnungsnummer", inr_str)
+
+ # date
+ date = invoice.GetDatePosted()
+ udate = date.strftime("%d.%m.%Y")
+ lco_out += write_variable("date", udate) + "\n"
+
+ # date due
+ date_due = invoice.GetDateDue()
+ udate_due = date_due.strftime("%d.%m.%Y")
+ lco_out += write_variable("date_due", udate_due) + "\n"
+
+ # Write the entries
+ ent_str = u""
+ locale.setlocale(locale.LC_ALL, "de_DE")
+ for n, ent in enumerate(invoice.GetEntries()):
+
+ line_str = u""
+
+ if type(ent) != Entry:
+ ent = Entry(instance=ent) # Add to method_returns_list
+
+ descr = ent.GetDescription()
+ price = ent.GetInvPrice().to_double()
+ n = ent.GetQuantity()
+
+ uprice = locale.currency(price).rstrip(" EUR")
+ un = unicode(
+ int(float(n.num()) / n.denom())
+ ) # choose best way to format numbers according to locale
+
+ line_str = u"\Artikel{"
+ line_str += un
+ line_str += u"}{"
+ line_str += descr.decode("UTF-8")
+ line_str += u"}{"
+ line_str += uprice
+ line_str += u"}"
+
+ # print(line_str)
+ ent_str += line_str
+
+ lco_out += write_variable("entries", ent_str)
+
+ return lco_out
def main(argv=None):
@@ -181,20 +195,20 @@ def main(argv=None):
try:
opts, args = getopt.getopt(argv[1:], "fhiln:po:", ["help"])
except getopt.error as msg:
- raise Usage(msg)
+ raise Usage(msg)
for opt in opts:
if opt[0] in ["-f"]:
print("ignoring lock")
ignore_lock = True
- if opt[0] in ["-h","--help"]:
+ if opt[0] in ["-h", "--help"]:
raise Usage("Help:")
if opt[0] in ["-i"]:
print("Using ipshell")
with_ipshell = True
if opt[0] in ["-l"]:
print("listing all invoices")
- list_invoices=True
+ list_invoices = True
if opt[0] in ["-n"]:
invoice_number = int(opt[1])
print("using invoice number", invoice_number)
@@ -202,25 +216,25 @@ def main(argv=None):
if opt[0] in ["-o"]:
output_file_name = opt[1]
print("using output file", output_file_name)
- if len(args)>1:
- print("opts:",opts,"args:",args)
+ if len(args) > 1:
+ print("opts:", opts, "args:", args)
raise Usage("Only one input can be accepted !")
- if len(args)==0:
+ if len(args) == 0:
raise Usage("No input given !")
input_url = args[0]
except Usage as err:
if err.msg == "Help:":
- retcode=0
+ retcode = 0
else:
print("Error:", err.msg, file=sys.stderr)
print("for help use --help", file=sys.stderr)
- retcode=2
+ retcode = 2
print("Generate a LaTeX invoice or print out all invoices.")
print()
print("Usage:")
print()
- print("Invoke with",prog_name,"input.")
+ print("Invoke with", prog_name, "input.")
print("where input is")
print(" filename")
print("or file://filename")
@@ -253,39 +267,39 @@ def main(argv=None):
comm_table = book.get_table()
EUR = comm_table.lookup("CURRENCY", "EUR")
- invoice_list=get_all_invoices(book)
+ invoice_list = get_all_invoices(book)
if list_invoices:
- for number,invoice in enumerate(invoice_list):
- print(str(number)+")")
+ for number, invoice in enumerate(invoice_list):
+ print(str(number) + ")")
print(invoice)
if not (no_latex_output):
if invoice_number == None:
print("Using the first invoice:")
- invoice_number=0
+ invoice_number = 0
- invoice=invoice_list[invoice_number]
+ invoice = invoice_list[invoice_number]
print("Using the following invoice:")
print(invoice)
- lco_str=invoice_to_lco(invoice)
+ lco_str = invoice_to_lco(invoice)
# Opening output file
- f=open(output_file_name,"w")
- lco_str=lco_str.encode("latin1")
+ f = open(output_file_name, "w")
+ lco_str = lco_str.encode("latin1")
f.write(lco_str)
f.close()
if with_ipshell:
app = TerminalIPythonApp.instance()
- app.initialize(argv=[]) # argv=[] instructs IPython to ignore sys.argv
+ app.initialize(argv=[]) # argv=[] instructs IPython to ignore sys.argv
app.start()
- #session.save()
+ # session.save()
session.end()
+
if __name__ == "__main__":
sys.exit(main())
-
commit 485d8a65b0ad4d6e7e3e52de991fdf99135ff088
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 19 19:17:09 2020 +0200
decorate Session.begin with default mode argument
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 360c71ced..f5647c012 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -627,7 +627,9 @@ Session.decorate_functions(one_arg_default_none, "load", "save")
Session.decorate_functions( Session.raise_backend_errors_after_call,
"begin", "load", "save", "end")
+Session.decorate_method(default_arguments_decorator, "begin", None, mode=SessionOpenMode.SESSION_NORMAL_OPEN)
Session.decorate_functions(deprecated_args_session_begin, "begin")
+
Session.get_book = method_function_returns_instance(
Session.get_book, Book )
commit 44e61f4df27c972c62218dea2069a43e92819ea5
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 19 19:15:51 2020 +0200
enable Session.__init__() to be provided with existing instance or book
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 32c33bca3..360c71ced 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -210,7 +210,12 @@ class Session(GnuCashCoreClass):
you don't need to cleanup and call end() and destroy(), that is handled
for you, and the exception is raised.
"""
- GnuCashCoreClass.__init__(self, Book())
+ if instance is not None:
+ GnuCashCoreClass.__init__(self, instance=instance)
+ else:
+ if book is None:
+ book = Book()
+ GnuCashCoreClass.__init__(self, book)
if book_uri is not None:
try:
commit 5833c5afcbce5a60bf65291bc52407be1508913a
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 19 18:24:33 2020 +0200
add unittests for function_class
add tests for some existing function_class functionality.
Add tests for the keyword argument changes.
diff --git a/bindings/python/tests/CMakeLists.txt b/bindings/python/tests/CMakeLists.txt
index e118e5a6c..f583e1ce8 100644
--- a/bindings/python/tests/CMakeLists.txt
+++ b/bindings/python/tests/CMakeLists.txt
@@ -25,6 +25,7 @@ set(test_python_bindings_DATA
test_session.py
test_split.py
test_transaction.py
- test_query.py)
+ test_query.py
+ test_function_class.py)
set_dist_list(test_python_bindings_DIST CMakeLists.txt ${test_python_bindings_DATA})
diff --git a/bindings/python/tests/runTests.py.in b/bindings/python/tests/runTests.py.in
index 8f88d8eb6..5b9142592 100755
--- a/bindings/python/tests/runTests.py.in
+++ b/bindings/python/tests/runTests.py.in
@@ -5,6 +5,7 @@ import os
os.environ["GNC_UNINSTALLED"] = "1"
+from test_function_class import TestFunctionClass
from test_gettext import TestGettext
from test_session import TestSession
from test_book import TestBook
diff --git a/bindings/python/tests/test_function_class.py b/bindings/python/tests/test_function_class.py
new file mode 100644
index 000000000..df099e086
--- /dev/null
+++ b/bindings/python/tests/test_function_class.py
@@ -0,0 +1,177 @@
+# test cases for function_class.py
+#
+# @date 2020-06-18
+# @author Christoph Holtermann <mail at c-holtermann.net>
+
+import sys
+from unittest import TestCase, main
+from gnucash.function_class import ClassFromFunctions, default_arguments_decorator
+
+
+class Instance:
+ """instance class for ClassFromFunction tests"""
+
+ pass
+
+
+def prefix_new_function():
+ """new function for ClassFromFunction tests
+
+ returns instance of Instance class"""
+ return Instance()
+
+
+def prefix_test_function(self):
+ """test function for ClassFromFunction tests"""
+ return True
+
+
+def prefix_test_function_return_args(self, *args, **kargs):
+ return self, args, kargs
+
+
+b_default = "b default value"
+
+
+def prefix_test_function_return_arg_karg(self, a, b=b_default):
+ return {"self": self, "a": a, "b": b}
+
+
+def other_function(self, arg=None):
+ return self, arg
+
+
+class TestClass(ClassFromFunctions):
+ _module = sys.modules[__name__]
+
+ pass
+
+
+class TestFunctionClass(TestCase):
+ def test_add_constructor_and_methods_with_prefix(self):
+ TestClass.add_constructor_and_methods_with_prefix("prefix_", "new_function")
+ self.TestClass = TestClass
+ self.testClass = TestClass()
+ self.assertIsInstance(self.testClass.instance, Instance)
+ self.assertTrue(self.testClass.test_function())
+
+ def test_add_method(self):
+ """test if add_method adds method and if in case of FunctionClass
+ Instance instances get returned instead of FunctionClass instances"""
+ TestClass.add_method("other_function", "other_method")
+ self.t = TestClass()
+ obj, arg = self.t.other_method()
+ self.assertIsInstance(obj, Instance)
+ obj, arg = self.t.other_method(self.t)
+ self.assertIsInstance(arg, Instance)
+ obj, arg = self.t.other_method(arg=self.t)
+ self.assertIsInstance(arg, Instance)
+
+ def test_ya_add_method(self):
+ """test if ya_add_method adds method and if in case of FunctionClass
+ Instance instances get returned instead of FunctionClass instances
+ with the exception of self (first) argument"""
+ TestClass.ya_add_method("other_function", "other_method")
+ self.t = TestClass()
+ obj, arg = self.t.other_method()
+ self.assertIsInstance(obj, TestClass)
+ obj, arg = self.t.other_method(self.t)
+ self.assertIsInstance(arg, Instance)
+ obj, arg = self.t.other_method(arg=self.t)
+ self.assertIsInstance(arg, Instance)
+
+ def test_default_arguments_decorator(self):
+ """test default_arguments_decorator()"""
+ TestClass.backup_test_function_return_args = TestClass.test_function_return_args
+ TestClass.backup_test_function_return_arg_karg = (
+ TestClass.test_function_return_arg_karg
+ )
+ self.t = TestClass()
+
+ arg1 = "arg1"
+ arg2 = "arg2"
+ arg3 = {"arg3": arg2}
+ arg4 = 4
+ TestClass.decorate_method(
+ default_arguments_decorator, "test_function_return_args", arg1, arg2
+ )
+ self.assertEqual(
+ self.t.test_function_return_args(), (self.t.instance, (arg2,), {})
+ ) # default arg1 gets overwritten by class instances instance attribute
+ self.assertEqual(
+ self.t.test_function_return_args(arg3), (self.t.instance, (arg3,), {})
+ )
+ self.assertEqual(
+ self.t.test_function_return_args(arg1, arg3),
+ (self.t.instance, (arg1, arg3), {}),
+ )
+ self.assertEqual(
+ self.t.test_function_return_args(arg1, arg3, arg4=arg4),
+ (self.t.instance, (arg1, arg3), {"arg4": arg4}),
+ )
+
+ TestClass.test_function_return_args = TestClass.backup_test_function_return_args
+ TestClass.decorate_method(
+ default_arguments_decorator,
+ "test_function_return_args",
+ arg1,
+ arg2,
+ arg4=arg4,
+ )
+ self.assertEqual(
+ self.t.test_function_return_args(),
+ (self.t.instance, (arg2,), {"arg4": arg4}),
+ )
+ self.assertEqual(
+ self.t.test_function_return_args(arg1, arg3, arg4=arg2),
+ (self.t.instance, (arg1, arg3), {"arg4": arg2}),
+ )
+
+ with self.assertRaises(TypeError):
+ # should fail because a is set both as a positional and as a keyword argument
+ TestClass.decorate_method(
+ default_arguments_decorator,
+ "test_function_return_arg_karg",
+ None,
+ arg1,
+ a=arg2,
+ kargs_pos={"a": 1, "b": 2},
+ )
+ TestClass.decorate_method(
+ default_arguments_decorator,
+ "test_function_return_arg_karg",
+ None,
+ a=arg1,
+ kargs_pos={"a": 1, "b": 2},
+ )
+ self.assertEqual(
+ self.t.test_function_return_arg_karg(),
+ {"self": self.t.instance, "a": arg1, "b": b_default},
+ )
+
+ TestClass.test_function_return_arg_karg = (
+ TestClass.backup_test_function_return_arg_karg
+ )
+ TestClass.decorate_method(
+ default_arguments_decorator,
+ "test_function_return_arg_karg",
+ None,
+ arg1,
+ kargs_pos={"a": 1, "b": 2},
+ )
+ self.assertEqual(
+ self.t.test_function_return_arg_karg(),
+ {"self": self.t.instance, "a": arg1, "b": b_default},
+ )
+ self.assertEqual(
+ self.t.test_function_return_arg_karg(arg2),
+ {"self": self.t.instance, "a": arg2, "b": b_default},
+ )
+ self.assertEqual(
+ self.t.test_function_return_arg_karg(arg2, arg3),
+ {"self": self.t.instance, "a": arg2, "b": arg3},
+ )
+
+
+if __name__ == "__main__":
+ main()
commit 17d606e1f80915fd201606eac9d1f67c5ad0d536
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 19 19:14:20 2020 +0200
enable keyword arguments for default_arguments_decorator
default_arguments_decorator until now only allows positional
argument defaults. This adds keyword defaults. The keywords
can be mapped to the positional arguments by optional argument
kargs_pos so interactions between keyword and positional arg
defaults can raise a TypeError. Some more information in
the docstring is included. In addition the docstring of
the wrapped function will be modified to contain information
about the defaults.
diff --git a/bindings/python/function_class.py b/bindings/python/function_class.py
index 31559f6c1..5ed9f82f8 100644
--- a/bindings/python/function_class.py
+++ b/bindings/python/function_class.py
@@ -252,18 +252,93 @@ def methods_return_instance_lists(cls, function_dict):
method_function_returns_instance_list(
getattr(cls, func_name), instance_name))
-def default_arguments_decorator(function, *args):
- """Decorates a function to give it default, positional arguments
+def default_arguments_decorator(function, *args, **kargs):
+ """! Decorates a function to give it default, positional and keyword arguments
+
+ mimics python behavior when setting defaults in function/method arguments.
+ arguments can be set for positional or keyword arguments.
+
+ kargs_pos contains positions of the keyword arguments.
+ @exception A TypeError will be raised if an argument is set as a positional and keyword argument
+ at the same time.
+ @note It might be possible to get keyword argument positional information using
+ introspection to avoid having to specify them manually
+
+ a keyword argument default will be overwritten by a positional argument at the
+ actual function call
+
+ this function modifies the docstring of the wrapped funtion to reflect
+ the defaults.
You can't use this decorator with @, because this function has more
than one argument.
+
+ arguments:
+ @param *args: optional positional defaults
+ @param kargs_pos: dict with keyword arguments as key and their position in the argument list as value
+ @param **kargs: optional keyword defaults
+
+ @return new_function wrapping original function
"""
- def new_function(*function_args):
+
+ def new_function(*function_args, **function_kargs):
+ kargs_pos = {}
+ if "kargs_pos" in kargs:
+ kargs_pos = kargs.pop("kargs_pos")
new_argset = list(function_args)
- new_argset.extend( args[ len(function_args): ] )
- return function( *new_argset )
+ new_argset.extend(args[len(function_args) :])
+ new_kargset = {**kargs, **function_kargs}
+ for karg_pos in kargs_pos:
+ if karg_pos in new_kargset:
+ pos_karg = kargs_pos[karg_pos]
+ if pos_karg < len(new_argset):
+ new_kargset.pop(karg_pos)
+
+ return function(*new_argset, **new_kargset)
+
+ kargs_pos = {} if "kargs_pos" not in kargs else kargs["kargs_pos"]
+ for karg_pos in kargs_pos:
+ if karg_pos in kargs:
+ pos_karg = kargs_pos[karg_pos]
+ if pos_karg < len(args):
+ raise TypeError(
+ "default_arguments_decorator() got multiple values for argument '%s'"
+ % karg_pos
+ )
+
+ if new_function.__doc__ is None:
+ new_function.__doc__ = ""
+ if len(args):
+ firstarg = True
+ new_function.__doc__ += "positional argument defaults:\n"
+ for arg in args:
+ if not firstarg:
+ new_function.__doc__ += ", "
+ else:
+ new_function.__doc__ += " "
+ firstarg = False
+ new_function.__doc__ += str(arg)
+ new_function.__doc__ += "\n"
+ if len(kargs):
+ new_function.__doc__ += "keyword argument defaults:\n"
+ for karg in kargs:
+ if karg != "kargs_pos":
+ new_function.__doc__ += (
+ " " + str(karg) + " = " + str(kargs[karg]) + "\n"
+ )
+ if kargs_pos:
+ new_function.__doc__ += "keyword argument positions:\n"
+ for karg in kargs_pos:
+ new_function.__doc__ += (
+ " " + str(karg) + " is at pos " + str(kargs_pos[karg]) + "\n"
+ )
+ if len(args) or len(kargs):
+ new_function.__doc__ += (
+ "(defaults have been set by default_arguments_decorator method)"
+ )
return new_function
+
def return_instance_if_value_has_it(value):
"""Return value.instance if value is an instance of ClassFromFunctions,
else return value
commit c222503f42fd47ef973b0cfd49457de96f12a694
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 19 19:08:00 2020 +0200
add method decorate_method to function_class.py
ClassFromFunctions.decorate_method() allows to provide positional
and keyword arguments for the decorator call besides the wrapped
method.
diff --git a/bindings/python/function_class.py b/bindings/python/function_class.py
index fc99cc80f..31559f6c1 100644
--- a/bindings/python/function_class.py
+++ b/bindings/python/function_class.py
@@ -207,6 +207,22 @@ class ClassFromFunctions(object):
setattr( cls, function_name,
decorator( getattr(cls, function_name) ) )
+ @classmethod
+ def decorate_method(cls, decorator, method_name, *args, **kargs):
+ """! decorate method method_name of class cls with decorator decorator
+
+ in difference to decorate_functions() this allows to provide additional
+ arguments for the decorator function.
+
+ arguments:
+ @param cls: class
+ @param decorator: function to decorate method
+ @param method_name: name of method to decorate (string)
+ @param *args: positional arguments for decorator
+ @param **kargs: keyword arguments for decorator"""
+ setattr(cls, method_name,
+ decorator(getattr(cls, method_name), *args, **kargs))
+
def method_function_returns_instance(method_function, cls):
"""A function decorator that is used to decorate method functions that
return instance data, to return instances instead.
commit ee77b713c235e8eb0ee73710bdb35f4918e363a6
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 12 12:24:05 2020 +0200
update example scripts to SessionOpenMode
diff --git a/bindings/python/example_scripts/account_analysis.py b/bindings/python/example_scripts/account_analysis.py
index 135d2e60e..fe24a2e21 100644
--- a/bindings/python/example_scripts/account_analysis.py
+++ b/bindings/python/example_scripts/account_analysis.py
@@ -35,7 +35,7 @@ from math import log10
import csv
# gnucash imports
-from gnucash import Session, GncNumeric, Split
+from gnucash import Session, GncNumeric, Split, SessionOpenMode
# Invoke this script like the following example
# $ python3 account_analysis.py gnucash_file.gnucash \
@@ -173,7 +173,7 @@ def main():
account_path = argv[8:]
- gnucash_session = Session(gnucash_file, is_new=False)
+ gnucash_session = Session(gnucash_file, SessionOpenMode.SESSION_NORMAL_OPEN)
root_account = gnucash_session.book.get_root_account()
account_of_interest = account_from_path(root_account, account_path)
diff --git a/bindings/python/example_scripts/gncinvoice_jinja.py b/bindings/python/example_scripts/gncinvoice_jinja.py
index f44f308ab..bfa7c09a5 100755
--- a/bindings/python/example_scripts/gncinvoice_jinja.py
+++ b/bindings/python/example_scripts/gncinvoice_jinja.py
@@ -39,6 +39,7 @@ try:
import str_methods
import jinja2
from gncinvoicefkt import *
+ from gnucash import SessionOpenMode
except ImportError as import_error:
print("Problem importing modules.")
print(import_error)
@@ -137,7 +138,7 @@ def main(argv=None):
print("or file://filename")
print("or mysql://user:password@host/databasename")
print()
- print("-f force open = ignore lock")
+ print("-f force open = ignore lock (read only)")
print("-l list all invoices")
print("-h or --help for this help")
print("-I ID use invoice ID")
@@ -150,8 +151,15 @@ def main(argv=None):
# Try to open the given input
try:
- print("Opening", input_url, ".")
- session = gnucash.Session(input_url, ignore_lock=ignore_lock)
+ print(
+ "Opening", input_url, " (ignore-lock = read-only)." if ignore_lock else "."
+ )
+ session = gnucash.Session(
+ input_url,
+ SessionOpenMode.SESSION_READ_ONLY
+ if ignore_lock
+ else SessionOpenMode.SESSION_NORMAL_OPEN,
+ )
except Exception as exception:
print("Problem opening input.")
print(exception)
diff --git a/bindings/python/example_scripts/latex_invoices.py b/bindings/python/example_scripts/latex_invoices.py
index 2937c980a..c29922e05 100644
--- a/bindings/python/example_scripts/latex_invoices.py
+++ b/bindings/python/example_scripts/latex_invoices.py
@@ -64,6 +64,7 @@ try:
from gnucash.gnucash_business import Customer, Employee, Vendor, Job, \
Address, Invoice, Entry, TaxTable, TaxTableEntry, GNC_AMT_TYPE_PERCENT, \
GNC_DISC_PRETAX
+ from gnucash import SessionOpenMode
import locale
except ImportError as import_error:
print("Problem importing modules.")
@@ -236,7 +237,12 @@ def main(argv=None):
# Try to open the given input
try:
- session = gnucash.Session(input_url,ignore_lock=ignore_lock)
+ session = gnucash.Session(
+ input_url,
+ SessionOpenMode.SESSION_READ_ONLY
+ if ignore_lock
+ else SessionOpenMode.SESSION_NORMAL_OPEN,
+ )
except Exception as exception:
print("Problem opening input.")
print(exception)
diff --git a/bindings/python/example_scripts/new_book_with_opening_balances.py b/bindings/python/example_scripts/new_book_with_opening_balances.py
index df2d29ae7..3a2d04e7b 100644
--- a/bindings/python/example_scripts/new_book_with_opening_balances.py
+++ b/bindings/python/example_scripts/new_book_with_opening_balances.py
@@ -28,7 +28,8 @@
# @author Mark Jenkins, ParIT Worker Co-operative <mark at parit.ca>
# @ingroup python_bindings_examples
-from gnucash import Session, Account, Transaction, Split, GncNumeric
+from gnucash import (
+ Session, Account, Transaction, Split, GncNumeric, SessionOpenMode)
from gnucash.gnucash_core_c import \
GNC_DENOM_AUTO, GNC_HOW_DENOM_EXACT, \
ACCT_TYPE_ASSET, ACCT_TYPE_BANK, ACCT_TYPE_CASH, ACCT_TYPE_CHECKING, \
@@ -299,8 +300,8 @@ def main():
#have everything in a try block to unable us to release our hold on stuff to the extent possible
try:
- original_book_session = Session(argv[1], is_new=False)
- new_book_session = Session(argv[2], is_new=True)
+ original_book_session = Session(argv[1], SessionOpenMode.SESSION_NORMAL_OPEN)
+ new_book_session = Session(argv[2], SessionOpenMode.SESSION_NEW_STORE)
new_book = new_book_session.get_book()
new_book_root = new_book.get_root_account()
diff --git a/bindings/python/example_scripts/rest-api/gnucash_rest.py b/bindings/python/example_scripts/rest-api/gnucash_rest.py
index 51c75eb09..533447139 100644
--- a/bindings/python/example_scripts/rest-api/gnucash_rest.py
+++ b/bindings/python/example_scripts/rest-api/gnucash_rest.py
@@ -68,6 +68,8 @@ from gnucash import \
from gnucash import \
INVOICE_IS_PAID
+from gnucash import SessionOpenMode
+
app = Flask(__name__)
app.debug = True
@@ -1884,7 +1886,7 @@ for option, value in options:
#start gnucash session base on connection string argument
if is_new:
- session = gnucash.Session(arguments[0], is_new=True)
+ session = gnucash.Session(arguments[0], SessionOpenMode.SESSION_NEW_STORE)
# seem to get errors if we use the session directly, so save it and
#destroy it so it's no longer new
@@ -1893,7 +1895,8 @@ if is_new:
session.end()
session.destroy()
-session = gnucash.Session(arguments[0], ignore_lock=True)
+# unsure about SESSION_BREAK_LOCK - it used to be ignore_lock=True
+session = gnucash.Session(arguments[0], SessionOpenMode.SESSION_BREAK_LOCK)
# register method to close gnucash connection gracefully
atexit.register(shutdown)
diff --git a/bindings/python/example_scripts/simple_book.py b/bindings/python/example_scripts/simple_book.py
index 19ebb0186..0059ff64e 100644
--- a/bindings/python/example_scripts/simple_book.py
+++ b/bindings/python/example_scripts/simple_book.py
@@ -5,13 +5,13 @@
# @ingroup python_bindings_examples
import sys
-from gnucash import Session
+from gnucash import Session, SessionOpenMode
# We need to tell GnuCash the data format to create the new file as (xml://)
uri = "xml:///tmp/simple_book.gnucash"
print("uri:", uri)
-with Session(uri, is_new=True) as ses:
+with Session(uri, SessionOpenMode.SESSION_NEW_STORE) as ses:
book = ses.get_book()
#Call some methods that produce output to show that Book works
diff --git a/bindings/python/example_scripts/simple_business_create.py b/bindings/python/example_scripts/simple_business_create.py
index bb00846df..e0df30bb8 100644
--- a/bindings/python/example_scripts/simple_business_create.py
+++ b/bindings/python/example_scripts/simple_business_create.py
@@ -53,7 +53,7 @@ from os.path import abspath
from sys import argv, exit
import datetime
from datetime import timedelta
-from gnucash import Session, Account, GncNumeric
+from gnucash import Session, Account, GncNumeric, SessionOpenMode
from gnucash.gnucash_business import Customer, Employee, Vendor, Job, \
Address, Invoice, Entry, TaxTable, TaxTableEntry, GNC_AMT_TYPE_PERCENT, \
GNC_DISC_PRETAX
@@ -70,7 +70,7 @@ if len(argv) < 2:
try:
- s = Session(argv[1], is_new=True)
+ s = Session(argv[1], SessionOpenMode.SESSION_NEW_STORE)
book = s.book
root = book.get_root_account()
diff --git a/bindings/python/example_scripts/simple_invoice_insert.py b/bindings/python/example_scripts/simple_invoice_insert.py
index eef4c03ba..ee1bbfb06 100644
--- a/bindings/python/example_scripts/simple_invoice_insert.py
+++ b/bindings/python/example_scripts/simple_invoice_insert.py
@@ -46,7 +46,7 @@
# @author Mark Jenkins, ParIT Worker Co-operative <mark at parit.ca>
# @ingroup python_bindings_examples
-from gnucash import Session, GUID, GncNumeric
+from gnucash import Session, GUID, GncNumeric, SessionOpenMode
from gnucash.gnucash_business import Customer, Invoice, Entry
from gnucash.gnucash_core_c import string_to_guid
from os.path import abspath
@@ -86,7 +86,7 @@ def gnc_numeric_from_decimal(decimal_value):
return GncNumeric(numerator, denominator)
-s = Session(argv[1], is_new=False)
+s = Session(argv[1], SessionOpenMode.SESSION_NORMAL_OPEN)
book = s.book
root = book.get_root_account()
diff --git a/bindings/python/example_scripts/simple_session.py b/bindings/python/example_scripts/simple_session.py
index 05da9487b..daebfdf64 100644
--- a/bindings/python/example_scripts/simple_session.py
+++ b/bindings/python/example_scripts/simple_session.py
@@ -3,9 +3,11 @@
# @brief Example Script simple session
# @ingroup python_bindings_examples
-from gnucash import \
- Session, GnuCashBackendException, \
+from gnucash import (
+ Session, GnuCashBackendException,
+ SessionOpenMode,
ERR_BACKEND_LOCKED, ERR_FILEIO_FILE_NOT_FOUND
+)
FILE_1 = "/tmp/not_there.xac"
FILE_2 = "/tmp/example_file.xac"
@@ -19,7 +21,7 @@ except GnuCashBackendException as backend_exception:
# create a new file, this requires a file type specification
-with Session("xml://%s" % FILE_2, is_new=True) as session:
+with Session("xml://%s" % FILE_2, SessionOpenMode.SESSION_NEW_STORE) as session:
book = session.book
root = book.get_root_account()
diff --git a/bindings/python/example_scripts/simple_sqlite_create.py b/bindings/python/example_scripts/simple_sqlite_create.py
index 7900c9534..61675be08 100644
--- a/bindings/python/example_scripts/simple_sqlite_create.py
+++ b/bindings/python/example_scripts/simple_sqlite_create.py
@@ -3,11 +3,11 @@
# @brief Example Script simple sqlite create
# @ingroup python_bindings_examples
-from gnucash import Session, Account
+from gnucash import Session, Account, SessionOpenMode
from os.path import abspath
from gnucash.gnucash_core_c import ACCT_TYPE_ASSET
-s = Session('sqlite3://%s' % abspath('test.blob'), is_new=True)
+s = Session('sqlite3://%s' % abspath('test.blob'), SessionOpenMode.SESSION_NEW_STORE)
# this seems to make a difference in more complex cases
s.save()
diff --git a/bindings/python/example_scripts/simple_test.py b/bindings/python/example_scripts/simple_test.py
index b8a1bed02..7101823df 100644
--- a/bindings/python/example_scripts/simple_test.py
+++ b/bindings/python/example_scripts/simple_test.py
@@ -3,11 +3,12 @@
# @brief Creates a basic set of accounts and a couple of transactions
# @ingroup python_bindings_examples
-from gnucash import Session, Account, Transaction, Split, GncNumeric
+from gnucash import (
+ Session, Account, Transaction, Split, GncNumeric, SessionOpenMode)
FILE_1 = "/tmp/example.gnucash"
-with Session("xml://%s" % FILE_1, is_new=True) as session:
+with Session("xml://%s" % FILE_1, SessionOpenMode.SESSION_NEW_STORE) as session:
book = session.book
root_acct = Account(book)
@@ -80,4 +81,4 @@ with Session("xml://%s" % FILE_1, is_new=True) as session:
trans1.CommitEdit()
- trans2.CommitEdit()
\ No newline at end of file
+ trans2.CommitEdit()
commit b073dbc5c323f88363e97231afc37fab017dfa8c
Author: c-holtermann <mail at c-holtermann.net>
Date: Thu Jun 11 21:11:06 2020 +0200
allow keyword arguments for function_class.py
allow keyword arguments for function_class methods
and functions. process_dict_convert_to_instance() is added to
mimic the behavior of the process_list_convert_to_instance()
Derived methods in gnucash_core.py like raise_backend_errors_after_call
get modified to accept being called with keyword args.
Also adds some docstrings.
diff --git a/bindings/python/function_class.py b/bindings/python/function_class.py
index 81bebb049..fc99cc80f 100644
--- a/bindings/python/function_class.py
+++ b/bindings/python/function_class.py
@@ -71,7 +71,8 @@ class ClassFromFunctions(object):
self.__instance = kargs[INSTANCE_ARGUMENT]
else:
self.__instance = getattr(self._module, self._new_instance)(
- *process_list_convert_to_instance(args) )
+ *process_list_convert_to_instance(args),
+ **process_dict_convert_to_instance(kargs))
def get_instance(self):
"""Get the instance data.
@@ -86,12 +87,29 @@ class ClassFromFunctions(object):
@classmethod
def add_method(cls, function_name, method_name):
- """Add the function, method_name to this class as a method named name
- """
- def method_function(self, *meth_func_args):
+ """! Add the function, method_name to this class as a method named name
+
+ arguments:
+ @param cls Class: class to add methods to
+ @param function_name string: name of the function to add
+ @param method_name string: name of the method that function will be called
+
+ function will be wrapped by method_function"""
+
+ def method_function(self, *meth_func_args, **meth_func_kargs):
+ """! wrapper method for function
+
+ arguments:
+ @param self: FunctionClass instance. Will be turned to its instance property.
+ @param *meth_func_args: arguments to be passed to function. All FunctionClass
+ objects will be turned to their respective instances.
+ @param **meth_func_kargs: keyword arguments to be passed to function. All
+ FunctionClass objects will be turned to their respective instances."""
return getattr(self._module, function_name)(
self.instance,
- *process_list_convert_to_instance(meth_func_args) )
+ *process_list_convert_to_instance(meth_func_args),
+ **process_dict_convert_to_instance(meth_func_kargs)
+ )
setattr(cls, method_name, method_function)
setattr(method_function, "__name__", method_name)
@@ -99,14 +117,32 @@ class ClassFromFunctions(object):
@classmethod
def ya_add_classmethod(cls, function_name, method_name):
- """Add the function, method_name to this class as a classmethod named name
+ """! Add the function, method_name to this class as a classmethod named name
- Taken from function_class and slightly modified.
- """
- def method_function(self, *meth_func_args):
+ Taken from function_class and modified from add_method() to add classmethod
+ instead of method and not to turn self argument to self.instance.
+
+ arguments:
+ @param cls Class: class to add methods to
+ @param function_name string: name of the function to add
+ @param method_name string: name of the classmethod that function will be called
+
+ function will be wrapped by method_function"""
+
+ def method_function(self, *meth_func_args, **meth_func_kargs):
+ """! wrapper method for function
+
+ arguments:
+ @param self: FunctionClass instance.
+ @param *meth_func_args: arguments to be passed to function. All FunctionClass
+ objects will be turned to their respective instances.
+ @param **meth_func_kargs: keyword arguments to be passed to function. All
+ FunctionClass objects will be turned to their respective instances."""
return getattr(self._module, function_name)(
self,
- *process_list_convert_to_instance(meth_func_args) )
+ *process_list_convert_to_instance(meth_func_args),
+ **process_dict_convert_to_instance(meth_func_kargs)
+ )
setattr(cls, method_name, classmethod(method_function))
setattr(method_function, "__name__", method_name)
@@ -114,14 +150,32 @@ class ClassFromFunctions(object):
@classmethod
def ya_add_method(cls, function_name, method_name):
- """Add the function, method_name to this class as a method named name
+ """! Add the function, method_name to this class as a method named name
- Taken from function_class and slightly modified.
- """
- def method_function(self, *meth_func_args):
+ Taken from function_class. Modified to not turn self to self.instance
+ as add_method() does.
+
+ arguments:
+ @param cls Class: class to add methods to
+ @param function_name string: name of the function to add
+ @param method_name string: name of the method that function will be called
+
+ function will be wrapped by method_function"""
+
+ def method_function(self, *meth_func_args, **meth_func_kargs):
+ """! wrapper method for function
+
+ arguments:
+ @param self: FunctionClass instance.
+ @param *meth_func_args: arguments to be passed to function. All FunctionClass
+ objects will be turned to their respective instances.
+ @param **meth_func_kargs: keyword arguments to be passed to function. All
+ FunctionClass objects will be turned to their respective instances."""
return getattr(self._module, function_name)(
self,
- *process_list_convert_to_instance(meth_func_args) )
+ *process_list_convert_to_instance(meth_func_args),
+ **process_dict_convert_to_instance(meth_func_kargs)
+ )
setattr(cls, method_name, method_function)
setattr(method_function, "__name__", method_name)
@@ -161,19 +215,19 @@ def method_function_returns_instance(method_function, cls):
argument.
"""
assert( 'instance' == INSTANCE_ARGUMENT )
- def new_function(*args):
- kargs = { INSTANCE_ARGUMENT : method_function(*args) }
- if kargs['instance'] == None:
+ def new_function(*args, **kargs):
+ kargs_cls = { INSTANCE_ARGUMENT : method_function(*args, **kargs) }
+ if kargs_cls['instance'] == None:
return None
else:
- return cls( **kargs )
+ return cls( **kargs_cls )
return new_function
def method_function_returns_instance_list(method_function, cls):
- def new_function(*args):
+ def new_function(*args, **kargs):
return [ cls( **{INSTANCE_ARGUMENT: item} )
- for item in method_function(*args) ]
+ for item in method_function(*args, **kargs) ]
return new_function
def methods_return_instance_lists(cls, function_dict):
@@ -213,6 +267,18 @@ def process_list_convert_to_instance( value_list ):
return [ return_instance_if_value_has_it(value)
for value in value_list ]
+def process_dict_convert_to_instance(value_dict):
+ """Return a dict built from value_dict, where if a value is in an instance
+ of ClassFromFunctions, we put value.instance in the dict instead.
+
+ Things that are not instances of ClassFromFunctions are returned to
+ the new dict unchanged.
+ """
+ return {
+ key: return_instance_if_value_has_it(value) for key, value in value_dict.items()
+ }
+
+
def extract_attributes_with_prefix(obj, prefix):
"""Generator that iterates through the attributes of an object and
for any attribute that matches a prefix, this yields
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index fa116b1d7..32c33bca3 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -266,12 +266,12 @@ class Session(GnuCashCoreClass):
# STATIC METHODS
@staticmethod
- def raise_backend_errors_after_call(function):
+ def raise_backend_errors_after_call(function, *args, **kwargs):
"""A function decorator that results in a call to
raise_backend_errors after execution.
"""
- def new_function(self, *args):
- return_value = function(self, *args)
+ def new_function(self, *args, **kwargs):
+ return_value = function(self, *args, **kwargs)
self.raise_backend_errors(function.__name__)
return return_value
return new_function
commit 4e280b959349e420e34ca93938982fb4e39481bd
Author: c-holtermann <mail at c-holtermann.net>
Date: Tue Jun 9 22:41:20 2020 +0200
adapt to use of sessionOpenMode in qof_session_begin
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index 5dbf68383..fa116b1d7 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -48,6 +48,12 @@ from gnucash.gnucash_core_c import gncInvoiceLookup, gncInvoiceGetInvoiceFromTxn
gnc_numeric_create, double_to_gnc_numeric, string_to_gnc_numeric, \
gnc_numeric_to_string
+from gnucash.deprecation import (
+ deprecated_args_session,
+ deprecated_args_session_init,
+ deprecated_args_session_begin
+)
+
try:
import gettext
@@ -148,44 +154,75 @@ class Session(GnuCashCoreClass):
Invoice..) is associated with a particular book where it is stored.
"""
- def __init__(self, book_uri=None, ignore_lock=False, is_new=False,
- force_new=False, instance=None):
- """A convenient constructor that allows you to specify a book URI,
+ @deprecated_args_session_init
+ def __init__(self, book_uri=None, mode=None, instance=None, book=None):
+ """!
+ A convenient constructor that allows you to specify a book URI,
begin the session, and load the book.
This can give you the power of calling
qof_session_new, qof_session_begin, and qof_session_load all in one!
- book_uri can be None to skip the calls to qof_session_begin and
- qof_session_load, or it can be a string like "file:/test.xac"
-
- qof_session_load is only called if is_new is set to False
-
- is_new is passed to qof_session_begin as the argument create,
- and force_new as the argument force. Is_new will create a new
- database or file; force will force creation even if it will
- destroy an existing dataset.
+ qof_session_load is only called if url scheme is "xml" and
+ mode is SESSION_NEW_STORE or SESSION_NEW_OVERWRITE
- ignore_lock is passed to qof_session_begin's argument of the
- same name and is used to break an existing lock on a dataset.
+ @param book_uri must be a string in the form of a URI/URL. The access
+ method specified depends on the loaded backends. Paths may be relative
+ or absolute. If the path is relative, that is if the argument is
+ "file://somefile.xml", then the current working directory is
+ assumed. Customized backends can choose to search other
+ application-specific directories or URI schemes as well.
+ It be None to skip the calls to qof_session_begin and
+ qof_session_load.
- instance argument can be passed if new Session is used as a
+ @param instance argument can be passed if new Session is used as a
wrapper for an existing session instance
-
- This function can raise a GnuCashBackendException. If it does,
+ @param mode The SessionOpenMode.
+ @note SessionOpenMode replaces deprecated ignore_lock, is_new and force_new.
+
+ @par SessionOpenMode
+ `SESSION_NORMAL`: Find an existing file or database at the provided uri and
+ open it if it is unlocked. If it is locked post a QOF_BACKEND_LOCKED error.
+ @par
+ `SESSION_NEW_STORE`: Check for an existing file or database at the provided
+ uri and if none is found, create it. If the file or database exists post a
+ QOF_BACKED_STORE_EXISTS and return.
+ @par
+ `SESSION_READ_ONLY`: Find an existing file or database and open it without
+ disturbing the lock if it exists or setting one if not. This will also set a
+ flag on the book that will prevent many elements from being edited and will
+ prevent the backend from saving any edits.
+ @par
+ `SESSION_OVERWRITE`: Create a new file or database at the provided uri,
+ deleting any existing file or database.
+ @par
+ `SESSION_BREAK_LOCK`: Find an existing file or database, lock it, and open
+ it. If there is already a lock replace it with a new one for this session.
+
+ @par Errors
+ qof_session_begin() signals failure by queuing errors. After it completes use
+ qof_session_get_error() and test that the value is `ERROR_BACKEND_NONE` to
+ determine that the session began successfully.
+
+ @exception as begin() and load() are wrapped with raise_backend_errors_after_call()
+ this function can raise a GnuCashBackendException. If it does,
you don't need to cleanup and call end() and destroy(), that is handled
for you, and the exception is raised.
"""
GnuCashCoreClass.__init__(self, Book())
+
if book_uri is not None:
try:
- self.begin(book_uri, ignore_lock, is_new, force_new)
+ if mode is None:
+ mode = SessionOpenMode.SESSION_NORMAL_OPEN
+ self.begin(book_uri, mode)
# Take care of backend inconsistency
# New xml file can't be loaded, new sql store
# has to be loaded before it can be altered
# Any existing store obviously has to be loaded
# More background: https://bugs.gnucash.org/show_bug.cgi?id=726891
+ is_new = mode in (SessionOpenMode.SESSION_NEW_STORE, SessionOpenMode.SESSION_NEW_OVERWRITE)
if book_uri[:3] != "xml" or not is_new:
self.load()
except GnuCashBackendException as backend_exception:
@@ -585,6 +622,7 @@ Session.decorate_functions(one_arg_default_none, "load", "save")
Session.decorate_functions( Session.raise_backend_errors_after_call,
"begin", "load", "save", "end")
+Session.decorate_functions(deprecated_args_session_begin, "begin")
Session.get_book = method_function_returns_instance(
Session.get_book, Book )
commit 48072f5a4c957e5ef4a168f8774006872cd4acf7
Author: c-holtermann <mail at c-holtermann.net>
Date: Thu Jun 11 17:50:49 2020 +0200
make SessionOpenMode enum available for python
diff --git a/bindings/python/gnucash_core.py b/bindings/python/gnucash_core.py
index b82c5a53b..5dbf68383 100644
--- a/bindings/python/gnucash_core.py
+++ b/bindings/python/gnucash_core.py
@@ -28,6 +28,7 @@
# @author Jeff Green, ParIT Worker Co-operative <jeff at parit.ca>
# @ingroup python_bindings
+from enum import IntEnum
from gnucash import gnucash_core_c
from gnucash import _sw_core_utils
@@ -83,6 +84,57 @@ class GnuCashBackendException(Exception):
Exception.__init__(self, msg)
self.errors = errors
+
+class SessionOpenMode(IntEnum):
+ """Mode for opening sessions.
+
+ This replaces three booleans that were passed in order: ignore_lock, create,
+ and force. It's structured so that one can use it as a bit field with the
+ values in the same order, i.e. ignore_lock = 1 << 2, create_new = 1 << 1, and
+ force_new = 1.
+
+ enumeration members
+ -------------------
+
+ SESSION_NORMAL_OPEN = 0 (All False)
+ Open will fail if the URI doesn't exist or is locked.
+
+ SESSION_NEW_STORE = 2 (False, True, False (create))
+ Create a new store at the URI. It will fail if the store already exists and is found to contain data that would be overwritten.
+
+ SESSION_NEW_OVERWRITE = 3 (False, True, True (create | force))
+ Create a new store at the URI even if a store already exists there.
+
+ SESSION_READ_ONLY = 4, (True, False, False (ignore_lock))
+ Open the session read-only, ignoring any existing lock and not creating one if the URI isn't locked.
+
+ SESSION_BREAK_LOCK = 5 (True, False, True (ignore_lock | force))
+ Open the session, taking over any existing lock.
+
+ source: lignucash/engine/qofsession.h
+ """
+
+ SESSION_NORMAL_OPEN = gnucash_core_c.SESSION_NORMAL_OPEN
+ """All False
+ Open will fail if the URI doesn't exist or is locked."""
+
+ SESSION_NEW_STORE = gnucash_core_c.SESSION_NEW_STORE
+ """False, True, False (create)
+ Create a new store at the URI. It will fail if the store already exists and is found to contain data that would be overwritten."""
+
+ SESSION_NEW_OVERWRITE = gnucash_core_c.SESSION_NEW_OVERWRITE
+ """False, True, True (create | force)
+ Create a new store at the URI even if a store already exists there."""
+
+ SESSION_READ_ONLY = gnucash_core_c.SESSION_READ_ONLY
+ """True, False, False (ignore_lock)
+ Open the session read-only, ignoring any existing lock and not creating one if the URI isn't locked."""
+
+ SESSION_BREAK_LOCK = gnucash_core_c.SESSION_BREAK_LOCK
+ """True, False, True (ignore_lock | force)
+ Open the session, taking over any existing lock."""
+
+
class Session(GnuCashCoreClass):
"""A GnuCash book editing session
commit ee3342d2b474fc94be206e1a768dc8de07b0ea32
Author: c-holtermann <mail at c-holtermann.net>
Date: Fri Jun 19 22:43:08 2020 +0200
introduce python submodule deprecation
the deprecation submodule will house content related to deprecation.
That is general convenience function and functions related to specific
deprecation issues. The latter starts with decorator functions to bridge
the change in qof_session_begin argument change to SessionOpenMode.
diff --git a/bindings/python/CMakeLists.txt b/bindings/python/CMakeLists.txt
index 8c5884e3a..4e7480495 100644
--- a/bindings/python/CMakeLists.txt
+++ b/bindings/python/CMakeLists.txt
@@ -1,7 +1,7 @@
add_subdirectory(example_scripts)
add_subdirectory(tests)
-set(PYEXEC_FILES __init__.py function_class.py gnucash_business.py gnucash_core.py app_utils.py)
+set(PYEXEC_FILES __init__.py function_class.py gnucash_business.py gnucash_core.py app_utils.py deprecation.py)
set(SWIG_FILES ${CMAKE_CURRENT_SOURCE_DIR}/gnucash_core.i ${CMAKE_CURRENT_SOURCE_DIR}/time64.i)
set(GNUCASH_CORE_C_INCLUDES
diff --git a/bindings/python/__init__.py b/bindings/python/__init__.py
index 16b72fba9..8b3e0b687 100644
--- a/bindings/python/__init__.py
+++ b/bindings/python/__init__.py
@@ -5,6 +5,7 @@
# >>> from gnucash.gnucash_core import thingy
from gnucash.gnucash_core import *
from . import app_utils
+from . import deprecation
## @file
# @brief helper file for the importing of gnucash
# @author Mark Jenkins, ParIT Worker Co-operative <mark at parit.ca>
diff --git a/bindings/python/deprecation.py b/bindings/python/deprecation.py
new file mode 100644
index 000000000..81d4cdc36
--- /dev/null
+++ b/bindings/python/deprecation.py
@@ -0,0 +1,68 @@
+# deprecation.py - gnucash submodule with deprecation related content
+#
+# contains decorator methods dealing with deprecated methods and
+# deprecation related convenience methods
+#
+# @brief gnucash submodule with deprecation related content
+# @author Christoph Holtermann <mail at c-holtermann.net>
+# @ingroup python_bindings
+
+from functools import wraps
+
+# use of is_new, force_new and ignore_lock is deprecated, use mode instead
+# the following decorators enable backward compatibility for the deprecation period
+def deprecated_args_session(ignore_lock_or_mode=None, is_new=None,
+ force_new=None, mode=None, ignore_lock=None):
+
+ # check for usage of deprecated arguments (ignore_lock, is_new, force_new)
+ deprecated_args = (ignore_lock, is_new, force_new)
+ deprecated_keyword_use = deprecated_args.count(None) != len(deprecated_args)
+ if deprecated_keyword_use:
+ # deprecated arguments have been used by keyword or more than three args have been used which is only possible with the deprecated args
+ deprecation = True
+ else:
+ deprecation = False
+ # __init__ could have been called without keywords like __init__(book_uri, True) where True aims at ignore_lock
+ # which ist not distinguishable from __init__(book, SessionOpenMode.SESSION_NORMAL_OPEN)
+ # so if mode has not been set by keyword use the 3rd argument
+ if mode is None:
+ mode = ignore_lock_or_mode
+
+ if deprecation:
+ # if any(item in ("is_new", "ignore_lock", "force_new") for item in kwargs):
+ import warnings
+ warnings.warn(
+ "Use of ignore_lock, is_new or force_new arguments is deprecated. Use mode argument instead. Have a look at gnucash.SessionOpenMode.",
+ category=DeprecationWarning,
+ stacklevel=3
+ )
+
+ # if not provided calculate mode from deprecated args
+ if mode is None:
+ from gnucash.gnucash_core import SessionOpenMode
+ ignore_lock = False if ignore_lock is None else ignore_lock
+ is_new = False if is_new is None else is_new
+ force_new = False if force_new is None else force_new
+ mode = SessionOpenMode((ignore_lock << 2) + (is_new << 1) + force_new)
+
+ return mode
+
+def deprecated_args_session_init(original_function):
+ """decorator for Session.__init__() to provide backward compatibility for deprecated use of ignore_lock, is_new and force_new"""
+ @wraps(original_function)
+ def new_function(self, book_uri=None, ignore_lock_or_mode=None, is_new=None,
+ force_new=None, instance=None, mode=None, ignore_lock=None):
+
+ mode = deprecated_args_session(ignore_lock_or_mode, is_new, force_new, mode, ignore_lock)
+ return(original_function(self, book_uri=book_uri, mode=mode, instance=instance))
+ return new_function
+
+def deprecated_args_session_begin(original_function):
+ """decorator for Session.begin() to provide backward compatibility for deprecated use of ignore_lock, is_new and force_new"""
+ @wraps(original_function)
+ def new_function(self, new_uri=None, ignore_lock_or_mode=None, is_new=None,
+ force_new=None, mode=None, ignore_lock=None):
+ mode = deprecated_args_session(ignore_lock_or_mode, is_new, force_new, mode, ignore_lock)
+ return(original_function(self, new_uri=new_uri, mode=mode))
+ return new_function
+
Summary of changes:
bindings/python/CMakeLists.txt | 2 +-
bindings/python/__init__.py | 1 +
bindings/python/deprecation.py | 68 ++++++
.../python/example_scripts/account_analysis.py | 4 +-
.../python/example_scripts/gncinvoice_jinja.py | 75 ++++---
bindings/python/example_scripts/latex_invoices.py | 236 +++++++++++----------
.../new_book_with_opening_balances.py | 7 +-
.../example_scripts/rest-api/gnucash_rest.py | 7 +-
bindings/python/example_scripts/simple_book.py | 4 +-
.../example_scripts/simple_business_create.py | 4 +-
.../example_scripts/simple_invoice_insert.py | 4 +-
bindings/python/example_scripts/simple_session.py | 8 +-
.../python/example_scripts/simple_sqlite_create.py | 4 +-
bindings/python/example_scripts/simple_test.py | 7 +-
bindings/python/function_class.py | 209 +++++++++++++++---
bindings/python/gnucash_core.py | 146 +++++++++++--
bindings/python/tests/CMakeLists.txt | 3 +-
bindings/python/tests/runTests.py.in | 1 +
bindings/python/tests/test_function_class.py | 177 ++++++++++++++++
bindings/python/tests/test_session.py | 47 +++-
libgnucash/engine/qofsession.h | 18 +-
21 files changed, 817 insertions(+), 215 deletions(-)
create mode 100644 bindings/python/deprecation.py
create mode 100644 bindings/python/tests/test_function_class.py
More information about the gnucash-changes
mailing list