From 44f37509879786e4341e54eeda7284bbf7dcb84f Mon Sep 17 00:00:00 2001 From: James Rossini Date: Thu, 2 Oct 2025 16:22:07 +0200 Subject: [PATCH] - Added entities "Security" and "Currency" - Adapted setup.py and .gitignore Partially reverted changes in setup.py (for pull request, got ahead of myself) - fileinfo, payee: Adaptations to new file format 1.9 (KMM version 5.2.x) - tag: Small improvements - kmy: Improved readability Updated README (and changed extension according to newer conventions) Updated tests: - Converted Test.kmy to new file format - Added test cases for new entities (currencies, securities) - Adapted test cases for new file format (fileinfo) - Removed redundant files - Some small stuff Tests: Small corr.s in new files Reverted README to RST format (so used to writing Markdown...) --- .gitignore | 4 + README.rst | 32 ++++- kmy/__init__.py | 2 + kmy/currency.py | 33 +++++ kmy/fileinfo.py | 2 + kmy/kmy.py | 25 ++++ kmy/payee.py | 24 +++- kmy/security.py | 39 +++++ kmy/tag.py | 6 +- setup.py | 7 +- tests/Test.kmy | Bin 1679 -> 1877 bytes tests/Test.kmy.1~ | Bin 1596 -> 0 bytes tests/Test.kmy.2~ | Bin 1016 -> 0 bytes tests/Test.kmy.xml | 136 ------------------ tests/test_account.py | 4 +- tests/test_currency.py | 42 ++++++ tests/test_fileinfo.py | 7 +- tests/test_institution.py | 2 +- ...address.py => test_institution_address.py} | 0 ..._payeeaddress.py => test_payee_address.py} | 0 tests/test_security.py | 45 ++++++ tests/test_tag.py | 2 +- 22 files changed, 255 insertions(+), 157 deletions(-) create mode 100644 kmy/currency.py create mode 100644 kmy/security.py delete mode 100644 tests/Test.kmy.1~ delete mode 100644 tests/Test.kmy.2~ delete mode 100644 tests/Test.kmy.xml create mode 100644 tests/test_currency.py rename tests/{test_institutionaddress.py => test_institution_address.py} (100%) rename tests/{test_payeeaddress.py => test_payee_address.py} (100%) create mode 100644 tests/test_security.py diff --git a/.gitignore b/.gitignore index d4b323f..369601a 100644 --- a/.gitignore +++ b/.gitignore @@ -4,3 +4,7 @@ /build/ /.idea/ /**/__pycache__/ +# +.project +.pydevproject +.directory diff --git a/README.rst b/README.rst index 2660773..d50b4c6 100644 --- a/README.rst +++ b/README.rst @@ -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 `__). + +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 @@ -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/ \ No newline at end of file +.. _KMyMoney: https://kmymoney.org/ diff --git a/kmy/__init__.py b/kmy/__init__.py index 65be0e7..9ca5037 100644 --- a/kmy/__init__.py +++ b/kmy/__init__.py @@ -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 diff --git a/kmy/currency.py b/kmy/currency.py new file mode 100644 index 0000000..4ca68de --- /dev/null +++ b/kmy/currency.py @@ -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'] diff --git a/kmy/fileinfo.py b/kmy/fileinfo.py index 10cae0e..613212c 100644 --- a/kmy/fileinfo.py +++ b/kmy/fileinfo.py @@ -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})" @@ -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 diff --git a/kmy/kmy.py b/kmy/kmy.py index d936eda..8353005 100644 --- a/kmy/kmy.py +++ b/kmy/kmy.py @@ -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 @@ -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): @@ -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 diff --git a/kmy/payee.py b/kmy/payee.py index 9b22659..ddc057e 100644 --- a/kmy/payee.py +++ b/kmy/payee.py @@ -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}')" @@ -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(" ") 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(" ") 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(): diff --git a/kmy/security.py b/kmy/security.py new file mode 100644 index 0000000..aa00fff --- /dev/null +++ b/kmy/security.py @@ -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')) diff --git a/kmy/tag.py b/kmy/tag.py index d616fcc..bbf4c88 100644 --- a/kmy/tag.py +++ b/kmy/tag.py @@ -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): @@ -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'] diff --git a/setup.py b/setup.py index 45987c2..d4e3725 100644 --- a/setup.py +++ b/setup.py @@ -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( @@ -26,4 +27,4 @@ "Operating System :: MacOS :: MacOS X", "Operating System :: Microsoft :: Windows", ] -) \ No newline at end of file +) diff --git a/tests/Test.kmy b/tests/Test.kmy index 03b6bc031290da798fb8b61819f81f055d6a9e3d..90bea83ee7d065169593803a94feb6952f2c5eed 100644 GIT binary patch literal 1877 zcmV-b2delViwFq0tKVn<15{;mbS`*pYyj0-S$Cs06n^iopiEC+<`4s3vefR>F=RsH z?HD{WewrhL9CO0r0#3%$)BoNpAz|7vx%eepfv8Aq`j1`ScU zkVOFpcIddlV?!KTchZ&k?)RVSKWn{~HR|iaLub_Kb@h>SXS8*AvC#EMY6BQj;vPFLG@A|Lw#uK z10g^@C^HJ@5q$)feB^o!Q73;yE?UzDx@ih=5@DP)M3ep%1=~#)4bci;0ukO(Sgmj# zBny)?MB%GDYl!_2UU{w1fsO=wcs30K7-;cF5JTofiI|OOdKqE@ zl0F$-(=x0fF`3C=<+>+y?GAYZ+sVrUE%RPL@RE(C0C82Ow*;TD@V{GBlHS!?23Hoqt6w3#tG9`bLp|tEd9eAtBSb zX+BOvx@cyikN3<-AMbkt2q@UwLkBLZ4mWf$fk$3TEE>X(1 z(5G_xZqZkv16nLL)Au@N&gPlB!IU|eyD)k|k@KTKVUMY|w|b`4(z_NU;56}A&G+2- z;gQ09Uv&Lj6bTk0PUnSjibqJ$qaAu7q?R{yu*}mK7sq3j%Vy0qaLvTIxoO5RNzF3^ z3?|5?xCj;x5pwJ>uwBYt+T+8HIC0ZSHYdJW!BX`Cp z+VhB556B#v+R{em@V2P@O7GV6v~kE~K0mFA&QjJ^n%XWJyB;{nd8txl&eaDTjorZU zTzmS2-2?|`;H`WjDf$-x65}&dk_CKo9zUWnJ_8Z86Nxb3sOX=Yhb)WhZV;fUe-0ul z`y;7(s?+>6KYtk2mR!LG@PuYAob%4WmwG%IJiD*(P{_*rD9IHNMRKyEDd&ielog5Y zCycEw{mYN6OoNQVO~=}7%uXUpTj8o(;Buch8t-nP>u%A5(d6`fi#!Aehxky}c+f{5 zn(d+9ZyJNVbC9<1gcq4)Cb-E7dq&=z6i%>I&SN%++wU{wEhOf)sPplQxmz3B_DW~;{#mj3JXC^(j#rquL3ZMB!wlb?`%qn?e zmCd~N(L!(&s0SwBF!;CWf0*MI)RtbaclG?9QQ7DjmH*B&b}*}C%oYOe{9C|jUQoz-shblLzSDJK?3EC*POGD8?;z#?6G}%a`JC*U1L`J*kZ= zs_I~AqKsv!QnoLpam_)JYU4``if{qM3 zo`<5P!oP5J%Ll3(+pL4>Qe3>=9~dp2-M0?(es91cvwl0&-s`;4>$Z)q{%7x&v>}%j P|FP#^i%YpWDI@>@9F&$63cm1?S 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 diff --git a/tests/Test.kmy.2~ b/tests/Test.kmy.2~ deleted file mode 100644 index 9d1ca6da3a6b1c140818784b71104311a7594496..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 1016 zcmVF%YY>CNJvS znX%<-^H<~T?>{&{`jM9`7Z|<2S>8k;UYggz9K*)J8aZMN!s`g;$nj$ACP5T_^xXub zoN_iZ?1?=wr?xpgGc0*%?vmu=P2>mH0rtOp@&k?uXhTH~P?hXD_-icwMT#A31j9)Q zj!{i3P`PZjt(=0J6)nn{fyH1*Rj%*Hm=MHS$u@UQ4d)X*HBi=UYrdNqo*W4Wh#)M4 zC~5ww4fJoZ0pj4JMbmLwdN4Oo$6Lnu%ph`9-~=Es2||(tNh%m3y?)`B#4_4KUDNVj zbKGn>LENaEozfgm63 zB<{OIk+L|5FI8lFvEog5R??1Fkml=x~quJ0{bF9D1{gYJ&K9^A9jaJ8KmfeqkwtzHqUeHyDW^D

    T!=q!0g7M%9SR;lnFFF&~R6ffK$*2y5|=$`Ec zunMcI`Ts=At*G;WUT4UWo^g&0b6J3r*bRy6ohrzqEwq6GKWb5w2NkOtSwovjkj^!0 zKv}yx>S(0H$>i{-t20^5gZ&i{3t)f8WRcc}x;uzRH#5#{OC;qGcA9C*H*Jv#&c*&d zY&wmHXst_nD@rI@(fYoSu~R(us`p`)Ui8?>s1Q90(R>z0`$-thM?j9=ih)$e8pTtl z!8r6naGW*!+`F$Tb45Gmsp~O75%IQ894_+V$d~oS>fE+-X0E5JiFtmOU7D+loSJsV zE~m_9xy>%Vsz&h;ghRYPu%FJ4nk0g~G!ByBxO+C|yL*=vDSfUohqTM$u#_yV3 z^h~IuYf-gehmWLDI1fVnXLR*&1K4`_zsP^=Yy}wH3;+PP`2$q| diff --git a/tests/Test.kmy.xml b/tests/Test.kmy.xml deleted file mode 100644 index 2eba809..0000000 --- a/tests/Test.kmy.xml +++ /dev/null @@ -1,136 +0,0 @@ - - - - - - - - - - -
    - - - -
    - - - - - - - - -
    - - -
    - - -
    - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/tests/test_account.py b/tests/test_account.py index 4e0a421..f17d4ff 100644 --- a/tests/test_account.py +++ b/tests/test_account.py @@ -11,7 +11,7 @@ def setUp(self): self.account0 = self.accounts[0] def test_read_accounts_count(self): - self.assertEqual(11, len(self.accounts)) + self.assertEqual(13, len(self.accounts)) def test_read_number(self): self.assertEqual('', self.account0.number) @@ -47,7 +47,7 @@ def test_read_id(self): self.assertEqual('AStd::Asset', self.account0.id) def test_read_subaccounts(self): - self.assertEqual(1, len(self.account0.subAccounts)) + self.assertEqual(2, len(self.account0.subAccounts)) def test_read_subaccount_id(self): self.assertEqual('A000001', self.account0.subAccounts[0].id) diff --git a/tests/test_currency.py b/tests/test_currency.py new file mode 100644 index 0000000..d16b001 --- /dev/null +++ b/tests/test_currency.py @@ -0,0 +1,42 @@ +import unittest +from kmy.kmy import Kmy + +file_name = 'Test.kmy' + + +class TestCurrency(unittest.TestCase): + def setUp(self): + mm = Kmy.from_kmy_file(file_name) + self.currencies = mm.currencies + self.curr0 = self.currencies[0] + + def test_read_currencies_count(self): + self.assertEqual(1, len(self.currencies)) + + def test_read_id(self): + self.assertEqual("USD", self.curr0.id) + + def test_read_name(self): + self.assertEqual("US-Dollar", self.curr0.name) + + def test_read_symbol(self): + self.assertEqual("$", self.curr0.symbol) + + def test_read_type(self): + self.assertEqual("3", self.curr0.type) + + def test_read_rounding_method(self): + self.assertEqual("7", self.curr0.roundingMethod) + + def test_read_saf(self): + self.assertEqual("100", self.curr0.saf) + + def test_read_pp(self): + self.assertEqual("4", self.curr0.pp) + + def test_read_scf(self): + self.assertEqual("100", self.curr0.scf) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_fileinfo.py b/tests/test_fileinfo.py index 4daa082..bf2aca8 100644 --- a/tests/test_fileinfo.py +++ b/tests/test_fileinfo.py @@ -13,13 +13,16 @@ def test_read_creationDate(self): self.assertEqual('2020-12-13', self.fileInfo.creationDate) def test_read_lastModifiedDate(self): - self.assertEqual('2020-12-13', self.fileInfo.lastModifiedDate) + self.assertEqual('2025-10-03T12:53:29+02:00', self.fileInfo.lastModifiedDate) def test_read_version(self): self.assertEqual('1', self.fileInfo.version) def test_read_fixVersion(self): - self.assertEqual('5', self.fileInfo.fixVersion) + self.assertEqual('9', self.fileInfo.fixVersion) + + def test_read_appVersion(self): + self.assertEqual('5.2.1-c9bd024', self.fileInfo.appVersion) if __name__ == '__main__': diff --git a/tests/test_institution.py b/tests/test_institution.py index dca1bb2..773c282 100644 --- a/tests/test_institution.py +++ b/tests/test_institution.py @@ -14,7 +14,7 @@ def test_read_institutions_count(self): self.assertEqual(1, len(self.institutions)) def test_read_institution_sortcode(self): - self.assertEqual('Routing number', self.institution0.sortcode) + self.assertEqual('Routing number', self.institution0.sortCode) def test_read_institution_manager(self): self.assertEqual('', self.institution0.manager) diff --git a/tests/test_institutionaddress.py b/tests/test_institution_address.py similarity index 100% rename from tests/test_institutionaddress.py rename to tests/test_institution_address.py diff --git a/tests/test_payeeaddress.py b/tests/test_payee_address.py similarity index 100% rename from tests/test_payeeaddress.py rename to tests/test_payee_address.py diff --git a/tests/test_security.py b/tests/test_security.py new file mode 100644 index 0000000..4387898 --- /dev/null +++ b/tests/test_security.py @@ -0,0 +1,45 @@ +import unittest +from kmy.kmy import Kmy + +file_name = 'Test.kmy' + + +class TestSecurity(unittest.TestCase): + def setUp(self): + mm = Kmy.from_kmy_file(file_name) + self.securities = mm.securities + self.sec0 = self.securities[0] + + def test_read_securities_count(self): + self.assertEqual(1, len(self.securities)) + + def test_read_id(self): + self.assertEqual("E000001", self.sec0.id) + + def test_read_name(self): + self.assertEqual("3M", self.sec0.name) + + def test_read_symbol(self): + self.assertEqual("MMM", self.sec0.symbol) + + def test_read_type(self): + self.assertEqual("0", self.sec0.type) + + def test_read_rounding_method(self): + self.assertEqual("7", self.sec0.roundingMethod) + + def test_read_saf(self): + self.assertEqual("100", self.sec0.saf) + + def test_read_pp(self): + self.assertEqual("4", self.sec0.pp) + + def test_read_trading_currency(self): + self.assertEqual("USD", self.sec0.tradingCurrency) + + def test_read_trading_market(self): + self.assertEqual("NYSE", self.sec0.tradingMarket) + + +if __name__ == '__main__': + unittest.main() diff --git a/tests/test_tag.py b/tests/test_tag.py index aa56814..9914f3f 100644 --- a/tests/test_tag.py +++ b/tests/test_tag.py @@ -17,7 +17,7 @@ def test_read_closed(self): self.assertEqual(False, self.tag0.closed) def test_read_tagcolor(self): - self.assertEqual('#000000', self.tag0.tagColor) + self.assertEqual('#000000', self.tag0.color) def test_read_name(self): self.assertEqual('Bar Tag', self.tag0.name)