Skip to content

Commit

Permalink
Merge branch 'feature/default-currency' into develop
Browse files Browse the repository at this point in the history
  • Loading branch information
sdementen committed Apr 16, 2018
2 parents 7071c0b + ad941e5 commit 3de30d5
Show file tree
Hide file tree
Showing 13 changed files with 637 additions and 36 deletions.
2 changes: 1 addition & 1 deletion .travis.yml
Expand Up @@ -12,7 +12,7 @@ env:
install:
- hash -r
- pip install pipenv==11.0.2
- pipenv install -e .[test]
- pipenv install -e .[test] --ignore-pipfile

script:
- python setup.py test
Expand Down
4 changes: 4 additions & 0 deletions DEVELOPER.rst
Expand Up @@ -9,6 +9,10 @@ Some note for developers:

python setup.py sdist

- to regenerate the requirements-dev.txt used by rtd::

requirements-dev.txt

- to upload file on PyPI::

twine upload dist\piecash-0.13.0.tar.gz
Expand Down
505 changes: 505 additions & 0 deletions Pipfile.lock

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion appveyor.yml
Expand Up @@ -112,7 +112,7 @@ install:
# compiled extensions and are not provided as pre-built wheel packages,
# pip will build them from source using the MSVC compiler matching the
# target Python version and architecture
- "pipenv install .[test]"
- "pipenv install .[test] --ignore-pipfile"
# - "pipenv graph"


Expand Down
18 changes: 17 additions & 1 deletion piecash/_common.py
@@ -1,5 +1,6 @@
from decimal import Decimal
import locale

from decimal import Decimal
from sqlalchemy import Column, VARCHAR, INTEGER, cast, Float
from sqlalchemy.ext.hybrid import hybrid_property

Expand All @@ -21,6 +22,7 @@ class GncValidationError(GnucashException):
class GncImbalanceError(GncValidationError):
pass


class GncConversionError(GnucashException):
pass

Expand Down Expand Up @@ -158,3 +160,17 @@ def __call__(self, **kwargs):

get = __call__


def get_system_currency_mnemonic():
"""Returns the mnemonic of the locale currency (and EUR if not defined).
At the target, it could also look in Gnucash configuration/registry to see if the user
has chosen another default currency.
"""

if locale.getlocale() == (None, None):
locale.setlocale(locale.LC_ALL, '')

mnemonic = locale.localeconv()['int_curr_symbol'].strip() or "EUR"

return mnemonic
40 changes: 21 additions & 19 deletions piecash/core/book.py
@@ -1,18 +1,19 @@
import locale
import warnings
from collections import defaultdict
from operator import attrgetter

from sqlalchemy import Column, VARCHAR, ForeignKey
from sqlalchemy.orm import relation, aliased, joinedload
from sqlalchemy.orm.base import instance_state
from sqlalchemy.orm.exc import NoResultFound

from . import factories
from .account import Account
from .commodity import Commodity, Price
from .transaction import Split, Transaction
from ..business.invoice import Invoice
from .._common import CallableList, GnucashException
from .._declbase import DeclarativeBaseGuid
from ..business.invoice import Invoice
from ..sa_extra import kvp_attribute


Expand Down Expand Up @@ -56,6 +57,7 @@ class Book(DeclarativeBaseGuid):
Attributes:
root_account (:class:`piecash.core.account.Account`): the root account of the book
root_template (:class:`piecash.core.account.Account`): the root template of the book (usage not yet clear...)
default_currency (:class:`piecash.core.commodity.Commodity`): the currency of the root account (=default currency of the book)
uri (str): connection string of the book (set by the GncSession when accessing the book)
session (:class:`sqlalchemy.orm.session.Session`): the sqlalchemy session encapsulating the book
use_trading_accounts (bool): true if option "Use trading accounts" is enabled
Expand Down Expand Up @@ -108,13 +110,17 @@ class Book(DeclarativeBaseGuid):
to_gnc=lambda v: float(v),
default=0)

counter_customer = kvp_attribute("counters/gncCustomer", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)
counter_customer = kvp_attribute("counters/gncCustomer", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v),
default=0)
counter_vendor = kvp_attribute("counters/gncVendor", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)
counter_employee = kvp_attribute("counters/gncEmployee", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)
counter_invoice = kvp_attribute("counters/gncInvoice", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)
counter_employee = kvp_attribute("counters/gncEmployee", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v),
default=0)
counter_invoice = kvp_attribute("counters/gncInvoice", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v),
default=0)
counter_job = kvp_attribute("counters/gncJob", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)
counter_bill = kvp_attribute("counters/gncBill", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)
counter_exp_voucher = kvp_attribute("counters/gncExpVoucher", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)
counter_exp_voucher = kvp_attribute("counters/gncExpVoucher", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v),
default=0)
counter_order = kvp_attribute("counters/gncOrder", from_gnc=lambda v: int(v), to_gnc=lambda v: int(v), default=0)

def __init__(self, root_account=None, root_template=None):
Expand All @@ -134,14 +140,13 @@ def control_mode(self):

@property
def default_currency(self):
try:
return self["default-currency"].value
except KeyError:
if locale.getlocale() == (None, None):
locale.setlocale(locale.LC_ALL, '')
mnemonic = locale.localeconv()['int_curr_symbol'].strip() or "EUR"
def_curr = self["default-currency"] = self.currencies(mnemonic=mnemonic)
return def_curr
return self.root_account.commodity

@default_currency.setter
def default_currency(self, value):
assert isinstance(value, Commodity) and value.namespace == "CURRENCY"

self.root_account.commodity = value

@property
def book(self):
Expand Down Expand Up @@ -361,11 +366,9 @@ def invoices(self):
gives easy access to all commodities in the book through a :class:`piecash.model_common.CallableList`
of :class:`piecash.core.commodity.Commodity`
"""
from .commodity import Commodity

return CallableList(self.session.query(Invoice))


@property
def currencies(self):
"""
Expand Down Expand Up @@ -450,13 +453,12 @@ def preload(self):

# load all splits
splits = self.session.query(Split).join(Transaction).options(
joinedload("account"),
joinedload("lot")) \
joinedload("account"),
joinedload("lot")) \
.order_by(Transaction.post_date, Split.value).all()

return accounts, splits


def splits_df(self, additional_fields=None):
"""
Return a pandas DataFrame with all splits (:class:`piecash.core.commodity.Split`) from the book
Expand Down
10 changes: 6 additions & 4 deletions piecash/core/session.py
@@ -1,13 +1,14 @@
import datetime
import os
import shutil
import socket
from collections import defaultdict

import datetime
import socket
from sqlalchemy import event, Column, VARCHAR, INTEGER, Table, PrimaryKeyConstraint
from sqlalchemy.sql.ddl import DropConstraint, DropIndex
from sqlalchemy_utils import database_exists

from piecash.core import factories
from .book import Book
from .._common import GnucashException
from ..sa_extra import create_piecash_engine, DeclarativeBase, Session
Expand Down Expand Up @@ -248,9 +249,10 @@ def create_book(sqlite_file=None,
# create commodities and initial accounts
from .account import Account

b.root_account = Account(name="Root Account", type="ROOT", commodity=None, book=b)
b.root_account = Account(name="Root Account", type="ROOT",
commodity=factories.create_currency_from_ISO(currency),
book=b)
b.root_template = Account(name="Template Root", type="ROOT", commodity=None, book=b)
b["default-currency"] = b.currencies(mnemonic=currency)
b.save()

return b
Expand Down
44 changes: 44 additions & 0 deletions requirements-dev.txt
@@ -0,0 +1,44 @@
alabaster==0.7.10
attrs==17.4.0
babel==2.5.3
certifi==2018.1.18
chardet==3.0.4
colorama==0.3.9; sys_platform == 'win32'
coverage==4.5.1
docutils==0.14
idna==2.6
imagesize==1.0.0
jinja2==2.10
markupsafe==1.0
more-itertools==4.1.0
numpy==1.14.2
packaging==17.1
pandas==0.22.0
pew==1.1.5
pipenv==11.0.2
pluggy==0.6.0
pockets==0.6.2
psutil==5.3.1; sys_platform == 'win32'
psycopg2==2.7.4
py==1.5.3
pygments==2.2.0
pymysql==0.8.0
pyparsing==2.2.0
pytest-cov==2.5.1
pytest==3.5.0
python-dateutil==2.7.2
pytz==2018.4
qifparse==0.5
requests==2.18.4
six==1.11.0
snowballstemmer==1.2.1
sphinx-rtd-theme==0.3.0
sphinx==1.7.2
sphinxcontrib-napoleon==0.6.1
sphinxcontrib-programoutput==0.11
sphinxcontrib-websupport==1.0.1
tox-pipenv==1.4.1
tox==2.9.1
urllib3==1.22
virtualenv-clone==0.3.0
virtualenv==15.2.0
4 changes: 2 additions & 2 deletions tests/test_book.py
Expand Up @@ -40,7 +40,7 @@ def test_create_default(self, new_book):
assert len(root_accs) == 2

# no slots
assert len(new_book.slots) == 1
assert len(new_book.slots) == 0

def test_create_save_cancel_flush(self, new_book):
EUR = new_book.commodities[0]
Expand Down Expand Up @@ -237,7 +237,7 @@ def test_book_options(self, new_book):
assert new_book.use_split_action_field == False
assert new_book.RO_threshold_day == 0

assert len(new_book.slots) == 1
assert len(new_book.slots) == 0

with pytest.raises(KeyError):
new_book["options"]
Expand Down
13 changes: 8 additions & 5 deletions tests/test_helper.py
@@ -1,9 +1,11 @@
# -*- coding: latin-1 -*-
import os
import sys
from datetime import date

import pytest
from datetime import date
from sqlalchemy_utils import database_exists, drop_database

from piecash import create_book, open_book, Account, Commodity, Employee, Customer, Vendor, Transaction, Split, Price

test_folder = os.path.dirname(os.path.realpath(__file__))
Expand All @@ -13,7 +15,8 @@
file_template_full = os.path.join(book_folder, "test_book.gnucash")
file_for_test_full = os.path.join(test_folder, "test_book_for_test.gnucash")
file_ghost_kvp_scheduled_transaction = os.path.join(book_folder, "ghost_kvp_scheduled_transaction.gnucash")
file_ghost_kvp_scheduled_transaction_for_test = os.path.join(test_folder, "ghost_kvp_scheduled_transaction_for_test.gnucash")
file_ghost_kvp_scheduled_transaction_for_test = os.path.join(test_folder,
"ghost_kvp_scheduled_transaction_for_test.gnucash")

if sys.version_info.major == 3:
def run_file(fname):
Expand Down Expand Up @@ -273,6 +276,7 @@ def book_sample(request):
with open_book(file_template_full) as book:
yield book


@pytest.yield_fixture()
def book_complex(request):
"""
Expand All @@ -291,6 +295,5 @@ def is_inmemory_sqlite(book_basic):
return book_basic.uri.database == ":memory:"


needweb = pytest.mark.skipif(not bool(os.environ.get("DOGOONWEB", False)),
reason="no access to web")

needweb = pytest.mark.skipif(not (os.environ.get("DOGOONWEB", "False") == "True"),
reason="no access to web")
3 changes: 1 addition & 2 deletions tests/test_integration.py
Expand Up @@ -113,7 +113,7 @@ def test_slots_create_access(self, book):

def test_slots_strings_access(self, book):
b = book
del b["default-currency"]

b["a/b/c/d/e"] = 1
book.book.flush()
assert b["a"]["b"]["c"]["d"]["e"].value == 1
Expand Down Expand Up @@ -170,7 +170,6 @@ def test_slots_strings_access(self, book):
assert {n for (n,) in book.session.query(Slot._name)} == set([])

def test_smart_slots(self, book):
del book["default-currency"]
book["account"] = book.root_account
assert book.slots[0].guid_val == book.root_account.guid
assert book["account"].value == book.root_account
Expand Down
2 changes: 1 addition & 1 deletion tests/test_model_core.py
Expand Up @@ -69,7 +69,7 @@ def test_commodities(self, session):
def test_slots(self, session):
# no slots in an empty gnucash file but the default_currency
slots = session.query(Slot._name).all()
assert slots == [('default-currency',)]
assert slots == []

def test_versions(self, session):
# confirm versions of tables
Expand Down
26 changes: 26 additions & 0 deletions tests/test_session.py
@@ -1,9 +1,13 @@
# coding=utf-8
from __future__ import unicode_literals

import locale
import sys

import pytest

from piecash import create_book, Account, open_book
from piecash._common import get_system_currency_mnemonic
from piecash.core.session import build_uri
from test_helper import db_sqlite_uri, db_sqlite, new_book, new_book_USD, book_uri, book_db_config

Expand Down Expand Up @@ -63,3 +67,25 @@ def test_build_uri(self):
# When run with just the name (without sqlite:// prefix):
uri = "some_file"
assert build_uri(sqlite_file=uri) == sqlite_uri


def test_get_system_currency_mnemonic():
assert get_system_currency_mnemonic() == "EUR"


def test_get_system_currency_mnemonic_US():
l = locale.getlocale()

if sys.platform == "win32":
# see https://docs.moodle.org/dev/Table_of_locales
locale.setlocale(locale.LC_ALL, 'English_United States.1252')
else:
locale.setlocale(locale.LC_ALL, 'en_US')

assert get_system_currency_mnemonic() == "USD"

locale.setlocale(locale.LC_ALL, l)


def test_get_system_currency_mnemonic_after():
assert get_system_currency_mnemonic() == "EUR"

0 comments on commit 3de30d5

Please sign in to comment.