mirror of
https://github.com/Gnucash/gnucash.git
synced 2025-02-25 18:55:30 -06:00
Merge Christoph Holtermann's 'python-sessionOpenMode' into master.
This commit is contained in:
commit
b0b238958e
@ -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
|
||||
|
@ -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>
|
||||
|
68
bindings/python/deprecation.py
Normal file
68
bindings/python/deprecation.py
Normal 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
|
||||
|
@ -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)
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
||||
|
@ -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())
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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)
|
||||
|
@ -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
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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
|
||||
|
@ -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 )
|
||||
|
||||
|
@ -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})
|
||||
|
@ -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
|
||||
|
177
bindings/python/tests/test_function_class.py
Normal file
177
bindings/python/tests/test_function_class.py
Normal 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()
|
@ -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()
|
||||
|
@ -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.
|
||||
|
Loading…
Reference in New Issue
Block a user