Merge Christoph Holtermann's 'python-sessionOpenMode' into master.

This commit is contained in:
John Ralls 2020-07-06 12:45:07 -07:00
commit b0b238958e
21 changed files with 801 additions and 199 deletions

View File

@ -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

View File

@ -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@parit.ca>

View File

@ -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@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

View File

@ -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)

View File

@ -39,15 +39,18 @@ 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)
sys.exit(2)
class Usage(Exception):
def __init__(self, msg):
self.msg = msg
def main(argv=None):
if argv is None:
argv = sys.argv
@ -68,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]
@ -98,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]
@ -122,22 +125,22 @@ 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")
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 +153,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)
@ -165,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):
@ -183,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)
@ -191,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()

View File

@ -57,112 +57,127 @@ 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:
print("Problem importing modules.")
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"""
"""returns a string which forms a lco-file for use with LaTeX"""
lco_out=u"\ProvidesFile{data.lco}[]\n"
lco_out = u"\ProvidesFile{data.lco}[]\n"
def write_variable(ukey, uvalue, replace_linebreak=True):
def write_variable(ukey, uvalue, replace_linebreak=True):
outstr = u""
if uvalue.endswith("\n"):
uvalue=uvalue[0:len(uvalue)-1]
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{'
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"}\n"
outstr += u"}{"
if replace_linebreak:
outstr += uvalue.replace(u"\n", u"\\\\") + "}"
return outstr
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"
# 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"
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)
lco_out += write_variable("toaddress2",add_str)
# Invoice number
inr_str = invoice.GetID()
lco_out += write_variable("rechnungsnummer", inr_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
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"
# 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""
# Write the entries
ent_str = u""
locale.setlocale(locale.LC_ALL,"de_DE")
for n,ent in enumerate(invoice.GetEntries()):
if type(ent) != Entry:
ent = Entry(instance=ent) # Add to method_returns_list
line_str = u""
descr = ent.GetDescription()
price = ent.GetInvPrice().to_double()
n = ent.GetQuantity()
if type(ent) != Entry:
ent=Entry(instance=ent) # Add to method_returns_list
uprice = locale.currency(price).rstrip(" EUR")
un = unicode(
int(float(n.num()) / n.denom())
) # choose best way to format numbers according to locale
descr = ent.GetDescription()
price = ent.GetInvPrice().to_double()
n = ent.GetQuantity()
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"}"
uprice = locale.currency(price).rstrip(" EUR")
un = unicode(int(float(n.num())/n.denom())) # choose best way to format numbers according to locale
# print(line_str)
ent_str += line_str
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"}"
lco_out += write_variable("entries", ent_str)
#print(line_str)
ent_str += line_str
lco_out += write_variable("entries",ent_str)
return lco_out
return lco_out
def main(argv=None):
@ -180,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)
@ -201,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")
@ -236,7 +251,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)
@ -247,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())

View File

@ -28,7 +28,8 @@
# @author Mark Jenkins, ParIT Worker Co-operative <mark@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()

View File

@ -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)

View File

@ -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

View File

@ -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()

View File

@ -46,7 +46,7 @@
# @author Mark Jenkins, ParIT Worker Co-operative <mark@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()

View File

@ -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()

View File

@ -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()

View File

@ -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()
trans2.CommitEdit()

View File

@ -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)
@ -153,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.
@ -161,19 +231,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):
@ -182,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
@ -213,6 +358,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

View File

@ -28,6 +28,9 @@
# @author Jeff Green, ParIT Worker Co-operative <jeff@parit.ca>
# @ingroup python_bindings
from enum import IntEnum
from urllib.parse import urlparse
from gnucash import gnucash_core_c
from gnucash import _sw_core_utils
@ -47,6 +50,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
@ -83,6 +92,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
@ -96,45 +156,82 @@ 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 url scheme is "xml" and
mode is SESSION_NEW_STORE or SESSION_NEW_OVERWRITE
qof_session_load is only called if is_new is set to False
@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.
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.
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.
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
@param mode The SessionOpenMode.
@note SessionOpenMode replaces deprecated ignore_lock, is_new and force_new.
This function can raise a GnuCashBackendException. If it does,
@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_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_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 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:
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
if book_uri[:3] != "xml" or not is_new:
is_new = mode in (SessionOpenMode.SESSION_NEW_STORE, SessionOpenMode.SESSION_NEW_OVERWRITE)
scheme = urlparse(book_uri).scheme
if not (is_new and scheme == 'xml'):
self.load()
except GnuCashBackendException as backend_exception:
self.end()
@ -177,12 +274,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
@ -533,6 +630,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 )

View File

@ -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})

View File

@ -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

View File

@ -0,0 +1,177 @@
# test cases for function_class.py
#
# @date 2020-06-18
# @author Christoph Holtermann <mail@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()

View File

@ -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()

View File

@ -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_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.
* `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.