Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,7 @@
/build/
/.idea/
/**/__pycache__/
#
.project
.pydevproject
.directory
32 changes: 28 additions & 4 deletions README.rst
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
kmy KMyMoney_ data file reader
================================
kmy: KMyMoney_ Data File Reader
===============================

`kmy` is a free and open-source Pyton library for reading the XML file
format of the KMyMoney personal finance software
(`kmymoney.org <https://kmymoney.org>`__).

It is not directly affiliated with / sponsored or coordinated by the
developers of the KMyMoney project.

Compatibility
-------------

This package is compatible with V. 5.2.x of KMyMoney. Files generated
with V. 5.1.x are not supported any more.

Major Changes
-------------

V. 0.0.3 → 0.1
~~~~~~~~~~~~~~

- Adaptations to new file format (KMM V. 5.2.x)
- New entities: currencies, securities
- Improved entities / attribute coverage for tags and payees

Usage
------------
-----

`kmy` is straightforward to use (Python 3 tested only) - e.g.

.. code-block:: python
Expand All @@ -28,4 +52,4 @@ There are still some outliers like `tags` that are not yet implemented.
Feel free to submit Issues or MRs. It works for what I want for now, but
we shall see how or if it evolves :-)

.. _KMyMoney: https://kmymoney.org/
.. _KMyMoney: https://kmymoney.org/
2 changes: 2 additions & 0 deletions kmy/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,5 +11,7 @@
from .subaccount import SubAccount
from .tag import Tag
from .transaction import Transaction
from .currency import Currency
from .security import Security
from .user import User
from .useraddress import UserAddress
33 changes: 33 additions & 0 deletions kmy/currency.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
from typing import Dict
from typing import List


class Currency:
def __init__(self):
self.id: str = ""
self.type: int = 0
self.name: str = ""
self.symbol: str = ""
self.roundingMethod: int = 0
self.saf: int = 0
self.pp: int = 0
self.scf: int = 0

def __repr__(self):
return f"{self.__class__.__name__}(name='{self.name}')"

@classmethod
def from_xml(cls, node):
currency = cls()
currency.init_from_xml(node)
return currency

def init_from_xml(self, node):
self.id = node.attrib['id']
self.type = node.attrib['type']
self.name = node.attrib['name']
self.symbol = node.attrib['symbol']
self.roundingMethod = node.attrib['rounding-method']
self.saf = node.attrib['saf']
self.pp = node.attrib['pp']
self.scf = node.attrib['scf']
2 changes: 2 additions & 0 deletions kmy/fileinfo.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ def __init__(self):
self.lastModifiedDate: str = ""
self.version: str = ""
self.fixVersion: str = ""
self.appVersion: str = ""

def __repr__(self):
return f"{self.__class__.__name__}(creationDate='{self.creationDate}', lastModifiedDate={self.lastModifiedDate})"
Expand All @@ -15,4 +16,5 @@ def from_xml(cls, node):
file_info.lastModifiedDate = node.find('LAST_MODIFIED_DATE').attrib['date']
file_info.version = node.find('VERSION').attrib['id']
file_info.fixVersion = node.find('FIXVERSION').attrib['id']
file_info.appVersion = node.find('APPVERSION').attrib['id']
return file_info
25 changes: 25 additions & 0 deletions kmy/kmy.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,12 @@

from .account import Account
from .costcenter import CostCenter
from .currency import Currency
from .fileinfo import FileInfo
from .institution import Institution
from .pairs import pairs_dict_from_xml
from .payee import Payee
from .security import Security
from .tag import Tag
from .transaction import Transaction
from .user import User
Expand All @@ -24,6 +26,8 @@ def __init__(self):
self.tags: List[Tag] = []
self.accounts: List[Account] = []
self.transactions: List[Transaction] = []
self.securities: List[Security] = []
self.currencies: List[Currency] = []
self.keyValuePairs: Dict[str, str] = {}

def __repr__(self):
Expand All @@ -37,31 +41,52 @@ def from_xml(cls, node):

def init_from_xml(self, node):
self.fileInfo = FileInfo.from_xml(node.find('FILEINFO'))

self.user = User.from_xml(node.find('USER'))

institution_nodes = node.find('INSTITUTIONS')
for institution_node in institution_nodes:
institution = Institution.from_xml(institution_node)
self.institutions.append(institution)

payee_nodes = node.find('PAYEES')
for payee_node in payee_nodes:
payee = Payee.from_xml(payee_node)
self.payees.append(payee)

# Cost-centers, according to KMyMoney's official
# documentation, are not used yet/reserved for
# future versions.
costcenter_nodes = node.find('COSTCENTERS')
for costcenter_node in costcenter_nodes:
costcenter = CostCenter.from_xml(costcenter_node)
self.costCenters.append(costcenter)

tag_nodes = node.find('TAGS')
for tag_node in tag_nodes:
tag = Tag.from_xml(tag_node)
self.tags.append(tag)

account_nodes = node.find('ACCOUNTS')
for account_node in account_nodes:
account = Account.from_xml(account_node)
self.accounts.append(account)

transaction_nodes = node.find('TRANSACTIONS')
for transaction_node in transaction_nodes:
transaction = Transaction.from_xml(transaction_node)
self.transactions.append(transaction)

security_nodes = node.find('SECURITIES')
for security_node in security_nodes:
security = Security.from_xml(security_node)
self.securities.append(security)

currency_nodes = node.find('CURRENCIES')
for currency_node in currency_nodes:
currency = Currency.from_xml(currency_node)
self.currencies.append(currency)

self.keyValuePairs = pairs_dict_from_xml(node.find('KEYVALUEPAIRS'))

@classmethod
Expand Down
24 changes: 18 additions & 6 deletions kmy/payee.py
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,16 @@ def __init__(self):
self.reference: str = ""
self.name: str = ""
self.email: str = ""
self.notes: str = ""
self.id: str = ""
self.matchingEnabled: bool = False
self.address: PayeeAddress = PayeeAddress()
self.payeeIdentifiers: List[Dict[str, str]] = list()
self.usingMatchkey: bool = False
self.matchkeys: List[str]
self.matchingEnabled: bool = False
self.matchIgnoreCase: bool = False
self.usingMatchKey: bool = False
self.matchKeys: List[str] # yes, one or several of them in one field
self.idPattern: str = ""
self.urlTemplate : str = ""

def __repr__(self):
return f"{self.__class__.__name__}(name='{self.name}')"
Expand All @@ -29,18 +33,26 @@ def init_from_xml(self, node):
self.reference = node.attrib['reference']
self.name = node.attrib['name']
self.email = node.attrib['email']
self.notes = node.attrib['notes'] if "notes" in node.attrib else ""
self.id = node.attrib['id']
self.matchingEnabled = (node.attrib['matchingenabled'] != '0')
self.usingMatchkey = (node.attrib["usingmatchkey"] != "0") if "usingmatchkey" in node.attrib else False
self.matchkeys = node.attrib["matchkey"].split("&#xa;") if "matchkeys" in node.attrib else []

address_node = node.find('ADDRESS')
if address_node is not None:
self.address = PayeeAddress.from_xml(address_node)

payee_identifier_nodes = node.findall("payeeIdentifier")
if payee_identifier_nodes is not None:
for payee_identifier_node in payee_identifier_nodes:
self.payeeIdentifiers.append(payee_identifier_node.attrib.copy())

self.matchingEnabled = (node.attrib['matchingenabled'] != "0")
self.matchIgnoreCase = (node.attrib['matchignorecase'] != "0") if "matchignorecase" in node.attrib else False
self.usingMatchKey = (node.attrib["usingmatchkey"] != "0") if "usingmatchkey" in node.attrib else False
self.matchKeys = node.attrib["matchkey"].split("&#10;") if "matchkey" in node.attrib else []

self.idPattern = node.attrib['idpattern'] if "idpattern" in node.attrib else ""
self.urlTemplate = node.attrib['urltemplate'] if "urltemplate" in node.attrib else ""

def matched_by(self, **kwargs: Dict[str, str]) -> bool:
def is_match(candidate: Dict[str, str]) -> bool:
for key, val in kwargs.items():
Expand Down
39 changes: 39 additions & 0 deletions kmy/security.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,39 @@
from typing import Dict
from typing import List

from .pairs import pairs_dict_from_xml


class Security:
def __init__(self):
self.id: str = ""
self.type: int = 0
self.name: str = ""
self.symbol: str = ""
self.roundingMethod: int = 0
self.saf: int = 0
self.pp: int = 0
self.tradingCurrency: str = ""
self.tradingMarket: str = ""
self.keyValuePairs: Dict[str, str] = {}

def __repr__(self):
return f"{self.__class__.__name__}(name='{self.name}')"

@classmethod
def from_xml(cls, node):
security = cls()
security.init_from_xml(node)
return security

def init_from_xml(self, node):
self.id = node.attrib['id']
self.type = node.attrib['type']
self.name = node.attrib['name']
self.symbol = node.attrib['symbol']
self.roundingMethod = node.attrib['rounding-method']
self.saf = node.attrib['saf']
self.pp = node.attrib['pp']
self.tradingCurrency = node.attrib['trading-currency']
self.tradingMarket = node.attrib['trading-market']
self.keyValuePairs = pairs_dict_from_xml(node.find('KEYVALUEPAIRS'))
6 changes: 4 additions & 2 deletions kmy/tag.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
class Tag:
def __init__(self):
self.closed: bool = False
self.tagColor: str = ""
self.color: str = ""
self.name: str = ""
self.notes: str = ""
self.id: str = ""

def __repr__(self):
Expand All @@ -16,6 +17,7 @@ def from_xml(cls, node):

def init_from_xml(self, node):
self.closed = (node.attrib['closed'] != '0')
self.tagColor = node.attrib['tagcolor']
self.color = node.attrib['tagcolor']
self.name = node.attrib['name']
self.notes = node.attrib['notes'] if "notes" in node.attrib else ""
self.id = node.attrib['id']
7 changes: 4 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
@@ -1,10 +1,11 @@
from setuptools import setup, find_packages

VERSION = '0.0.3'
VERSION = '0.1'
DESCRIPTION = 'KMyMoney (.kmy) file parser'
URL = 'https://github.com/timerickson/kmy'
LONG_DESCRIPTION = 'A simply library to read and provide typed access to KMyMoney data in .kmy files.' \
'It currently only supports readonly access.'
'It currently only supports read-only access.' \
'Only XML-based files, not SQLite-based ones.'

# Setting up
setup(
Expand All @@ -26,4 +27,4 @@
"Operating System :: MacOS :: MacOS X",
"Operating System :: Microsoft :: Windows",
]
)
)
Binary file modified tests/Test.kmy
Binary file not shown.
Binary file removed tests/Test.kmy.1~
Binary file not shown.
Binary file removed tests/Test.kmy.2~
Binary file not shown.
Loading