diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 9b43b18..5acc08b 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -11,7 +11,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10", "3.11"] + python-version: ["3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v2 diff --git a/Pipfile b/Pipfile index 2284bd1..3fbaad5 100644 --- a/Pipfile +++ b/Pipfile @@ -12,8 +12,10 @@ mypy = "*" types-mock = "*" exceptiongroup = {markers="python_version < '3.11'"} tomli = {markers="python_version < '3.11'"} +types-appdirs = "*" [packages] ofxstatement = {editable = true,path = "."} exceptiongroup = "*" -importlib_metadata = {version = ">=3.8", markers="python_version < '3.10'"} \ No newline at end of file +importlib_metadata = {version = ">=3.8", markers="python_version < '3.10'"} +zipp = {version = ">=3.8", markers="python_version < '3.10'"} diff --git a/Pipfile.lock b/Pipfile.lock index f56bba1..08a7e97 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,7 +1,7 @@ { "_meta": { "hash": { - "sha256": "9eff26da7c0885e82355bd0e9d77d60f3ebee69507ba41b063a5a5599ccc6f6f" + "sha256": "d01ad0c4b47d2fff1cffed79da795c043f9e96086adac9594cc73053658fa6ed" }, "pipfile-spec": 6, "requires": {}, @@ -31,13 +31,8 @@ "version": "==1.2.0" }, "importlib-metadata": { - "hashes": [ - "sha256:4805911c3a4ec7c3966410053e9ec6a1fecd629117df5adee56dfc9432a1081e", - "sha256:f238736bb06590ae52ac1fab06a3a9ef1d8dce2b7a35b5ab329371d6c8f5d2cc" - ], - "index": "pypi", "markers": "python_version < '3.10'", - "version": "==7.0.1" + "version": "==3.8" }, "ofxstatement": { "editable": true, @@ -45,42 +40,43 @@ }, "zipp": { "hashes": [ - "sha256:0e923e726174922dce09c53c59ad483ff7bbb8e572e00c7f7c46b88556409f31", - "sha256:84e64a1c28cf7e91ed2078bb8cc8c259cb19b76942096c8d7b84947690cabaf0" + "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b", + "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715" ], - "markers": "python_version < '3.10' and python_version >= '3.8'", - "version": "==3.17.0" + "index": "pypi", + "markers": "python_version >= '3.8'", + "version": "==3.18.1" } }, "develop": { "black": { "hashes": [ - "sha256:0808494f2b2df923ffc5723ed3c7b096bd76341f6213989759287611e9837d50", - "sha256:1fa88a0f74e50e4487477bc0bb900c6781dbddfdfa32691e780bf854c3b4a47f", - "sha256:25e57fd232a6d6ff3f4478a6fd0580838e47c93c83eaf1ccc92d4faf27112c4e", - "sha256:2d9e13db441c509a3763a7a3d9a49ccc1b4e974a47be4e08ade2a228876500ec", - "sha256:3e1b38b3135fd4c025c28c55ddfc236b05af657828a8a6abe5deec419a0b7055", - "sha256:3fa4be75ef2a6b96ea8d92b1587dd8cb3a35c7e3d51f0738ced0781c3aa3a5a3", - "sha256:4ce3ef14ebe8d9509188014d96af1c456a910d5b5cbf434a09fef7e024b3d0d5", - "sha256:4f0031eaa7b921db76decd73636ef3a12c942ed367d8c3841a0739412b260a54", - "sha256:602cfb1196dc692424c70b6507593a2b29aac0547c1be9a1d1365f0d964c353b", - "sha256:6d1bd9c210f8b109b1762ec9fd36592fdd528485aadb3f5849b2740ef17e674e", - "sha256:78baad24af0f033958cad29731e27363183e140962595def56423e626f4bee3e", - "sha256:8d4df77958a622f9b5a4c96edb4b8c0034f8434032ab11077ec6c56ae9f384ba", - "sha256:97e56155c6b737854e60a9ab1c598ff2533d57e7506d97af5481141671abf3ea", - "sha256:9c4352800f14be5b4864016882cdba10755bd50805c95f728011bcb47a4afd59", - "sha256:a4d6a9668e45ad99d2f8ec70d5c8c04ef4f32f648ef39048d010b0689832ec6d", - "sha256:a920b569dc6b3472513ba6ddea21f440d4b4c699494d2e972a1753cdc25df7b0", - "sha256:ae76c22bde5cbb6bfd211ec343ded2163bba7883c7bc77f6b756a1049436fbb9", - "sha256:b18fb2ae6c4bb63eebe5be6bd869ba2f14fd0259bda7d18a46b764d8fb86298a", - "sha256:c04b6d9d20e9c13f43eee8ea87d44156b8505ca8a3c878773f68b4e4812a421e", - "sha256:c88b3711d12905b74206227109272673edce0cb29f27e1385f33b0163c414bba", - "sha256:dd15245c8b68fe2b6bd0f32c1556509d11bb33aec9b5d0866dd8e2ed3dba09c2", - "sha256:e0aaf6041986767a5e0ce663c7a2f0e9eaf21e6ff87a5f95cbf3675bfd4c41d2" + "sha256:2818cf72dfd5d289e48f37ccfa08b460bf469e67fb7c4abb07edc2e9f16fb63f", + "sha256:41622020d7120e01d377f74249e677039d20e6344ff5851de8a10f11f513bf93", + "sha256:4acf672def7eb1725f41f38bf6bf425c8237248bb0804faa3965c036f7672d11", + "sha256:4be5bb28e090456adfc1255e03967fb67ca846a03be7aadf6249096100ee32d0", + "sha256:4f1373a7808a8f135b774039f61d59e4be7eb56b2513d3d2f02a8b9365b8a8a9", + "sha256:56f52cfbd3dabe2798d76dbdd299faa046a901041faf2cf33288bc4e6dae57b5", + "sha256:65b76c275e4c1c5ce6e9870911384bff5ca31ab63d19c76811cb1fb162678213", + "sha256:65c02e4ea2ae09d16314d30912a58ada9a5c4fdfedf9512d23326128ac08ac3d", + "sha256:6905238a754ceb7788a73f02b45637d820b2f5478b20fec82ea865e4f5d4d9f7", + "sha256:79dcf34b33e38ed1b17434693763301d7ccbd1c5860674a8f871bd15139e7837", + "sha256:7bb041dca0d784697af4646d3b62ba4a6b028276ae878e53f6b4f74ddd6db99f", + "sha256:7d5e026f8da0322b5662fa7a8e752b3fa2dac1c1cbc213c3d7ff9bdd0ab12395", + "sha256:9f50ea1132e2189d8dff0115ab75b65590a3e97de1e143795adb4ce317934995", + "sha256:a0c9c4a0771afc6919578cec71ce82a3e31e054904e7197deacbc9382671c41f", + "sha256:aadf7a02d947936ee418777e0247ea114f78aff0d0959461057cae8a04f20597", + "sha256:b5991d523eee14756f3c8d5df5231550ae8993e2286b8014e2fdea7156ed0959", + "sha256:bf21b7b230718a5f08bd32d5e4f1db7fc8788345c8aea1d155fc17852b3410f5", + "sha256:c45f8dff244b3c431b36e3224b6be4a127c6aca780853574c00faf99258041eb", + "sha256:c7ed6668cbbfcd231fa0dc1b137d3e40c04c7f786e626b405c62bcd5db5857e4", + "sha256:d7de8d330763c66663661a1ffd432274a2f92f07feeddd89ffd085b5744f85e7", + "sha256:e19cb1c6365fd6dc38a6eae2dcb691d7d83935c10215aef8e6c38edee3f77abd", + "sha256:e2af80566f43c85f5797365077fb64a393861a3730bd110971ab7a0c94e873e7" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==23.12.1" + "version": "==24.3.0" }, "click": { "hashes": [ @@ -95,61 +91,61 @@ "toml" ], "hashes": [ - "sha256:04387a4a6ecb330c1878907ce0dc04078ea72a869263e53c72a1ba5bbdf380ca", - "sha256:0676cd0ba581e514b7f726495ea75aba3eb20899d824636c6f59b0ed2f88c471", - "sha256:0e8d06778e8fbffccfe96331a3946237f87b1e1d359d7fbe8b06b96c95a5407a", - "sha256:0eb3c2f32dabe3a4aaf6441dde94f35687224dfd7eb2a7f47f3fd9428e421058", - "sha256:109f5985182b6b81fe33323ab4707011875198c41964f014579cf82cebf2bb85", - "sha256:13eaf476ec3e883fe3e5fe3707caeb88268a06284484a3daf8250259ef1ba143", - "sha256:164fdcc3246c69a6526a59b744b62e303039a81e42cfbbdc171c91a8cc2f9446", - "sha256:26776ff6c711d9d835557ee453082025d871e30b3fd6c27fcef14733f67f0590", - "sha256:26f66da8695719ccf90e794ed567a1549bb2644a706b41e9f6eae6816b398c4a", - "sha256:29f3abe810930311c0b5d1a7140f6395369c3db1be68345638c33eec07535105", - "sha256:316543f71025a6565677d84bc4df2114e9b6a615aa39fb165d697dba06a54af9", - "sha256:36b0ea8ab20d6a7564e89cb6135920bc9188fb5f1f7152e94e8300b7b189441a", - "sha256:3cc9d4bc55de8003663ec94c2f215d12d42ceea128da8f0f4036235a119c88ac", - "sha256:485e9f897cf4856a65a57c7f6ea3dc0d4e6c076c87311d4bc003f82cfe199d25", - "sha256:5040148f4ec43644702e7b16ca864c5314ccb8ee0751ef617d49aa0e2d6bf4f2", - "sha256:51456e6fa099a8d9d91497202d9563a320513fcf59f33991b0661a4a6f2ad450", - "sha256:53d7d9158ee03956e0eadac38dfa1ec8068431ef8058fe6447043db1fb40d932", - "sha256:5a10a4920def78bbfff4eff8a05c51be03e42f1c3735be42d851f199144897ba", - "sha256:5b14b4f8760006bfdb6e08667af7bc2d8d9bfdb648351915315ea17645347137", - "sha256:5b2ccb7548a0b65974860a78c9ffe1173cfb5877460e5a229238d985565574ae", - "sha256:697d1317e5290a313ef0d369650cfee1a114abb6021fa239ca12b4849ebbd614", - "sha256:6ae8c9d301207e6856865867d762a4b6fd379c714fcc0607a84b92ee63feff70", - "sha256:707c0f58cb1712b8809ece32b68996ee1e609f71bd14615bd8f87a1293cb610e", - "sha256:74775198b702868ec2d058cb92720a3c5a9177296f75bd97317c787daf711505", - "sha256:756ded44f47f330666843b5781be126ab57bb57c22adbb07d83f6b519783b870", - "sha256:76f03940f9973bfaee8cfba70ac991825611b9aac047e5c80d499a44079ec0bc", - "sha256:79287fd95585ed36e83182794a57a46aeae0b64ca53929d1176db56aacc83451", - "sha256:799c8f873794a08cdf216aa5d0531c6a3747793b70c53f70e98259720a6fe2d7", - "sha256:7d360587e64d006402b7116623cebf9d48893329ef035278969fa3bbf75b697e", - "sha256:80b5ee39b7f0131ebec7968baa9b2309eddb35b8403d1869e08f024efd883566", - "sha256:815ac2d0f3398a14286dc2cea223a6f338109f9ecf39a71160cd1628786bc6f5", - "sha256:83c2dda2666fe32332f8e87481eed056c8b4d163fe18ecc690b02802d36a4d26", - "sha256:846f52f46e212affb5bcf131c952fb4075b55aae6b61adc9856222df89cbe3e2", - "sha256:936d38794044b26c99d3dd004d8af0035ac535b92090f7f2bb5aa9c8e2f5cd42", - "sha256:9864463c1c2f9cb3b5db2cf1ff475eed2f0b4285c2aaf4d357b69959941aa555", - "sha256:995ea5c48c4ebfd898eacb098164b3cc826ba273b3049e4a889658548e321b43", - "sha256:a1526d265743fb49363974b7aa8d5899ff64ee07df47dd8d3e37dcc0818f09ed", - "sha256:a56de34db7b7ff77056a37aedded01b2b98b508227d2d0979d373a9b5d353daa", - "sha256:a7c97726520f784239f6c62506bc70e48d01ae71e9da128259d61ca5e9788516", - "sha256:b8e99f06160602bc64da35158bb76c73522a4010f0649be44a4e167ff8555952", - "sha256:bb1de682da0b824411e00a0d4da5a784ec6496b6850fdf8c865c1d68c0e318dd", - "sha256:bf477c355274a72435ceb140dc42de0dc1e1e0bf6e97195be30487d8eaaf1a09", - "sha256:bf635a52fc1ea401baf88843ae8708591aa4adff875e5c23220de43b1ccf575c", - "sha256:bfd5db349d15c08311702611f3dccbef4b4e2ec148fcc636cf8739519b4a5c0f", - "sha256:c530833afc4707fe48524a44844493f36d8727f04dcce91fb978c414a8556cc6", - "sha256:cc6d65b21c219ec2072c1293c505cf36e4e913a3f936d80028993dd73c7906b1", - "sha256:cd3c1e4cb2ff0083758f09be0f77402e1bdf704adb7f89108007300a6da587d0", - "sha256:cfd2a8b6b0d8e66e944d47cdec2f47c48fef2ba2f2dff5a9a75757f64172857e", - "sha256:d0ca5c71a5a1765a0f8f88022c52b6b8be740e512980362f7fdbb03725a0d6b9", - "sha256:e7defbb9737274023e2d7af02cac77043c86ce88a907c58f42b580a97d5bcca9", - "sha256:e9d1bf53c4c8de58d22e0e956a79a5b37f754ed1ffdbf1a260d9dcfa2d8a325e", - "sha256:ea81d8f9691bb53f4fb4db603203029643caffc82bf998ab5b59ca05560f4c06" + "sha256:006d220ba2e1a45f1de083d5022d4955abb0aedd78904cd5a779b955b019ec73", + "sha256:06fe398145a2e91edaf1ab4eee66149c6776c6b25b136f4a86fcbbb09512fd10", + "sha256:175f56572f25e1e1201d2b3e07b71ca4d201bf0b9cb8fad3f1dfae6a4188de86", + "sha256:18cac867950943fe93d6cd56a67eb7dcd2d4a781a40f4c1e25d6f1ed98721a55", + "sha256:1a5ee18e3a8d766075ce9314ed1cb695414bae67df6a4b0805f5137d93d6f1cb", + "sha256:20a875bfd8c282985c4720c32aa05056f77a68e6d8bbc5fe8632c5860ee0b49b", + "sha256:2412e98e70f16243be41d20836abd5f3f32edef07cbf8f407f1b6e1ceae783ac", + "sha256:2599972b21911111114100d362aea9e70a88b258400672626efa2b9e2179609c", + "sha256:2ed37e16cf35c8d6e0b430254574b8edd242a367a1b1531bd1adc99c6a5e00fe", + "sha256:32b4ab7e6c924f945cbae5392832e93e4ceb81483fd6dc4aa8fb1a97b9d3e0e1", + "sha256:34423abbaad70fea9d0164add189eabaea679068ebdf693baa5c02d03e7db244", + "sha256:3507427d83fa961cbd73f11140f4a5ce84208d31756f7238d6257b2d3d868405", + "sha256:3733545eb294e5ad274abe131d1e7e7de4ba17a144505c12feca48803fea5f64", + "sha256:3ff5bdb08d8938d336ce4088ca1a1e4b6c8cd3bef8bb3a4c0eb2f37406e49643", + "sha256:3ff7f92ae5a456101ca8f48387fd3c56eb96353588e686286f50633a611afc95", + "sha256:42a9e754aa250fe61f0f99986399cec086d7e7a01dd82fd863a20af34cbce962", + "sha256:51593a1f05c39332f623d64d910445fdec3d2ac2d96b37ce7f331882d5678ddf", + "sha256:5b11f9c6587668e495cc7365f85c93bed34c3a81f9f08b0920b87a89acc13469", + "sha256:69f1665165ba2fe7614e2f0c1aed71e14d83510bf67e2ee13df467d1c08bf1e8", + "sha256:78cdcbf7b9cb83fe047ee09298e25b1cd1636824067166dc97ad0543b079d22f", + "sha256:7df95fdd1432a5d2675ce630fef5f239939e2b3610fe2f2b5bf21fa505256fa3", + "sha256:81a5fb41b0d24447a47543b749adc34d45a2cf77b48ca74e5bf3de60a7bd9edc", + "sha256:840456cb1067dc350af9080298c7c2cfdddcedc1cb1e0b30dceecdaf7be1a2d3", + "sha256:8562ca91e8c40864942615b1d0b12289d3e745e6b2da901d133f52f2d510a1e3", + "sha256:861d75402269ffda0b33af94694b8e0703563116b04c681b1832903fac8fd647", + "sha256:8b98c89db1b150d851a7840142d60d01d07677a18f0f46836e691c38134ed18b", + "sha256:a178b7b1ac0f1530bb28d2e51f88c0bab3e5949835851a60dda80bff6052510c", + "sha256:a8ddbd158e069dded57738ea69b9744525181e99974c899b39f75b2b29a624e2", + "sha256:ac4bab32f396b03ebecfcf2971668da9275b3bb5f81b3b6ba96622f4ef3f6e17", + "sha256:ac9e95cefcf044c98d4e2c829cd0669918585755dd9a92e28a1a7012322d0a95", + "sha256:adbdfcda2469d188d79771d5696dc54fab98a16d2ef7e0875013b5f56a251047", + "sha256:b3c8bbb95a699c80a167478478efe5e09ad31680931ec280bf2087905e3b95ec", + "sha256:b3f2b1eb229f23c82898eedfc3296137cf1f16bb145ceab3edfd17cbde273fb7", + "sha256:b4ae777bebaed89e3a7e80c4a03fac434a98a8abb5251b2a957d38fe3fd30088", + "sha256:b953275d4edfab6cc0ed7139fa773dfb89e81fee1569a932f6020ce7c6da0e8f", + "sha256:bf54c3e089179d9d23900e3efc86d46e4431188d9a657f345410eecdd0151f50", + "sha256:bf711d517e21fb5bc429f5c4308fbc430a8585ff2a43e88540264ae87871e36a", + "sha256:c00e54f0bd258ab25e7f731ca1d5144b0bf7bec0051abccd2bdcff65fa3262c9", + "sha256:c11ca2df2206a4e3e4c4567f52594637392ed05d7c7fb73b4ea1c658ba560265", + "sha256:c5f9683be6a5b19cd776ee4e2f2ffb411424819c69afab6b2db3a0a364ec6642", + "sha256:cf89ab85027427d351f1de918aff4b43f4eb5f33aff6835ed30322a86ac29c9e", + "sha256:d1b750a8409bec61caa7824bfd64a8074b6d2d420433f64c161a8335796c7c6b", + "sha256:d779a48fac416387dd5673fc5b2d6bd903ed903faaa3247dc1865c65eaa5a93e", + "sha256:d9a1ef0f173e1a19738f154fb3644f90d0ada56fe6c9b422f992b04266c55d5a", + "sha256:ddb79414c15c6f03f56cc68fa06994f047cf20207c31b5dad3f6bab54a0f66ef", + "sha256:ef00d31b7569ed3cb2036f26565f1984b9fc08541731ce01012b02a4c238bf03", + "sha256:f40ac873045db4fd98a6f40387d242bde2708a3f8167bd967ccd43ad46394ba2", + "sha256:f593a4a90118d99014517c2679e04a4ef5aee2d81aa05c26c734d271065efcb6", + "sha256:f5df76c58977bc35a49515b2fbba84a1d952ff0ec784a4070334dfbec28a2def", + "sha256:f72cdd2586f9a769570d4b5714a3837b3a59a53b096bb954f1811f6a0afad305", + "sha256:f8e845d894e39fb53834da826078f6dc1a933b32b1478cf437007367efaf6f6a", + "sha256:fe6e43c8b510719b48af7db9631b5fbac910ade4bd90e6378c85ac5ac706382c" ], "markers": "python_version >= '3.8'", - "version": "==7.4.0" + "version": "==7.4.2" }, "exceptiongroup": { "hashes": [ @@ -179,37 +175,37 @@ }, "mypy": { "hashes": [ - "sha256:028cf9f2cae89e202d7b6593cd98db6759379f17a319b5faf4f9978d7084cdc6", - "sha256:2afecd6354bbfb6e0160f4e4ad9ba6e4e003b767dd80d85516e71f2e955ab50d", - "sha256:2b5b6c721bd4aabaadead3a5e6fa85c11c6c795e0c81a7215776ef8afc66de02", - "sha256:42419861b43e6962a649068a61f4a4839205a3ef525b858377a960b9e2de6e0d", - "sha256:42c6680d256ab35637ef88891c6bd02514ccb7e1122133ac96055ff458f93fc3", - "sha256:485a8942f671120f76afffff70f259e1cd0f0cfe08f81c05d8816d958d4577d3", - "sha256:4c886c6cce2d070bd7df4ec4a05a13ee20c0aa60cb587e8d1265b6c03cf91da3", - "sha256:4e6d97288757e1ddba10dd9549ac27982e3e74a49d8d0179fc14d4365c7add66", - "sha256:4ef4be7baf08a203170f29e89d79064463b7fc7a0908b9d0d5114e8009c3a259", - "sha256:51720c776d148bad2372ca21ca29256ed483aa9a4cdefefcef49006dff2a6835", - "sha256:52825b01f5c4c1c4eb0db253ec09c7aa17e1a7304d247c48b6f3599ef40db8bd", - "sha256:538fd81bb5e430cc1381a443971c0475582ff9f434c16cd46d2c66763ce85d9d", - "sha256:5c1538c38584029352878a0466f03a8ee7547d7bd9f641f57a0f3017a7c905b8", - "sha256:6ff8b244d7085a0b425b56d327b480c3b29cafbd2eff27316a004f9a7391ae07", - "sha256:7178def594014aa6c35a8ff411cf37d682f428b3b5617ca79029d8ae72f5402b", - "sha256:720a5ca70e136b675af3af63db533c1c8c9181314d207568bbe79051f122669e", - "sha256:7f1478736fcebb90f97e40aff11a5f253af890c845ee0c850fe80aa060a267c6", - "sha256:855fe27b80375e5c5878492f0729540db47b186509c98dae341254c8f45f42ae", - "sha256:8963b83d53ee733a6e4196954502b33567ad07dfd74851f32be18eb932fb1cb9", - "sha256:9261ed810972061388918c83c3f5cd46079d875026ba97380f3e3978a72f503d", - "sha256:99b00bc72855812a60d253420d8a2eae839b0afa4938f09f4d2aa9bb4654263a", - "sha256:ab3c84fa13c04aeeeabb2a7f67a25ef5d77ac9d6486ff33ded762ef353aa5592", - "sha256:afe3fe972c645b4632c563d3f3eff1cdca2fa058f730df2b93a35e3b0c538218", - "sha256:d19c413b3c07cbecf1f991e2221746b0d2a9410b59cb3f4fb9557f0365a1a817", - "sha256:df9824ac11deaf007443e7ed2a4a26bebff98d2bc43c6da21b2b64185da011c4", - "sha256:e46f44b54ebddbeedbd3d5b289a893219065ef805d95094d16a0af6630f5d410", - "sha256:f5ac9a4eeb1ec0f1ccdc6f326bcdb464de5f80eb07fb38b5ddd7b0de6bc61e55" + "sha256:0235391f1c6f6ce487b23b9dbd1327b4ec33bb93934aa986efe8a9563d9349e6", + "sha256:190da1ee69b427d7efa8aa0d5e5ccd67a4fb04038c380237a0d96829cb157913", + "sha256:2418488264eb41f69cc64a69a745fad4a8f86649af4b1041a4c64ee61fc61129", + "sha256:3a3c007ff3ee90f69cf0a15cbcdf0995749569b86b6d2f327af01fd1b8aee9dc", + "sha256:3cc5da0127e6a478cddd906068496a97a7618a21ce9b54bde5bf7e539c7af974", + "sha256:48533cdd345c3c2e5ef48ba3b0d3880b257b423e7995dada04248725c6f77374", + "sha256:49c87c15aed320de9b438ae7b00c1ac91cd393c1b854c2ce538e2a72d55df150", + "sha256:4d3dbd346cfec7cb98e6cbb6e0f3c23618af826316188d587d1c1bc34f0ede03", + "sha256:571741dc4194b4f82d344b15e8837e8c5fcc462d66d076748142327626a1b6e9", + "sha256:587ce887f75dd9700252a3abbc9c97bbe165a4a630597845c61279cf32dfbf02", + "sha256:5d741d3fc7c4da608764073089e5f58ef6352bedc223ff58f2f038c2c4698a89", + "sha256:5e6061f44f2313b94f920e91b204ec600982961e07a17e0f6cd83371cb23f5c2", + "sha256:61758fabd58ce4b0720ae1e2fea5cfd4431591d6d590b197775329264f86311d", + "sha256:653265f9a2784db65bfca694d1edd23093ce49740b2244cde583aeb134c008f3", + "sha256:68edad3dc7d70f2f17ae4c6c1b9471a56138ca22722487eebacfd1eb5321d612", + "sha256:81a10926e5473c5fc3da8abb04119a1f5811a236dc3a38d92015cb1e6ba4cb9e", + "sha256:85ca5fcc24f0b4aeedc1d02f93707bccc04733f21d41c88334c5482219b1ccb3", + "sha256:a260627a570559181a9ea5de61ac6297aa5af202f06fd7ab093ce74e7181e43e", + "sha256:aceb1db093b04db5cd390821464504111b8ec3e351eb85afd1433490163d60cd", + "sha256:b685154e22e4e9199fc95f298661deea28aaede5ae16ccc8cbb1045e716b3e04", + "sha256:d357423fa57a489e8c47b7c85dfb96698caba13d66e086b412298a1a0ea3b0ed", + "sha256:d4d5ddc13421ba3e2e082a6c2d74c2ddb3979c39b582dacd53dd5d9431237185", + "sha256:e49499be624dead83927e70c756970a0bc8240e9f769389cdf5714b0784ca6bf", + "sha256:e54396d70be04b34f31d2edf3362c1edd023246c82f1730bbf8768c28db5361b", + "sha256:f88566144752999351725ac623471661c9d1cd8caa0134ff98cceeea181789f4", + "sha256:f8a67616990062232ee4c3952f41c779afac41405806042a8126fe96e098419f", + "sha256:fe28657de3bfec596bbeef01cb219833ad9d38dd5393fc649f4b366840baefe6" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==1.8.0" + "version": "==1.9.0" }, "mypy-extensions": { "hashes": [ @@ -221,11 +217,11 @@ }, "packaging": { "hashes": [ - "sha256:048fb0e9405036518eaaf48a55953c750c11e1a1b68e0dd1a9d62ed0c092cfc5", - "sha256:8c491190033a9af7e1d931d0b5dacc2ef47509b34dd0de67ed209b5203fc88c7" + "sha256:2ddfb553fdf02fb784c234c7ba6ccc288296ceabec964ad2eae3777778130bc5", + "sha256:eb82c5e3e56209074766e6885bb04b8c38a0c015d0a30036ebe7ece34c9989e9" ], "markers": "python_version >= '3.7'", - "version": "==23.2" + "version": "==24.0" }, "pathspec": { "hashes": [ @@ -237,28 +233,28 @@ }, "platformdirs": { "hashes": [ - "sha256:11c8f37bcca40db96d8144522d925583bdb7a31f7b0e37e3ed4318400a8e2380", - "sha256:906d548203468492d432bcb294d4bc2fff751bf84971fbb2c10918cc206ee420" + "sha256:0614df2a2f37e1a662acbd8e2b25b92ccf8632929bc6d43467e17fe89c75e068", + "sha256:ef0cc731df711022c174543cb70a9b5bd22e5a9337c8624ef2c2ceb8ddad8768" ], "markers": "python_version >= '3.8'", - "version": "==4.1.0" + "version": "==4.2.0" }, "pluggy": { "hashes": [ - "sha256:cf61ae8f126ac6f7c451172cf30e3e43d3ca77615509771b3a984a0730651e12", - "sha256:d89c696a773f8bd377d18e5ecda92b7a3793cbe66c87060a6fb58c7b6e1061f7" + "sha256:7db9f7b503d67d1c5b95f59773ebb58a8c1c288129a88665838012cfb07b8981", + "sha256:8c85c2876142a764e5b7548e7d9a0e0ddb46f5185161049a79b7e974454223be" ], "markers": "python_version >= '3.8'", - "version": "==1.3.0" + "version": "==1.4.0" }, "pytest": { "hashes": [ - "sha256:2cf0005922c6ace4a3e2ec8b4080eb0d9753fdc93107415332f50ce9e7994280", - "sha256:b090cdf5ed60bf4c45261be03239c2c1c22df034fbffe691abe93cd80cea01d8" + "sha256:2a8386cfc11fa9d2c50ee7b2a57e7d898ef90470a7a34c4b949ff59662bb78b7", + "sha256:ac978141a75948948817d360297b7aae0fcb9d6ff6bc9ec6d514b85d5a65c044" ], "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==7.4.4" + "markers": "python_version >= '3.8'", + "version": "==8.1.1" }, "pytest-cov": { "hashes": [ @@ -278,22 +274,30 @@ "markers": "python_version < '3.11'", "version": "==2.0.1" }, + "types-appdirs": { + "hashes": [ + "sha256:337c750e423c40911d389359b4edabe5bbc2cdd5cd0bd0518b71d2839646273b", + "sha256:83268da64585361bfa291f8f506a209276212a0497bd37f0512a939b3d69ff14" + ], + "index": "pypi", + "version": "==1.4.3.5" + }, "types-mock": { "hashes": [ - "sha256:13ca379d5710ccb3f18f69ade5b08881874cb83383d8fb49b1d4dac9d5c5d090", - "sha256:3d116955495935b0bcba14954b38d97e507cd43eca3e3700fc1b8e4f5c6bf2c7" + "sha256:0769cb376dfc75b45215619f17a9fd6333d771cc29ce4a38937f060b1e45530f", + "sha256:7472797986d83016f96fde7f73577d129b0cd8a8d0b783487a7be330d57ba431" ], "index": "pypi", "markers": "python_version >= '3.8'", - "version": "==5.1.0.20240106" + "version": "==5.1.0.20240311" }, "typing-extensions": { "hashes": [ - "sha256:23478f88c37f27d76ac8aee6c905017a143b0b1b886c3c9f66bc2fd94f9f5783", - "sha256:af72aea155e91adfc61c3ae9e0e342dbc0cba726d6cba4b6c72c1f34e47291cd" + "sha256:69b1a937c3a517342112fb4c6df7e72fc39a38e7891a5730ed4985b5214b5475", + "sha256:b0abd7c89e8fb96f98db18d86106ff1d90ab692004eb746cf6eda2682f91b3cb" ], "markers": "python_version >= '3.8'", - "version": "==4.9.0" + "version": "==4.10.0" } } } diff --git a/pyproject.toml b/pyproject.toml new file mode 100644 index 0000000..6b889f4 --- /dev/null +++ b/pyproject.toml @@ -0,0 +1,39 @@ +[build-system] +requires = ["setuptools>=61.0"] +build-backend = "setuptools.build_meta" + +[project] +name = "ofxstatement" +version = "0.9.2.dev0" +authors = [ + { name="Andrey Lebedev", email="andrey@lebedev.lt" }, +] +description = "Tool to convert proprietary bank statement to OFX format, suitable for importing to GnuCash" +readme = "README.rst" +requires-python = ">=3.9" +license = { file="LICENSE.txt" } +classifiers = [ + "Development Status :: 3 - Alpha", + "Programming Language :: Python :: 3", + "Natural Language :: English", + "Topic :: Office/Business :: Financial :: Accounting", + "Topic :: Utilities", + "Environment :: Console", + "Operating System :: OS Independent", + "License :: OSI Approved :: GNU General Public License v3 (GPLv3)", +] +keywords = ["ofx", "banking", "statement"] +dependencies = [ + "appdirs>=1.3.0", + "importlib_metadata>=3.8;python_version<'3.10'", + "zipp;python_version<'3.10'" +] + +[project.urls] +Homepage = "https://github.com/kedder/ofxstatement" + +[project.scripts] +ofxstatement = "ofxstatement.tool:run" + +[tool.setuptools.package-data] +ofxstatement = ["tests/samples"] \ No newline at end of file diff --git a/setup.cfg b/setup.cfg index c25c1ad..561057d 100644 --- a/setup.cfg +++ b/setup.cfg @@ -1,51 +1,10 @@ -[metadata] -name = ofxstatement -version = 0.9.2.dev0 -description = Tool to convert proprietary bank statement to OFX format, suitable for importing to GnuCash -long_description = file: README.rst -long_description_content_type = text/x-rst -url = https://github.com/kedder/ofxstatement -author = Andrey Lebedev -author_email = andrey@lebedev.lt -classifiers = - Development Status :: 3 - Alpha - Programming Language :: Python :: 3 - Natural Language :: English - Topic :: Office/Business :: Financial :: Accounting - Topic :: Utilities - Environment :: Console - Operating System :: OS Independent - License :: OSI Approved :: GNU General Public License v3 (GPLv3) -keywords = ofx banking statement -project_urls = - [options] package_dir = =src packages = find: -python_requires = >=3.8, <4 -install_requires = - appdirs>=1.3.0 - importlib_metadata>=3.8;python_version<'3.10' -data_files = +namespace_packages = + ofxstatement + ofxstatement.plugins + [options.packages.find] where = src - -[options.entry_points] -console_scripts = - ofxstatement=ofxstatement.tool:run - -[options.extras_require] -dev = - black - mypy -test = - mock - pytest - pytest-coverage - -[mypy] -ignore_missing_imports = True - -[pycodestyle] -max-line-length=88 diff --git a/src/ofxstatement/__init__.py b/src/ofxstatement/__init__.py index e69de29..5284146 100644 --- a/src/ofxstatement/__init__.py +++ b/src/ofxstatement/__init__.py @@ -0,0 +1 @@ +__import__("pkg_resources").declare_namespace(__name__) diff --git a/src/ofxstatement/ofx.py b/src/ofxstatement/ofx.py index 888c7d5..8b034c6 100644 --- a/src/ofxstatement/ofx.py +++ b/src/ofxstatement/ofx.py @@ -155,6 +155,8 @@ def buildInvestTransactionList(self) -> None: for security_id in dict.fromkeys( map(lambda x: x.security_id, self.statement.invest_lines) ): + if security_id is None: + continue tb.start("STOCKINFO", {}) tb.start("SECINFO", {}) tb.start("SECID", {}) @@ -199,12 +201,21 @@ def buildInvestTransactionList(self) -> None: tb.end("INVSTMTMSGSRSV1") def buildInvestTransaction(self, line: InvestStatementLine) -> None: - # invest transactions must always have trntype and trntype_detailed - if line.trntype is None or line.trntype_detailed is None: + # invest transactions must always have trntype + if line.trntype is None: return tb = self.tb + if line.trntype == "INVBANKTRAN": + tb.start(line.trntype, {}) + bankTran = StatementLine(line.id, line.date, line.memo, line.amount) + bankTran.trntype = line.trntype_detailed + self.buildBankTransaction(bankTran) + self.buildText("SUBACCTFUND", "OTHER") + tb.end(line.trntype) + return + tran_type_detailed_tag_name = None inner_tran_type_tag_name = None if line.trntype.startswith("BUY"): @@ -213,6 +224,10 @@ def buildInvestTransaction(self, line: InvestStatementLine) -> None: elif line.trntype.startswith("SELL"): tran_type_detailed_tag_name = "SELLTYPE" inner_tran_type_tag_name = "INVSELL" + elif line.trntype == "TRANSFER": + # Transfer transactions don't have details or an envelope + tran_type_detailed_tag_name = None + inner_tran_type_tag_name = None else: tran_type_detailed_tag_name = "INCOMETYPE" inner_tran_type_tag_name = ( @@ -220,7 +235,8 @@ def buildInvestTransaction(self, line: InvestStatementLine) -> None: ) tb.start(line.trntype, {}) - self.buildText(tran_type_detailed_tag_name, line.trntype_detailed, False) + if tran_type_detailed_tag_name: + self.buildText(tran_type_detailed_tag_name, line.trntype_detailed, False) if inner_tran_type_tag_name: tb.start(inner_tran_type_tag_name, {}) @@ -266,12 +282,7 @@ def buildInvestTransaction(self, line: InvestStatementLine) -> None: precision=self.invest_transactions_float_precision, ) - self.buildAmount( - "TOTAL", - line.amount, - False, - precision=self.invest_transactions_float_precision, - ) + self.buildAmount("TOTAL", line.amount) if inner_tran_type_tag_name: tb.end(inner_tran_type_tag_name) diff --git a/src/ofxstatement/parser.py b/src/ofxstatement/parser.py index e045cf5..1ef47d6 100644 --- a/src/ofxstatement/parser.py +++ b/src/ofxstatement/parser.py @@ -1,10 +1,10 @@ -from typing import Dict, Optional, Any, Iterable, List, TextIO, TypeVar, Generic +from typing import Dict, Optional, Any, Iterable, List, TextIO, TypeVar, Generic, Union from abc import abstractmethod import csv from decimal import Decimal, Decimal as D from datetime import datetime -from ofxstatement.statement import Statement, StatementLine +from ofxstatement.statement import Statement, StatementLine, InvestStatementLine LT = TypeVar("LT") @@ -46,7 +46,7 @@ def parse(self) -> Statement: stmt_line = self.parse_record(line) if stmt_line: stmt_line.assert_valid() - self.statement.lines.append(stmt_line) + self.statement.lines.append(stmt_line) # type: ignore return self.statement def split_records(self) -> Iterable[LT]: # pragma: no cover @@ -57,6 +57,12 @@ def parse_record(self, line: LT) -> Optional[StatementLine]: # pragma: no cover """Parse given transaction line and return StatementLine object""" raise NotImplementedError + def parse_invest_record( + self, line: LT + ) -> Optional[InvestStatementLine]: # pragma: no cover + """Parse given investement transaction line and return InvetStatementLine object""" + raise NotImplementedError + def parse_value(self, value: Optional[str], field: str) -> Any: tp = StatementLine.__annotations__.get(field) if value is None: diff --git a/src/ofxstatement/plugin.py b/src/ofxstatement/plugin.py index 9eaf532..ea81f34 100644 --- a/src/ofxstatement/plugin.py +++ b/src/ofxstatement/plugin.py @@ -2,6 +2,7 @@ Plugins are objects that configures and coordinates conversion machinery. """ + from typing import List, Tuple, Type from collections.abc import MutableMapping import sys @@ -21,7 +22,7 @@ def get_plugin(name: str, ui: UI, settings: MutableMapping) -> "Plugin": raise PluginNotRegistered(name) if len(plugins) > 1: raise PluginNameConflict(plugins) - plugin = plugins[0].load() # type: ignore[index] # index requires a int but class expects a string + plugin = plugins[name].load() return plugin(ui, settings) diff --git a/src/ofxstatement/plugins/__init__.py b/src/ofxstatement/plugins/__init__.py index e69de29..5284146 100644 --- a/src/ofxstatement/plugins/__init__.py +++ b/src/ofxstatement/plugins/__init__.py @@ -0,0 +1 @@ +__import__("pkg_resources").declare_namespace(__name__) diff --git a/src/ofxstatement/statement.py b/src/ofxstatement/statement.py index 922cd87..859b8b7 100644 --- a/src/ofxstatement/statement.py +++ b/src/ofxstatement/statement.py @@ -1,4 +1,5 @@ """Statement model""" + from typing import List, Optional from datetime import datetime from decimal import Decimal as D @@ -31,9 +32,11 @@ INVEST_TRANSACTION_TYPES = [ "BUYSTOCK", "BUYDEBT", + "INCOME", + "INVBANKTRAN", "SELLSTOCK", "SELLDEBT", - "INCOME", + "TRANSFER", ] INVEST_TRANSACTION_TYPES_DETAILED = [ @@ -43,6 +46,17 @@ "SELLSHORT", # open short sale "DIV", # only for INCOME "INTEREST", # only for INCOME + "CGLONG", # only for INCOME + "CGSHORT", # only for INCOME +] + +INVBANKTRAN_TYPES_DETAILED = [ + "INT", + "XFER", + "DEBIT", + "CREDIT", + "SRVCHG", + "OTHER", ] ACCOUNT_TYPE = [ @@ -274,19 +288,39 @@ def assert_valid(self) -> None: INVEST_TRANSACTION_TYPES, ) - assert ( - self.trntype_detailed in INVEST_TRANSACTION_TYPES_DETAILED - ), "trntype_detailed %s is not valid, must be one of %s" % ( - self.trntype_detailed, - INVEST_TRANSACTION_TYPES_DETAILED, - ) + if self.trntype == "INVBANKTRAN": + assert self.trntype_detailed in INVBANKTRAN_TYPES_DETAILED, ( + "trntype_detailed %s is not valid for INVBANKTRAN, must be one of %s" + % ( + self.trntype_detailed, + INVBANKTRAN_TYPES_DETAILED, + ) + ) + elif self.trntype == "TRANSFER": + assert ( + self.trntype_detailed is None + ), f"trntype_detailed '{self.trntype_detailed}' should be empty for TRANSFERS" + else: + assert ( + self.trntype_detailed in INVEST_TRANSACTION_TYPES_DETAILED + ), "trntype_detailed %s is not valid, must be one of %s" % ( + self.trntype_detailed, + INVEST_TRANSACTION_TYPES_DETAILED, + ) assert self.id - assert self.security_id - assert self.amount - - assert self.trntype == "INCOME" or self.units - assert self.trntype == "INCOME" or self.unit_price + assert self.date + assert self.trntype == "TRANSFER" or self.amount + assert self.trntype == "INVBANKTRAN" or self.security_id + + if self.trntype == "INVBANKTRAN": + pass + elif self.trntype == "INCOME": + assert self.security_id + else: + assert self.security_id + assert self.units + assert self.trntype == "TRANSFER" or self.unit_price class BankAccount(Printable): diff --git a/src/ofxstatement/tests/test_ofx_invest.py b/src/ofxstatement/tests/test_ofx_invest.py index 433a5af..82ba15b 100644 --- a/src/ofxstatement/tests/test_ofx_invest.py +++ b/src/ofxstatement/tests/test_ofx_invest.py @@ -88,7 +88,7 @@ 1.24000 138.28000 3.00000 - -416.08000 + -416.08 @@ -108,7 +108,7 @@ 0.28000 225.63000 -5.00000 - 1127.87000 + 1127.87 @@ -125,8 +125,33 @@ OTHER OTHER 0.50000 - 0.79000 + 0.79 + + + INT + 20210102 + 0.45 + 6 + Bank Interest + + OTHER + + + + 7 + 20210103 + Journaled Shares + + + MSFT + TICKER + + OTHER + OTHER + 225.63000 + 4.00000 + @@ -190,6 +215,22 @@ def test_ofxWriter(self) -> None: invest_line.assert_valid() statement.invest_lines.append(invest_line) + invest_line = InvestStatementLine( + "6", datetime(2021, 1, 2), "Bank Interest", "INVBANKTRAN", "INT" + ) + invest_line.amount = Decimal("0.45") + invest_line.assert_valid() + statement.invest_lines.append(invest_line) + + invest_line = InvestStatementLine( + "7", datetime(2021, 1, 3), "Journaled Shares", "TRANSFER" + ) + invest_line.security_id = "MSFT" + invest_line.units = Decimal("4") + invest_line.unit_price = Decimal("225.63") + invest_line.assert_valid() + statement.invest_lines.append(invest_line) + # Create writer: writer = ofx.OfxWriter(statement) diff --git a/src/ofxstatement/tests/test_plugin.py b/src/ofxstatement/tests/test_plugin.py index 34618d1..2d156de 100644 --- a/src/ofxstatement/tests/test_plugin.py +++ b/src/ofxstatement/tests/test_plugin.py @@ -2,6 +2,13 @@ import mock +import sys + +if sys.version_info < (3, 10): + from importlib_metadata import EntryPoints +else: + from importlib.metadata import EntryPoints + from ofxstatement import plugin @@ -13,7 +20,9 @@ def get_parser(self): ep = mock.Mock() ep.load.return_value = SamplePlugin - ep_patch = mock.patch("ofxstatement.plugin.entry_points", return_value=[ep]) + ep_patch = mock.patch( + "ofxstatement.plugin.entry_points", return_value=EntryPoints([ep]) + ) with ep_patch: p = plugin.get_plugin("sample", mock.Mock("UI"), mock.Mock("Settings")) self.assertIsInstance(p, SamplePlugin) @@ -21,7 +30,9 @@ def get_parser(self): def test_get_plugin_conflict(self) -> None: ep = mock.Mock() - ep_patch = mock.patch("ofxstatement.plugin.entry_points", return_value=[ep, ep]) + ep_patch = mock.patch( + "ofxstatement.plugin.entry_points", return_value=EntryPoints([ep, ep]) + ) with ep_patch: with self.assertRaises(plugin.PluginNameConflict): plugin.get_plugin("conflicting", mock.Mock("UI"), mock.Mock("Settings")) diff --git a/src/ofxstatement/tests/test_statement.py b/src/ofxstatement/tests/test_statement.py index 730bc94..cb63320 100644 --- a/src/ofxstatement/tests/test_statement.py +++ b/src/ofxstatement/tests/test_statement.py @@ -50,3 +50,88 @@ def test_generate_unique_transaction_id(self) -> None: self.assertTrue(tid2.endswith("-1")) self.assertEqual(len(txnids), 2) + + def test_transfer_line_validation(self) -> None: + line = statement.InvestStatementLine("id", datetime(2020, 3, 25)) + line.trntype = "TRANSFER" + line.security_id = "ABC" + line.units = Decimal(2) + line.assert_valid() + with self.assertRaises(AssertionError): + line.security_id = None + line.assert_valid() + line.security_id = "ABC" + with self.assertRaises(AssertionError): + line.units = None + line.assert_valid() + line.units = Decimal(2) + with self.assertRaises(AssertionError): + line.trntype_detailed = "DETAIL" + line.assert_valid() + + def test_invbank_line_validation(self) -> None: + line = statement.InvestStatementLine("id", datetime(2020, 3, 25)) + line.trntype = "INVBANKTRAN" + line.trntype_detailed = "INT" + line.amount = Decimal(1) + line.assert_valid() + with self.assertRaises(AssertionError): + line.amount = None + line.assert_valid() + line.amount = Decimal(1) + with self.assertRaises(AssertionError): + line.trntype_detailed = "BLAH" + line.assert_valid() + + def test_income_line_validation(self) -> None: + line = statement.InvestStatementLine("id", datetime(2020, 3, 25)) + line.trntype = "INCOME" + line.trntype_detailed = "INTEREST" + line.amount = Decimal(1) + line.security_id = "AAPL" + line.assert_valid() + with self.assertRaises(AssertionError): + line.amount = None + line.assert_valid() + line.amount = Decimal(1) + with self.assertRaises(AssertionError): + line.trntype_detailed = "BLAH" + line.assert_valid() + line.trntype_detailed = "INTEREST" + with self.assertRaises(AssertionError): + line.security_id = None + line.assert_valid() + + def test_buy_line_validation(self) -> None: + line = statement.InvestStatementLine("id", datetime(2020, 3, 25)) + line.trntype = "BUYSTOCK" + line.trntype_detailed = "BUY" + line.amount = Decimal(1) + line.security_id = "AAPL" + line.units = Decimal(3) + line.unit_price = Decimal(1.1) + line.assert_valid() + + with self.assertRaises(AssertionError): + line.amount = None + line.assert_valid() + line.amount = Decimal(1) + + with self.assertRaises(AssertionError): + line.trntype_detailed = "BLAH" + line.assert_valid() + line.trntype_detailed = "INTEREST" + + with self.assertRaises(AssertionError): + line.security_id = None + line.assert_valid() + line.security_id = "AAPL" + + with self.assertRaises(AssertionError): + line.units = None + line.assert_valid() + line.units = Decimal(3) + + with self.assertRaises(AssertionError): + line.unit_price = None + line.assert_valid() diff --git a/src/ofxstatement/tool.py b/src/ofxstatement/tool.py index 90bd0b6..4fdfc7d 100644 --- a/src/ofxstatement/tool.py +++ b/src/ofxstatement/tool.py @@ -1,5 +1,6 @@ """Command line tool for converting statements to OFX format """ + import os import argparse import shlex