diff --git a/.github/workflows/pylint.yml b/.github/workflows/pylint.yml index 383e65c..d76c6a2 100644 --- a/.github/workflows/pylint.yml +++ b/.github/workflows/pylint.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python-version: ["3.8", "3.9", "3.10"] + python-version: ["3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 - name: Set up Python ${{ matrix.python-version }} diff --git a/.github/workflows/python-package.yml b/.github/workflows/python-package.yml index 41f7112..fcb126c 100644 --- a/.github/workflows/python-package.yml +++ b/.github/workflows/python-package.yml @@ -16,7 +16,7 @@ jobs: strategy: fail-fast: false matrix: - python-version: ["3.7", "3.8", "3.9", "3.10", "3.11"] + python-version: ["3.7", "3.8", "3.9", "3.10", "3.11", "3.12"] steps: - uses: actions/checkout@v3 @@ -27,7 +27,7 @@ jobs: - name: Install dependencies run: | python -m pip install --upgrade pip - python -m pip install flake8 pytest + python -m pip install flake8 pytest coverage pytest-cov if [ -f requirements.txt ]; then pip install -r requirements.txt; fi - name: Lint with flake8 run: | @@ -37,4 +37,8 @@ jobs: flake8 . --count --exit-zero --max-complexity=10 --max-line-length=127 --statistics - name: Test with pytest run: | - pytest tests/unit + pytest tests/unit --cov + - name: Upload coverage reports to Codecov + uses: codecov/codecov-action@v3 + env: + CODECOV_TOKEN: ${{ secrets.CODECOV_TOKEN }} diff --git a/CHANGELOG.rst b/CHANGELOG.rst index 169f857..20fce8e 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -1,5 +1,35 @@ Changelog ========= + +* 0.9.10 (August 7, 2024) + * Update intuit-oauth dependency + * Fix issues with Invoice Sharable Link + * Added optional params to get + +* 0.9.9 (July 9, 2024) + * Removed simplejson + * Added use_decimal option (See PR: https://github.com/ej2/python-quickbooks/pull/356 for details) + +* 0.9.8 (May 20, 2024) + * Added ItemAccountRef to SalesItemLineDetail + * Updated from_json example in readme + +* 0.9.7 (March 12, 2024) + * Update intuit-oauth dependency + * Updated CompanyCurrency to ref to use Code instead of Id + * Added missing CurrentRef property from customer object + * Made improvements to file attachment handling + +* 0.9.6 (January 2, 2024) + * Replace RAuth with requests_oauthlib + * Removed python 2 code from client.py + * Removed unused dependencies from Pipfile + * Added new fields to Employee object + * Added VendorAddr to Bill object + * Added new fields to Estimate object + * Fix TaxInclusiveAmt and vendor setting 1099 creation + * Updated readme and contributing + * 0.9.5 (November 1, 2023) * Added the ability to void all voidable QB types * Added to_ref to CreditMemo object diff --git a/Pipfile b/Pipfile index 1cac194..133bdbf 100644 --- a/Pipfile +++ b/Pipfile @@ -4,15 +4,15 @@ url = "https://pypi.org/simple" verify_ssl = true [dev-packages] +coverage = "*" +twine = "*" +pytest = "*" +pytest-cov = "*" [packages] -urllib3 = ">=1.26.5" bleach = ">=3.3.0" -# Not needed as installed by Uncat core -# intuit-oauth = {git = "git+https://github.com/uncategorizedexpense/oauth-pythonclient.git@master#egg=intuit-oauth"} -rauth = ">=0.7.3" +urllib3 = ">=2.1.0" +# intuit-oauth = "==1.2.6" requests = ">=2.31.0" -simplejson = ">=3.19.1" -nose = "*" -coverage = "*" -twine = "*" +requests_oauthlib = ">=1.3.1" +setuptools = "*" diff --git a/Pipfile.lock b/Pipfile.lock index e27c4aa..e69de29 100644 --- a/Pipfile.lock +++ b/Pipfile.lock @@ -1,578 +0,0 @@ -{ - "_meta": { - "hash": { - "sha256": "146f64aceb9ae01c5807dad1d9bdc91464f68130fbe048444ac25420f86e66a5" - }, - "pipfile-spec": 6, - "requires": {}, - "sources": [ - { - "name": "pypi", - "url": "https://pypi.org/simple", - "verify_ssl": true - } - ] - }, - "default": { - "backports.tarfile": { - "hashes": [ - "sha256:91d59138ea401ee2a95e8b839c1e2f51f3e9ca76bdba8b6a29f8d773564686a8", - "sha256:b2f4df351db942d094db94588bbf2c6938697a5f190f44c934acc697da56008b" - ], - "markers": "python_version < '3.12'", - "version": "==1.1.0" - }, - "bleach": { - "hashes": [ - "sha256:0a31f1837963c41d46bbf1331b8778e1308ea0791db03cc4e7357b97cf42a8fe", - "sha256:3225f354cfc436b9789c66c4ee030194bee0568fbf9cbdad3bc8b5c26c5f12b6" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==6.1.0" - }, - "certifi": { - "hashes": [ - "sha256:0569859f95fc761b18b45ef421b1290a0f65f147e92a1e5eb3e635f9a5e4e66f", - "sha256:dc383c07b76109f368f6106eee2b593b04a011ea4d55f652c6ca24a754d1cdd1" - ], - "markers": "python_version >= '3.6'", - "version": "==2024.2.2" - }, - "charset-normalizer": { - "hashes": [ - "sha256:06435b539f889b1f6f4ac1758871aae42dc3a8c0e24ac9e60c2384973ad73027", - "sha256:06a81e93cd441c56a9b65d8e1d043daeb97a3d0856d177d5c90ba85acb3db087", - "sha256:0a55554a2fa0d408816b3b5cedf0045f4b8e1a6065aec45849de2d6f3f8e9786", - "sha256:0b2b64d2bb6d3fb9112bafa732def486049e63de9618b5843bcdd081d8144cd8", - "sha256:10955842570876604d404661fbccbc9c7e684caf432c09c715ec38fbae45ae09", - "sha256:122c7fa62b130ed55f8f285bfd56d5f4b4a5b503609d181f9ad85e55c89f4185", - "sha256:1ceae2f17a9c33cb48e3263960dc5fc8005351ee19db217e9b1bb15d28c02574", - "sha256:1d3193f4a680c64b4b6a9115943538edb896edc190f0b222e73761716519268e", - "sha256:1f79682fbe303db92bc2b1136016a38a42e835d932bab5b3b1bfcfbf0640e519", - "sha256:2127566c664442652f024c837091890cb1942c30937add288223dc895793f898", - "sha256:22afcb9f253dac0696b5a4be4a1c0f8762f8239e21b99680099abd9b2b1b2269", - "sha256:25baf083bf6f6b341f4121c2f3c548875ee6f5339300e08be3f2b2ba1721cdd3", - "sha256:2e81c7b9c8979ce92ed306c249d46894776a909505d8f5a4ba55b14206e3222f", - "sha256:3287761bc4ee9e33561a7e058c72ac0938c4f57fe49a09eae428fd88aafe7bb6", - "sha256:34d1c8da1e78d2e001f363791c98a272bb734000fcef47a491c1e3b0505657a8", - "sha256:37e55c8e51c236f95b033f6fb391d7d7970ba5fe7ff453dad675e88cf303377a", - "sha256:3d47fa203a7bd9c5b6cee4736ee84ca03b8ef23193c0d1ca99b5089f72645c73", - "sha256:3e4d1f6587322d2788836a99c69062fbb091331ec940e02d12d179c1d53e25fc", - "sha256:42cb296636fcc8b0644486d15c12376cb9fa75443e00fb25de0b8602e64c1714", - "sha256:45485e01ff4d3630ec0d9617310448a8702f70e9c01906b0d0118bdf9d124cf2", - "sha256:4a78b2b446bd7c934f5dcedc588903fb2f5eec172f3d29e52a9096a43722adfc", - "sha256:4ab2fe47fae9e0f9dee8c04187ce5d09f48eabe611be8259444906793ab7cbce", - "sha256:4d0d1650369165a14e14e1e47b372cfcb31d6ab44e6e33cb2d4e57265290044d", - "sha256:549a3a73da901d5bc3ce8d24e0600d1fa85524c10287f6004fbab87672bf3e1e", - "sha256:55086ee1064215781fff39a1af09518bc9255b50d6333f2e4c74ca09fac6a8f6", - "sha256:572c3763a264ba47b3cf708a44ce965d98555f618ca42c926a9c1616d8f34269", - "sha256:573f6eac48f4769d667c4442081b1794f52919e7edada77495aaed9236d13a96", - "sha256:5b4c145409bef602a690e7cfad0a15a55c13320ff7a3ad7ca59c13bb8ba4d45d", - "sha256:6463effa3186ea09411d50efc7d85360b38d5f09b870c48e4600f63af490e56a", - "sha256:65f6f63034100ead094b8744b3b97965785388f308a64cf8d7c34f2f2e5be0c4", - "sha256:663946639d296df6a2bb2aa51b60a2454ca1cb29835324c640dafb5ff2131a77", - "sha256:6897af51655e3691ff853668779c7bad41579facacf5fd7253b0133308cf000d", - "sha256:68d1f8a9e9e37c1223b656399be5d6b448dea850bed7d0f87a8311f1ff3dabb0", - "sha256:6ac7ffc7ad6d040517be39eb591cac5ff87416c2537df6ba3cba3bae290c0fed", - "sha256:6b3251890fff30ee142c44144871185dbe13b11bab478a88887a639655be1068", - "sha256:6c4caeef8fa63d06bd437cd4bdcf3ffefe6738fb1b25951440d80dc7df8c03ac", - "sha256:6ef1d82a3af9d3eecdba2321dc1b3c238245d890843e040e41e470ffa64c3e25", - "sha256:753f10e867343b4511128c6ed8c82f7bec3bd026875576dfd88483c5c73b2fd8", - "sha256:7cd13a2e3ddeed6913a65e66e94b51d80a041145a026c27e6bb76c31a853c6ab", - "sha256:7ed9e526742851e8d5cc9e6cf41427dfc6068d4f5a3bb03659444b4cabf6bc26", - "sha256:7f04c839ed0b6b98b1a7501a002144b76c18fb1c1850c8b98d458ac269e26ed2", - "sha256:802fe99cca7457642125a8a88a084cef28ff0cf9407060f7b93dca5aa25480db", - "sha256:80402cd6ee291dcb72644d6eac93785fe2c8b9cb30893c1af5b8fdd753b9d40f", - "sha256:8465322196c8b4d7ab6d1e049e4c5cb460d0394da4a27d23cc242fbf0034b6b5", - "sha256:86216b5cee4b06df986d214f664305142d9c76df9b6512be2738aa72a2048f99", - "sha256:87d1351268731db79e0f8e745d92493ee2841c974128ef629dc518b937d9194c", - "sha256:8bdb58ff7ba23002a4c5808d608e4e6c687175724f54a5dade5fa8c67b604e4d", - "sha256:8c622a5fe39a48f78944a87d4fb8a53ee07344641b0562c540d840748571b811", - "sha256:8d756e44e94489e49571086ef83b2bb8ce311e730092d2c34ca8f7d925cb20aa", - "sha256:8f4a014bc36d3c57402e2977dada34f9c12300af536839dc38c0beab8878f38a", - "sha256:9063e24fdb1e498ab71cb7419e24622516c4a04476b17a2dab57e8baa30d6e03", - "sha256:90d558489962fd4918143277a773316e56c72da56ec7aa3dc3dbbe20fdfed15b", - "sha256:923c0c831b7cfcb071580d3f46c4baf50f174be571576556269530f4bbd79d04", - "sha256:95f2a5796329323b8f0512e09dbb7a1860c46a39da62ecb2324f116fa8fdc85c", - "sha256:96b02a3dc4381e5494fad39be677abcb5e6634bf7b4fa83a6dd3112607547001", - "sha256:9f96df6923e21816da7e0ad3fd47dd8f94b2a5ce594e00677c0013018b813458", - "sha256:a10af20b82360ab00827f916a6058451b723b4e65030c5a18577c8b2de5b3389", - "sha256:a50aebfa173e157099939b17f18600f72f84eed3049e743b68ad15bd69b6bf99", - "sha256:a981a536974bbc7a512cf44ed14938cf01030a99e9b3a06dd59578882f06f985", - "sha256:a9a8e9031d613fd2009c182b69c7b2c1ef8239a0efb1df3f7c8da66d5dd3d537", - "sha256:ae5f4161f18c61806f411a13b0310bea87f987c7d2ecdbdaad0e94eb2e404238", - "sha256:aed38f6e4fb3f5d6bf81bfa990a07806be9d83cf7bacef998ab1a9bd660a581f", - "sha256:b01b88d45a6fcb69667cd6d2f7a9aeb4bf53760d7fc536bf679ec94fe9f3ff3d", - "sha256:b261ccdec7821281dade748d088bb6e9b69e6d15b30652b74cbbac25e280b796", - "sha256:b2b0a0c0517616b6869869f8c581d4eb2dd83a4d79e0ebcb7d373ef9956aeb0a", - "sha256:b4a23f61ce87adf89be746c8a8974fe1c823c891d8f86eb218bb957c924bb143", - "sha256:bd8f7df7d12c2db9fab40bdd87a7c09b1530128315d047a086fa3ae3435cb3a8", - "sha256:beb58fe5cdb101e3a055192ac291b7a21e3b7ef4f67fa1d74e331a7f2124341c", - "sha256:c002b4ffc0be611f0d9da932eb0f704fe2602a9a949d1f738e4c34c75b0863d5", - "sha256:c083af607d2515612056a31f0a8d9e0fcb5876b7bfc0abad3ecd275bc4ebc2d5", - "sha256:c180f51afb394e165eafe4ac2936a14bee3eb10debc9d9e4db8958fe36afe711", - "sha256:c235ebd9baae02f1b77bcea61bce332cb4331dc3617d254df3323aa01ab47bd4", - "sha256:cd70574b12bb8a4d2aaa0094515df2463cb429d8536cfb6c7ce983246983e5a6", - "sha256:d0eccceffcb53201b5bfebb52600a5fb483a20b61da9dbc885f8b103cbe7598c", - "sha256:d965bba47ddeec8cd560687584e88cf699fd28f192ceb452d1d7ee807c5597b7", - "sha256:db364eca23f876da6f9e16c9da0df51aa4f104a972735574842618b8c6d999d4", - "sha256:ddbb2551d7e0102e7252db79ba445cdab71b26640817ab1e3e3648dad515003b", - "sha256:deb6be0ac38ece9ba87dea880e438f25ca3eddfac8b002a2ec3d9183a454e8ae", - "sha256:e06ed3eb3218bc64786f7db41917d4e686cc4856944f53d5bdf83a6884432e12", - "sha256:e27ad930a842b4c5eb8ac0016b0a54f5aebbe679340c26101df33424142c143c", - "sha256:e537484df0d8f426ce2afb2d0f8e1c3d0b114b83f8850e5f2fbea0e797bd82ae", - "sha256:eb00ed941194665c332bf8e078baf037d6c35d7c4f3102ea2d4f16ca94a26dc8", - "sha256:eb6904c354526e758fda7167b33005998fb68c46fbc10e013ca97f21ca5c8887", - "sha256:eb8821e09e916165e160797a6c17edda0679379a4be5c716c260e836e122f54b", - "sha256:efcb3f6676480691518c177e3b465bcddf57cea040302f9f4e6e191af91174d4", - "sha256:f27273b60488abe721a075bcca6d7f3964f9f6f067c8c4c605743023d7d3944f", - "sha256:f30c3cb33b24454a82faecaf01b19c18562b1e89558fb6c56de4d9118a032fd5", - "sha256:fb69256e180cb6c8a894fee62b3afebae785babc1ee98b81cdf68bbca1987f33", - "sha256:fd1abc0d89e30cc4e02e4064dc67fcc51bd941eb395c502aac3ec19fab46b519", - "sha256:ff8fa367d09b717b2a17a052544193ad76cd49979c805768879cb63d9ca50561" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==3.3.2" - }, - "coverage": { - "hashes": [ - "sha256:00838a35b882694afda09f85e469c96367daa3f3f2b097d846a7216993d37f4c", - "sha256:0513b9508b93da4e1716744ef6ebc507aff016ba115ffe8ecff744d1322a7b63", - "sha256:09c3255458533cb76ef55da8cc49ffab9e33f083739c8bd4f58e79fecfe288f7", - "sha256:09ef9199ed6653989ebbcaacc9b62b514bb63ea2f90256e71fea3ed74bd8ff6f", - "sha256:09fa497a8ab37784fbb20ab699c246053ac294d13fc7eb40ec007a5043ec91f8", - "sha256:0f9f50e7ef2a71e2fae92774c99170eb8304e3fdf9c8c3c7ae9bab3e7229c5cf", - "sha256:137eb07173141545e07403cca94ab625cc1cc6bc4c1e97b6e3846270e7e1fea0", - "sha256:1f384c3cc76aeedce208643697fb3e8437604b512255de6d18dae3f27655a384", - "sha256:201bef2eea65e0e9c56343115ba3814e896afe6d36ffd37bab783261db430f76", - "sha256:38dd60d7bf242c4ed5b38e094baf6401faa114fc09e9e6632374388a404f98e7", - "sha256:3b799445b9f7ee8bf299cfaed6f5b226c0037b74886a4e11515e569b36fe310d", - "sha256:3ea79bb50e805cd6ac058dfa3b5c8f6c040cb87fe83de10845857f5535d1db70", - "sha256:40209e141059b9370a2657c9b15607815359ab3ef9918f0196b6fccce8d3230f", - "sha256:41c9c5f3de16b903b610d09650e5e27adbfa7f500302718c9ffd1c12cf9d6818", - "sha256:54eb8d1bf7cacfbf2a3186019bcf01d11c666bd495ed18717162f7eb1e9dd00b", - "sha256:598825b51b81c808cb6f078dcb972f96af96b078faa47af7dfcdf282835baa8d", - "sha256:5fc1de20b2d4a061b3df27ab9b7c7111e9a710f10dc2b84d33a4ab25065994ec", - "sha256:623512f8ba53c422fcfb2ce68362c97945095b864cda94a92edbaf5994201083", - "sha256:690db6517f09336559dc0b5f55342df62370a48f5469fabf502db2c6d1cffcd2", - "sha256:69eb372f7e2ece89f14751fbcbe470295d73ed41ecd37ca36ed2eb47512a6ab9", - "sha256:73bfb9c09951125d06ee473bed216e2c3742f530fc5acc1383883125de76d9cd", - "sha256:742a76a12aa45b44d236815d282b03cfb1de3b4323f3e4ec933acfae08e54ade", - "sha256:7c95949560050d04d46b919301826525597f07b33beba6187d04fa64d47ac82e", - "sha256:8130a2aa2acb8788e0b56938786c33c7c98562697bf9f4c7d6e8e5e3a0501e4a", - "sha256:8a2b2b78c78293782fd3767d53e6474582f62443d0504b1554370bde86cc8227", - "sha256:8ce1415194b4a6bd0cdcc3a1dfbf58b63f910dcb7330fe15bdff542c56949f87", - "sha256:9ca28a302acb19b6af89e90f33ee3e1906961f94b54ea37de6737b7ca9d8827c", - "sha256:a4cdc86d54b5da0df6d3d3a2f0b710949286094c3a6700c21e9015932b81447e", - "sha256:aa5b1c1bfc28384f1f53b69a023d789f72b2e0ab1b3787aae16992a7ca21056c", - "sha256:aadacf9a2f407a4688d700e4ebab33a7e2e408f2ca04dbf4aef17585389eff3e", - "sha256:ae71e7ddb7a413dd60052e90528f2f65270aad4b509563af6d03d53e979feafd", - "sha256:b14706df8b2de49869ae03a5ccbc211f4041750cd4a66f698df89d44f4bd30ec", - "sha256:b1a93009cb80730c9bca5d6d4665494b725b6e8e157c1cb7f2db5b4b122ea562", - "sha256:b2991665420a803495e0b90a79233c1433d6ed77ef282e8e152a324bbbc5e0c8", - "sha256:b2c5edc4ac10a7ef6605a966c58929ec6c1bd0917fb8c15cb3363f65aa40e677", - "sha256:b4d33f418f46362995f1e9d4f3a35a1b6322cb959c31d88ae56b0298e1c22357", - "sha256:b91cbc4b195444e7e258ba27ac33769c41b94967919f10037e6355e998af255c", - "sha256:c74880fc64d4958159fbd537a091d2a585448a8f8508bf248d72112723974cbd", - "sha256:c901df83d097649e257e803be22592aedfd5182f07b3cc87d640bbb9afd50f49", - "sha256:cac99918c7bba15302a2d81f0312c08054a3359eaa1929c7e4b26ebe41e9b286", - "sha256:cc4f1358cb0c78edef3ed237ef2c86056206bb8d9140e73b6b89fbcfcbdd40e1", - "sha256:ccd341521be3d1b3daeb41960ae94a5e87abe2f46f17224ba5d6f2b8398016cf", - "sha256:ce4b94265ca988c3f8e479e741693d143026632672e3ff924f25fab50518dd51", - "sha256:cf271892d13e43bc2b51e6908ec9a6a5094a4df1d8af0bfc360088ee6c684409", - "sha256:d5ae728ff3b5401cc320d792866987e7e7e880e6ebd24433b70a33b643bb0384", - "sha256:d71eec7d83298f1af3326ce0ff1d0ea83c7cb98f72b577097f9083b20bdaf05e", - "sha256:d898fe162d26929b5960e4e138651f7427048e72c853607f2b200909794ed978", - "sha256:d89d7b2974cae412400e88f35d86af72208e1ede1a541954af5d944a8ba46c57", - "sha256:dfa8fe35a0bb90382837b238fff375de15f0dcdb9ae68ff85f7a63649c98527e", - "sha256:e0be5efd5127542ef31f165de269f77560d6cdef525fffa446de6f7e9186cfb2", - "sha256:fdfafb32984684eb03c2d83e1e51f64f0906b11e64482df3c5db936ce3839d48", - "sha256:ff7687ca3d7028d8a5f0ebae95a6e4827c5616b31a4ee1192bdfde697db110d4" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==7.4.4" - }, - "docutils": { - "hashes": [ - "sha256:14c8d34a55b46c88f9f714adb29cefbdd69fb82f3fef825e59c5faab935390d8", - "sha256:65249d8a5345bc95e0f40f280ba63c98eb24de35c6c8f5b662e3e8948adea83f" - ], - "markers": "python_version >= '3.9'", - "version": "==0.21.1" - }, - "ecdsa": { - "hashes": [ - "sha256:2cea9b88407fdac7bbeca0833b189e4c9c53f2ef1e1eaa29f6224dbc809b707a", - "sha256:60eaad1199659900dd0af521ed462b793bbdf867432b3948e87416ae4caf6bf8" - ], - "markers": "python_version >= '2.6' and python_version not in '3.0, 3.1, 3.2, 3.3, 3.4'", - "version": "==0.19.0" - }, - "enum-compat": { - "hashes": [ - "sha256:3677daabed56a6f724451d585662253d8fb4e5569845aafa8bb0da36b1a8751e", - "sha256:88091b617c7fc3bbbceae50db5958023c48dc40b50520005aa3bf27f8f7ea157" - ], - "version": "==0.0.3" - }, - "idna": { - "hashes": [ - "sha256:028ff3aadf0609c1fd278d8ea3089299412a7a8b9bd005dd08b9f8285bcb5cfc", - "sha256:82fee1fc78add43492d3a1898bfa6d8a904cc97d8427f683ed8e798d07761aa0" - ], - "markers": "python_version >= '3.5'", - "version": "==3.7" - }, - "importlib-metadata": { - "hashes": [ - "sha256:30962b96c0c223483ed6cc7280e7f0199feb01a0e40cfae4d4450fc6fab1f570", - "sha256:b78938b926ee8d5f020fc4772d487045805a55ddbad2ecf21c6d60938dc7fcd2" - ], - "markers": "python_version >= '3.8'", - "version": "==7.1.0" - }, - "intuit-oauth": { - "git": "git+https://github.com/uncategorizedexpense/oauth-pythonclient.git@master#egg=intuit-oauth", - "ref": "68fcc8348ec3ec9bdf7192a64f1f9f51d199ed10" - }, - "jaraco.classes": { - "hashes": [ - "sha256:47a024b51d0239c0dd8c8540c6c7f484be3b8fcf0b2d85c13825780d3b3f3acd", - "sha256:f662826b6bed8cace05e7ff873ce0f9283b5c924470fe664fff1c2f00f581790" - ], - "markers": "python_version >= '3.8'", - "version": "==3.4.0" - }, - "jaraco.context": { - "hashes": [ - "sha256:3e16388f7da43d384a1a7cd3452e72e14732ac9fe459678773a3608a812bf266", - "sha256:c2f67165ce1f9be20f32f650f25d8edfc1646a8aeee48ae06fb35f90763576d2" - ], - "markers": "python_version >= '3.8'", - "version": "==5.3.0" - }, - "jaraco.functools": { - "hashes": [ - "sha256:3b24ccb921d6b593bdceb56ce14799204f473976e2a9d4b15b04d0f2c2326664", - "sha256:d33fa765374c0611b52f8b3a795f8900869aa88c84769d4d1746cd68fb28c3e8" - ], - "markers": "python_version >= '3.8'", - "version": "==4.0.1" - }, - "keyring": { - "hashes": [ - "sha256:26fc12e6a329d61d24aa47b22a7c5c3f35753df7d8f2860973cf94f4e1fb3427", - "sha256:7230ea690525133f6ad536a9b5def74a4bd52642abe594761028fc044d7c7893" - ], - "markers": "python_version >= '3.8'", - "version": "==25.1.0" - }, - "markdown-it-py": { - "hashes": [ - "sha256:355216845c60bd96232cd8d8c40e8f9765cc86f46880e43a8fd22dc1a1a8cab1", - "sha256:e3f60a94fa066dc52ec76661e37c851cb232d92f9886b15cb560aaada2df8feb" - ], - "markers": "python_version >= '3.8'", - "version": "==3.0.0" - }, - "mdurl": { - "hashes": [ - "sha256:84008a41e51615a49fc9966191ff91509e3c40b939176e643fd50a5c2196b8f8", - "sha256:bb413d29f5eea38f31dd4754dd7377d4465116fb207585f97bf925588687c1ba" - ], - "markers": "python_version >= '3.7'", - "version": "==0.1.2" - }, - "more-itertools": { - "hashes": [ - "sha256:686b06abe565edfab151cb8fd385a05651e1fdf8f0a14191e4439283421f8684", - "sha256:8fccb480c43d3e99a00087634c06dd02b0d50fbf088b380de5a41a015ec239e1" - ], - "markers": "python_version >= '3.8'", - "version": "==10.2.0" - }, - "nh3": { - "hashes": [ - "sha256:0316c25b76289cf23be6b66c77d3608a4fdf537b35426280032f432f14291b9a", - "sha256:1a814dd7bba1cb0aba5bcb9bebcc88fd801b63e21e2450ae6c52d3b3336bc911", - "sha256:1aa52a7def528297f256de0844e8dd680ee279e79583c76d6fa73a978186ddfb", - "sha256:22c26e20acbb253a5bdd33d432a326d18508a910e4dcf9a3316179860d53345a", - "sha256:40015514022af31975c0b3bca4014634fa13cb5dc4dbcbc00570acc781316dcc", - "sha256:40d0741a19c3d645e54efba71cb0d8c475b59135c1e3c580f879ad5514cbf028", - "sha256:551672fd71d06cd828e282abdb810d1be24e1abb7ae2543a8fa36a71c1006fe9", - "sha256:66f17d78826096291bd264f260213d2b3905e3c7fae6dfc5337d49429f1dc9f3", - "sha256:85cdbcca8ef10733bd31f931956f7fbb85145a4d11ab9e6742bbf44d88b7e351", - "sha256:a3f55fabe29164ba6026b5ad5c3151c314d136fd67415a17660b4aaddacf1b10", - "sha256:b4427ef0d2dfdec10b641ed0bdaf17957eb625b2ec0ea9329b3d28806c153d71", - "sha256:ba73a2f8d3a1b966e9cdba7b211779ad8a2561d2dba9674b8a19ed817923f65f", - "sha256:c21bac1a7245cbd88c0b0e4a420221b7bfa838a2814ee5bb924e9c2f10a1120b", - "sha256:c551eb2a3876e8ff2ac63dff1585236ed5dfec5ffd82216a7a174f7c5082a78a", - "sha256:c790769152308421283679a142dbdb3d1c46c79c823008ecea8e8141db1a2062", - "sha256:d7a25fd8c86657f5d9d576268e3b3767c5cd4f42867c9383618be8517f0f022a" - ], - "version": "==0.2.17" - }, - "nose": { - "hashes": [ - "sha256:9ff7c6cc443f8c51994b34a667bbcf45afd6d945be7477b52e97516fd17c53ac", - "sha256:dadcddc0aefbf99eea214e0f1232b94f2fa9bd98fa8353711dacb112bfcbbb2a", - "sha256:f1bffef9cbc82628f6e7d7b40d7e255aefaa1adb6a1b1d26c69a8b79e6208a98" - ], - "index": "pypi", - "version": "==1.3.7" - }, - "oauthlib": { - "hashes": [ - "sha256:8139f29aac13e25d502680e9e19963e83f16838d48a0d71c287fe40e7067fbca", - "sha256:9859c40929662bec5d64f34d01c99e093149682a3f38915dc0655d5a633dd918" - ], - "markers": "python_version >= '3.6'", - "version": "==3.2.2" - }, - "pkginfo": { - "hashes": [ - "sha256:5df73835398d10db79f8eecd5cd86b1f6d29317589ea70796994d49399af6297", - "sha256:889a6da2ed7ffc58ab5b900d888ddce90bce912f2d2de1dc1c26f4cb9fe65097" - ], - "markers": "python_version >= '3.6'", - "version": "==1.10.0" - }, - "pyasn1": { - "hashes": [ - "sha256:3a35ab2c4b5ef98e17dfdec8ab074046fbda76e281c5a706ccd82328cfc8f64c", - "sha256:cca4bb0f2df5504f02f6f8a775b6e416ff9b0b3b16f7ee80b5a3153d9b804473" - ], - "markers": "python_version >= '3.8'", - "version": "==0.6.0" - }, - "pygments": { - "hashes": [ - "sha256:b27c2826c47d0f3219f29554824c30c5e8945175d888647acd804ddd04af846c", - "sha256:da46cec9fd2de5be3a8a784f434e4c4ab670b4ff54d605c4c2717e9d49c4c367" - ], - "markers": "python_version >= '3.7'", - "version": "==2.17.2" - }, - "python-jose": { - "hashes": [ - "sha256:55779b5e6ad599c6336191246e95eb2293a9ddebd555f796a65f838f07e5d78a", - "sha256:9b1376b023f8b298536eedd47ae1089bcdb848f1535ab30555cd92002d78923a" - ], - "version": "==3.3.0" - }, - "rauth": { - "hashes": [ - "sha256:524cdbc1c28560eacfc9a9d40c59525eb8d00fdf07fbad86107ea24411477b0a", - "sha256:b18590fbd77bc3d871936bbdb851377d1b0c08e337b219c303f8fc2b5a42ef2d" - ], - "index": "pypi", - "version": "==0.7.3" - }, - "readme-renderer": { - "hashes": [ - "sha256:1818dd28140813509eeed8d62687f7cd4f7bad90d4db586001c5dc09d4fde311", - "sha256:19db308d86ecd60e5affa3b2a98f017af384678c63c88e5d4556a380e674f3f9" - ], - "markers": "python_version >= '3.8'", - "version": "==43.0" - }, - "requests": { - "hashes": [ - "sha256:58cd2187c01e70e6e26505bca751777aa9f2ee0b7f4300988b709f44e013003f", - "sha256:942c5a758f98d790eaed1a29cb6eefc7ffb0d1cf7af05c3d2791656dbd6ad1e1" - ], - "index": "pypi", - "markers": "python_version >= '3.7'", - "version": "==2.31.0" - }, - "requests-oauthlib": { - "hashes": [ - "sha256:7dd8a5c40426b779b0868c404bdef9768deccf22749cde15852df527e6269b36", - "sha256:b3dffaebd884d8cd778494369603a9e7b58d29111bf6b41bdc2dcd87203af4e9" - ], - "markers": "python_version >= '3.4'", - "version": "==2.0.0" - }, - "requests-toolbelt": { - "hashes": [ - "sha256:7681a0a3d047012b5bdc0ee37d7f8f07ebe76ab08caeccfc3921ce23c88d5bc6", - "sha256:cccfdd665f0a24fcf4726e690f65639d272bb0637b9b92dfd91a5568ccf6bd06" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2, 3.3'", - "version": "==1.0.0" - }, - "rfc3986": { - "hashes": [ - "sha256:50b1502b60e289cb37883f3dfd34532b8873c7de9f49bb546641ce9cbd256ebd", - "sha256:97aacf9dbd4bfd829baad6e6309fa6573aaf1be3f6fa735c8ab05e46cecb261c" - ], - "markers": "python_version >= '3.7'", - "version": "==2.0.0" - }, - "rich": { - "hashes": [ - "sha256:4edbae314f59eb482f54e9e30bf00d33350aaa94f4bfcd4e9e3110e64d0d7222", - "sha256:9be308cb1fe2f1f57d67ce99e95af38a1e2bc71ad9813b0e247cf7ffbcc3a432" - ], - "markers": "python_full_version >= '3.7.0'", - "version": "==13.7.1" - }, - "rsa": { - "hashes": [ - "sha256:90260d9058e514786967344d0ef75fa8727eed8a7d2e43ce9f4bcf1b536174f7", - "sha256:e38464a49c6c85d7f1351b0126661487a7e0a14a50f1675ec50eb34d4f20ef21" - ], - "markers": "python_version >= '3.6' and python_version < '4'", - "version": "==4.9" - }, - "simplejson": { - "hashes": [ - "sha256:0405984f3ec1d3f8777c4adc33eac7ab7a3e629f3b1c05fdded63acc7cf01137", - "sha256:0436a70d8eb42bea4fe1a1c32d371d9bb3b62c637969cb33970ad624d5a3336a", - "sha256:061e81ea2d62671fa9dea2c2bfbc1eec2617ae7651e366c7b4a2baf0a8c72cae", - "sha256:064300a4ea17d1cd9ea1706aa0590dcb3be81112aac30233823ee494f02cb78a", - "sha256:08889f2f597ae965284d7b52a5c3928653a9406d88c93e3161180f0abc2433ba", - "sha256:0a48679310e1dd5c9f03481799311a65d343748fe86850b7fb41df4e2c00c087", - "sha256:0b0a3eb6dd39cce23801a50c01a0976971498da49bc8a0590ce311492b82c44b", - "sha256:0d2d5119b1d7a1ed286b8af37357116072fc96700bce3bec5bb81b2e7057ab41", - "sha256:0d551dc931638e2102b8549836a1632e6e7cf620af3d093a7456aa642bff601d", - "sha256:1018bd0d70ce85f165185d2227c71e3b1e446186f9fa9f971b69eee223e1e3cd", - "sha256:11c39fbc4280d7420684494373b7c5904fa72a2b48ef543a56c2d412999c9e5d", - "sha256:11cc3afd8160d44582543838b7e4f9aa5e97865322844b75d51bf4e0e413bb3e", - "sha256:1537b3dd62d8aae644f3518c407aa8469e3fd0f179cdf86c5992792713ed717a", - "sha256:16ca9c90da4b1f50f089e14485db8c20cbfff2d55424062791a7392b5a9b3ff9", - "sha256:176a1b524a3bd3314ed47029a86d02d5a95cc0bee15bd3063a1e1ec62b947de6", - "sha256:18955c1da6fc39d957adfa346f75226246b6569e096ac9e40f67d102278c3bcb", - "sha256:1bb5b50dc6dd671eb46a605a3e2eb98deb4a9af787a08fcdddabe5d824bb9664", - "sha256:1c768e7584c45094dca4b334af361e43b0aaa4844c04945ac7d43379eeda9bc2", - "sha256:1dd4f692304854352c3e396e9b5f0a9c9e666868dd0bdc784e2ac4c93092d87b", - "sha256:25785d038281cd106c0d91a68b9930049b6464288cea59ba95b35ee37c2d23a5", - "sha256:287e39ba24e141b046812c880f4619d0ca9e617235d74abc27267194fc0c7835", - "sha256:2c1467d939932901a97ba4f979e8f2642415fcf02ea12f53a4e3206c9c03bc17", - "sha256:2c433a412e96afb9a3ce36fa96c8e61a757af53e9c9192c97392f72871e18e69", - "sha256:2d022b14d7758bfb98405672953fe5c202ea8a9ccf9f6713c5bd0718eba286fd", - "sha256:2f98d918f7f3aaf4b91f2b08c0c92b1774aea113334f7cde4fe40e777114dbe6", - "sha256:2fc697be37585eded0c8581c4788fcfac0e3f84ca635b73a5bf360e28c8ea1a2", - "sha256:3194cd0d2c959062b94094c0a9f8780ffd38417a5322450a0db0ca1a23e7fbd2", - "sha256:332c848f02d71a649272b3f1feccacb7e4f7e6de4a2e6dc70a32645326f3d428", - "sha256:346820ae96aa90c7d52653539a57766f10f33dd4be609206c001432b59ddf89f", - "sha256:3471e95110dcaf901db16063b2e40fb394f8a9e99b3fe9ee3acc6f6ef72183a2", - "sha256:3848427b65e31bea2c11f521b6fc7a3145d6e501a1038529da2391aff5970f2f", - "sha256:39b6d79f5cbfa3eb63a869639cfacf7c41d753c64f7801efc72692c1b2637ac7", - "sha256:3e74355cb47e0cd399ead3477e29e2f50e1540952c22fb3504dda0184fc9819f", - "sha256:3f39bb1f6e620f3e158c8b2eaf1b3e3e54408baca96a02fe891794705e788637", - "sha256:40847f617287a38623507d08cbcb75d51cf9d4f9551dd6321df40215128325a3", - "sha256:4280e460e51f86ad76dc456acdbfa9513bdf329556ffc8c49e0200878ca57816", - "sha256:445a96543948c011a3a47c8e0f9d61e9785df2544ea5be5ab3bc2be4bd8a2565", - "sha256:4969d974d9db826a2c07671273e6b27bc48e940738d768fa8f33b577f0978378", - "sha256:49aaf4546f6023c44d7e7136be84a03a4237f0b2b5fb2b17c3e3770a758fc1a0", - "sha256:49e0e3faf3070abdf71a5c80a97c1afc059b4f45a5aa62de0c2ca0444b51669b", - "sha256:49f9da0d6cd17b600a178439d7d2d57c5ef01f816b1e0e875e8e8b3b42db2693", - "sha256:4a8c3cc4f9dfc33220246760358c8265dad6e1104f25f0077bbca692d616d358", - "sha256:4d36081c0b1c12ea0ed62c202046dca11438bee48dd5240b7c8de8da62c620e9", - "sha256:4edcd0bf70087b244ba77038db23cd98a1ace2f91b4a3ecef22036314d77ac23", - "sha256:554313db34d63eac3b3f42986aa9efddd1a481169c12b7be1e7512edebff8eaf", - "sha256:5675e9d8eeef0aa06093c1ff898413ade042d73dc920a03e8cea2fb68f62445a", - "sha256:60848ab779195b72382841fc3fa4f71698a98d9589b0a081a9399904487b5832", - "sha256:66e5dc13bfb17cd6ee764fc96ccafd6e405daa846a42baab81f4c60e15650414", - "sha256:6779105d2fcb7fcf794a6a2a233787f6bbd4731227333a072d8513b252ed374f", - "sha256:6ad331349b0b9ca6da86064a3599c425c7a21cd41616e175ddba0866da32df48", - "sha256:6f0a0b41dd05eefab547576bed0cf066595f3b20b083956b1405a6f17d1be6ad", - "sha256:73a8a4653f2e809049999d63530180d7b5a344b23a793502413ad1ecea9a0290", - "sha256:778331444917108fa8441f59af45886270d33ce8a23bfc4f9b192c0b2ecef1b3", - "sha256:7cb98be113911cb0ad09e5523d0e2a926c09a465c9abb0784c9269efe4f95917", - "sha256:7d74beca677623481810c7052926365d5f07393c72cbf62d6cce29991b676402", - "sha256:7f2398361508c560d0bf1773af19e9fe644e218f2a814a02210ac2c97ad70db0", - "sha256:8434dcdd347459f9fd9c526117c01fe7ca7b016b6008dddc3c13471098f4f0dc", - "sha256:8a390e56a7963e3946ff2049ee1eb218380e87c8a0e7608f7f8790ba19390867", - "sha256:92c4a4a2b1f4846cd4364855cbac83efc48ff5a7d7c06ba014c792dd96483f6f", - "sha256:9300aee2a8b5992d0f4293d88deb59c218989833e3396c824b69ba330d04a589", - "sha256:9453419ea2ab9b21d925d0fd7e3a132a178a191881fab4169b6f96e118cc25bb", - "sha256:9652e59c022e62a5b58a6f9948b104e5bb96d3b06940c6482588176f40f4914b", - "sha256:972a7833d4a1fcf7a711c939e315721a88b988553fc770a5b6a5a64bd6ebeba3", - "sha256:9c1a4393242e321e344213a90a1e3bf35d2f624aa8b8f6174d43e3c6b0e8f6eb", - "sha256:9e038c615b3906df4c3be8db16b3e24821d26c55177638ea47b3f8f73615111c", - "sha256:9e4c166f743bb42c5fcc60760fb1c3623e8fda94f6619534217b083e08644b46", - "sha256:9eb117db8d7ed733a7317c4215c35993b815bf6aeab67523f1f11e108c040672", - "sha256:9eb442a2442ce417801c912df68e1f6ccfcd41577ae7274953ab3ad24ef7d82c", - "sha256:a3cd18e03b0ee54ea4319cdcce48357719ea487b53f92a469ba8ca8e39df285e", - "sha256:a8617625369d2d03766413bff9e64310feafc9fc4f0ad2b902136f1a5cd8c6b0", - "sha256:a970a2e6d5281d56cacf3dc82081c95c1f4da5a559e52469287457811db6a79b", - "sha256:aad7405c033d32c751d98d3a65801e2797ae77fac284a539f6c3a3e13005edc4", - "sha256:adcb3332979cbc941b8fff07181f06d2b608625edc0a4d8bc3ffc0be414ad0c4", - "sha256:af9c7e6669c4d0ad7362f79cb2ab6784d71147503e62b57e3d95c4a0f222c01c", - "sha256:b01fda3e95d07a6148702a641e5e293b6da7863f8bc9b967f62db9461330562c", - "sha256:b8d940fd28eb34a7084877747a60873956893e377f15a32ad445fe66c972c3b8", - "sha256:bccb3e88ec26ffa90f72229f983d3a5d1155e41a1171190fa723d4135523585b", - "sha256:bcedf4cae0d47839fee7de344f96b5694ca53c786f28b5f773d4f0b265a159eb", - "sha256:be893258d5b68dd3a8cba8deb35dc6411db844a9d35268a8d3793b9d9a256f80", - "sha256:c0521e0f07cb56415fdb3aae0bbd8701eb31a9dfef47bb57206075a0584ab2a2", - "sha256:c594642d6b13d225e10df5c16ee15b3398e21a35ecd6aee824f107a625690374", - "sha256:c87c22bd6a987aca976e3d3e23806d17f65426191db36d40da4ae16a6a494cbc", - "sha256:c9ac1c2678abf9270e7228133e5b77c6c3c930ad33a3c1dfbdd76ff2c33b7b50", - "sha256:d0e5ffc763678d48ecc8da836f2ae2dd1b6eb2d27a48671066f91694e575173c", - "sha256:d0f402e787e6e7ee7876c8b05e2fe6464820d9f35ba3f172e95b5f8b699f6c7f", - "sha256:d222a9ed082cd9f38b58923775152003765016342a12f08f8c123bf893461f28", - "sha256:d94245caa3c61f760c4ce4953cfa76e7739b6f2cbfc94cc46fff6c050c2390c5", - "sha256:de9a2792612ec6def556d1dc621fd6b2073aff015d64fba9f3e53349ad292734", - "sha256:e2f5a398b5e77bb01b23d92872255e1bcb3c0c719a3be40b8df146570fe7781a", - "sha256:e8dd53a8706b15bc0e34f00e6150fbefb35d2fd9235d095b4f83b3c5ed4fa11d", - "sha256:e9eb3cff1b7d71aa50c89a0536f469cb8d6dcdd585d8f14fb8500d822f3bdee4", - "sha256:ed628c1431100b0b65387419551e822987396bee3c088a15d68446d92f554e0c", - "sha256:ef7938a78447174e2616be223f496ddccdbf7854f7bf2ce716dbccd958cc7d13", - "sha256:f1c70249b15e4ce1a7d5340c97670a95f305ca79f376887759b43bb33288c973", - "sha256:f3c7363a8cb8c5238878ec96c5eb0fc5ca2cb11fc0c7d2379863d342c6ee367a", - "sha256:fbbcc6b0639aa09b9649f36f1bcb347b19403fe44109948392fbb5ea69e48c3e", - "sha256:febffa5b1eda6622d44b245b0685aff6fb555ce0ed734e2d7b1c3acd018a2cff", - "sha256:ff836cd4041e16003549449cc0a5e372f6b6f871eb89007ab0ee18fb2800fded" - ], - "index": "pypi", - "markers": "python_version >= '2.5' and python_version not in '3.0, 3.1, 3.2'", - "version": "==3.19.2" - }, - "six": { - "hashes": [ - "sha256:1e61c37477a1626458e36f7b1d82aa5c9b094fa4802892072e49de9c60c4c926", - "sha256:8abb2f1d86890a2dfb989f9a77cfcfd3e47c2a354b01111771326f8aa26e0254" - ], - "markers": "python_version >= '2.7' and python_version not in '3.0, 3.1, 3.2'", - "version": "==1.16.0" - }, - "twine": { - "hashes": [ - "sha256:89b0cc7d370a4b66421cc6102f269aa910fe0f1861c124f573cf2ddedbc10cf4", - "sha256:a262933de0b484c53408f9edae2e7821c1c45a3314ff2df9bdd343aa7ab8edc0" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==5.0.0" - }, - "urllib3": { - "hashes": [ - "sha256:450b20ec296a467077128bff42b73080516e71b56ff59a60a02bef2232c4fa9d", - "sha256:d0570876c61ab9e520d776c38acbbb5b05a776d3f9ff98a5c8fd5162a444cf19" - ], - "index": "pypi", - "markers": "python_version >= '3.8'", - "version": "==2.2.1" - }, - "webencodings": { - "hashes": [ - "sha256:a0af1213f3c2226497a97e2b3aa01a7e4bee4f403f95be16fc9acd2947514a78", - "sha256:b36a1c245f2d304965eb4e0a82848379241dc04b865afcc4aab16748587e1923" - ], - "version": "==0.5.1" - }, - "zipp": { - "hashes": [ - "sha256:206f5a15f2af3dbaee80769fb7dc6f249695e940acca08dfb2a4769fe61e538b", - "sha256:2884ed22e7d8961de1c9a05142eb69a247f120291bc0206a00a7642f09b5b715" - ], - "markers": "python_version >= '3.8'", - "version": "==3.18.1" - } - }, - "develop": {} -} diff --git a/README.md b/README.md index ea09ef7..fb517d9 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ python-quickbooks ================= [![Python package](https://github.com/ej2/python-quickbooks/actions/workflows/python-package.yml/badge.svg)](https://github.com/ej2/python-quickbooks/actions/workflows/python-package.yml) -[![Coverage Status](https://coveralls.io/repos/github/ej2/python-quickbooks/badge.svg?branch=master)](https://coveralls.io/github/ej2/python-quickbooks?branch=master) +[![codecov](https://codecov.io/gh/ej2/python-quickbooks/graph/badge.svg?token=AKXS2F7wvP)](https://codecov.io/gh/ej2/python-quickbooks) [![](https://img.shields.io/badge/License-MIT-yellow.svg)](https://github.com/ej2/python-quickbooks/blob/master/LICENSE) [![PyPI](https://img.shields.io/pypi/v/python-quickbooks)](https://pypi.org/project/python-quickbooks/) @@ -54,14 +54,14 @@ Then create a QuickBooks client object passing in the AuthClient, refresh token, company_id='COMPANY_ID', ) -If you need to access a minor version (See [Minor versions](https://developer.intuit.com/docs/0100_quickbooks_online/0200_dev_guides/accounting/minor_versions) for +If you need to access a minor version (See [Minor versions](https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/minor-versions#working-with-minor-versions) for details) pass in minorversion when setting up the client: client = QuickBooks( auth_client=auth_client, refresh_token='REFRESH_TOKEN', company_id='COMPANY_ID', - minorversion=59 + minorversion=69 ) Object Operations @@ -74,7 +74,9 @@ List of objects: **Note:** The maximum number of entities that can be returned in a response is 1000. If the result size is not specified, the default -number is 100. (See [Intuit developer guide](https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/querying_data) for details) +number is 100. (See [Query operations and syntax](https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/data-queries) for details) + +**Warning:** You should never allow user input to pass into a query without sanitizing it first! This library DOES NOT sanitize user input! Filtered list of objects: @@ -104,6 +106,8 @@ List with custom Where Clause (do not include the `"WHERE"`): customers = Customer.where("Active = True AND CompanyName LIKE 'S%'", qb=client) + + List with custom Where and ordering customers = Customer.where("Active = True AND CompanyName LIKE 'S%'", order_by='DisplayName', qb=client) @@ -112,7 +116,7 @@ List with custom Where Clause and paging: customers = Customer.where("CompanyName LIKE 'S%'", start_position=1, max_results=25, qb=client) -Filtering a list with a custom query (See [Intuit developer guide](https://developer.intuit.com/docs/0100_accounting/0300_developer_guides/querying_data) for +Filtering a list with a custom query (See [Query operations and syntax](https://developer.intuit.com/app/developer/qbo/docs/learn/explore-the-quickbooks-online-api/data-queries) for supported SQL statements): customers = Customer.query("SELECT * FROM Customer WHERE Active = True", qb=client) @@ -248,15 +252,23 @@ One example is `include=allowduplicatedocnum` on the Purchase object. You can ad purchase.save(qb=self.qb_client, params={'include': 'allowduplicatedocnum'}) -Other operations +Sharable Invoice Link ---------------- -Add Sharable link for an invoice sent to external customers (minorversion must be set to 36 or greater): +To add a sharable link for an invoice, make sure the AllowOnlineCreditCardPayment is set to True and BillEmail is set to a invalid email address: + + invoice.AllowOnlineCreditCardPayment = True + invoice.BillEmail = EmailAddress() + invoice.BillEmail.Address = 'test@email.com' - invoice.invoice_link = true +When you query the invoice include the following params (minorversion must be set to 36 or greater): + invoice = Invoice.get(id, qb=self.qb_client, params={'include': 'invoiceLink'}) -Void an invoice: +Void an invoice +---------------- +Call `void` on any invoice with an Id: + invoice = Invoice() invoice.Id = 7 invoice.void(qb=client) @@ -273,10 +285,10 @@ Converting an object to JSON data: Loading JSON data into a quickbooks object: - account = Account() - account.from_json( + account = Account.from_json( { "AccountType": "Accounts Receivable", + "AcctNum": "123123", "Name": "MyJobs" } ) diff --git a/contributing.md b/contributing.md index ae676b0..081f606 100644 --- a/contributing.md +++ b/contributing.md @@ -1,10 +1,10 @@ # Contributing -I am accepting pull requests. Sometimes life gets busy and it takes me a little while to get everything merged in. To help speed up the process, please write tests to cover your changes. I will review/merge them as soon as possible. +I am accepting pull requests. Sometimes life gets busy and it takes me a little while to get everything reviewed and merged in. To help speed up the process, please write tests to cover your changes. I will review/merge them as soon as possible. # Testing -I use [nose](https://nose.readthedocs.io/en/latest/index.html) and [Coverage](https://coverage.readthedocs.io/en/latest/) to run the test suite. +I use [pytest](https://docs.pytest.org/en/7.4.x/contents.html), [Coverage](https://coverage.readthedocs.io/en/latest/), and [pytest-cov](https://pytest-cov.readthedocs.io/en/latest/) to run the test suite. *WARNING*: The Tests connect to the QBO API and create/modify/delete data. DO NOT USE A PRODUCTION ACCOUNT! @@ -12,24 +12,27 @@ I use [nose](https://nose.readthedocs.io/en/latest/index.html) and [Coverage](ht 1. Create/login into your [Intuit Developer account](https://developer.intuit.com). 2. On your Intuit Developer account, create a Sandbox company and an App. -3. Go to the Intuit Developer OAuth 2.0 Playground and fill out the form to get an **access token** and **refresh token**. You will need to copy the following values into your enviroment variables: +3. Go to the Intuit Developer OAuth 2.0 Playground and fill out the form to get a **refresh token**. You will need to copy the following values into your enviroment variables: ``` export CLIENT_ID="" export CLIENT_SECRET="" - export COMPANY_ID="" - export ACCESS_TOKEN="" + export COMPANY_ID="" export REFRESH_TOKEN="" ``` - *Note*: You will need to update the access token when it expires. + *Note*: You will need to update the refresh token when it expires. -5. Install *nose* and *coverage*. Using Pip: - `pip install nose coverage` +5. Install *pytest*, *coverage*, and *pytest-cov*. Using Pip (or whatever): + `pip install pytest coverage pytest-cov` -6. Run `nosetests . --with-coverage --cover-package=quickbooks` +6. Run all tests: ```pytest --cov``` + Run only unit tests: ```pytest tests/unit --cov``` + Run only integration tests: ```pytest tests/integration --cov``` + + ## Creating new tests -Normal Unit tests that do not connect to the QBO API should be located under `test/unit` Test that connect to QBO API should go under `tests/integration`. Inheriting from `QuickbooksTestCase` will automatically setup `self.qb_client` to use when connecting to QBO. +Normal Unit tests that do not connect to the QBO API should be located under `test/unit`. Tests that connect to QBO API should go under `tests/integration`. Inheriting from `QuickbooksTestCase` will automatically setup `self.qb_client` to use when connecting to QBO. Example: ``` diff --git a/quickbooks/client.py b/quickbooks/client.py index 156530a..df88d37 100644 --- a/quickbooks/client.py +++ b/quickbooks/client.py @@ -1,30 +1,16 @@ -import warnings - -try: # Python 3 - import http.client as httplib - from urllib.parse import parse_qsl - from functools import partial - to_bytes = lambda value, *args, **kwargs: bytes(value, "utf-8", *args, **kwargs) -except ImportError: # Python 2 - import httplib - from urlparse import parse_qsl - to_bytes = str - +import http.client as httplib import textwrap -import codecs import json - -from . import exceptions import base64 import hashlib import hmac +import decimal + +from . import exceptions +from requests_oauthlib import OAuth2Session -try: - from rauth import OAuth1Session, OAuth1Service, OAuth2Session -except ImportError: - print("Please import Rauth:\n\n") - print("http://rauth.readthedocs.org/en/latest/\n") - raise +def to_bytes(value, *args, **kwargs): + return bytes(value, "utf-8", *args, **kwargs) class Environments(object): @@ -40,6 +26,7 @@ class QuickBooks(object): minorversion = None verifier_token = None invoice_link = False + use_decimal = False sandbox_api_url_v3 = "https://sandbox-quickbooks.api.intuit.com/v3" api_url_v3 = "https://quickbooks.api.intuit.com/v3" @@ -95,6 +82,9 @@ def __new__(cls, **kwargs): if 'verifier_token' in kwargs: instance.verifier_token = kwargs.get('verifier_token') + if 'use_decimal' in kwargs: + instance.use_decimal = kwargs.get('use_decimal') + return instance def _start_session(self): @@ -102,10 +92,13 @@ def _start_session(self): self.auth_client.refresh(refresh_token=self.refresh_token) self.session = OAuth2Session( - client_id=self.auth_client.client_id, - client_secret=self.auth_client.client_secret, - access_token=self.auth_client.access_token, + self.auth_client.client_id, + token={ + 'access_token': self.auth_client.access_token, + 'refresh_token': self.auth_client.refresh_token, + } ) + return self.auth_client.refresh_token def _drop(self): @@ -162,9 +155,6 @@ def make_request(self, request_type, url, request_body=None, content_type='appli if request_id: params['requestid'] = request_id - if self.invoice_link: - params['include'] = 'invoiceLink' - if not request_body: request_body = {} @@ -175,7 +165,6 @@ def make_request(self, request_type, url, request_body=None, content_type='appli } if file_path: - attachment = open(file_path, 'rb') url = url.replace('attachable', 'upload') boundary = '-------------PythonMultipartPost' headers.update({ @@ -186,7 +175,8 @@ def make_request(self, request_type, url, request_body=None, content_type='appli 'Connection': 'close' }) - binary_data = str(base64.b64encode(attachment.read()).decode('ascii')) + with open(file_path, 'rb') as attachment: + binary_data = str(base64.b64encode(attachment.read()).decode('ascii')) content_type = json.loads(request_body)['ContentType'] @@ -219,7 +209,10 @@ def make_request(self, request_type, url, request_body=None, content_type='appli "Application authentication failed", error_code=req.status_code, detail=req.text) try: - result = req.json() + if (self.use_decimal): + result = json.loads(req.text, parse_float=decimal.Decimal) + else: + result = json.loads(req.text) except: raise exceptions.QuickbooksException("Error reading json response: {0}".format(req.text), 10000) @@ -246,9 +239,9 @@ def process_request(self, request_type, url, headers="", params="", data=""): return self.session.request( request_type, url, headers=headers, params=params, data=data) - def get_single_object(self, qbbo, pk): + def get_single_object(self, qbbo, pk, params=None): url = "{0}/company/{1}/{2}/{3}/".format(self.api_url, self.company_id, qbbo.lower(), pk) - result = self.get(url, {}) + result = self.get(url, {}, params=params) return result diff --git a/quickbooks/mixins.py b/quickbooks/mixins.py index 2083139..c3b60d9 100644 --- a/quickbooks/mixins.py +++ b/quickbooks/mixins.py @@ -1,16 +1,20 @@ +import decimal +import json from urllib.parse import quote -try: import simplejson as json -except ImportError: import json - -from .utils import build_where_clause, build_choose_clause from .client import QuickBooks from .exceptions import QuickbooksException +from .utils import build_choose_clause, build_where_clause +class DecimalEncoder(json.JSONEncoder): + def default(self, obj): + if isinstance(obj, decimal.Decimal): + return str(obj) + return super(DecimalEncoder, self).default(obj) class ToJsonMixin(object): def to_json(self): - return json.dumps(self, default=self.json_filter(), sort_keys=True, indent=4) + return json.dumps(self, cls=DecimalEncoder, default=self.json_filter(), sort_keys=True, indent=4) def json_filter(self): """ @@ -90,11 +94,11 @@ class ReadMixin(object): qbo_json_object_name = "" @classmethod - def get(cls, id, qb=None): + def get(cls, id, qb=None, params=None): if not qb: qb = QuickBooks() - json_data = qb.get_single_object(cls.qbo_object_name, pk=id) + json_data = qb.get_single_object(cls.qbo_object_name, pk=id, params=params) if cls.qbo_json_object_name != '': return cls.from_json(json_data[cls.qbo_json_object_name]) @@ -178,7 +182,7 @@ def void(self, qb=None): data = self.get_void_data() params = self.get_void_params() - results = qb.post(url, json.dumps(data), params=params) + results = qb.post(url, json.dumps(data, cls=DecimalEncoder), params=params) return results @@ -232,7 +236,7 @@ def delete(self, qb=None, request_id=None): 'Id': self.Id, 'SyncToken': self.SyncToken, } - return qb.delete_object(self.qbo_object_name, json.dumps(data), request_id=request_id) + return qb.delete_object(self.qbo_object_name, json.dumps(data, cls=DecimalEncoder), request_id=request_id) class DeleteNoIdMixin(object): diff --git a/quickbooks/objects/bill.py b/quickbooks/objects/bill.py index 834da6a..09683bd 100644 --- a/quickbooks/objects/bill.py +++ b/quickbooks/objects/bill.py @@ -1,7 +1,7 @@ from quickbooks.objects.detailline import DetailLine, ItemBasedExpenseLine, AccountBasedExpenseLine, \ TDSLine from .base import Ref, LinkedTxn, QuickbooksManagedObject, QuickbooksTransactionEntity, \ - LinkedTxnMixin + LinkedTxnMixin, Address from .tax import TxnTaxDetail from ..mixins import DeleteMixin @@ -20,6 +20,7 @@ class Bill(DeleteMixin, QuickbooksManagedObject, QuickbooksTransactionEntity, Li "AttachableRef": Ref, "DepartmentRef": Ref, "TxnTaxDetail": TxnTaxDetail, + "VendorAddr": Address, } list_dict = { @@ -53,6 +54,7 @@ def __init__(self): self.VendorRef = None self.DepartmentRef = None self.APAccountRef = None + self.VendorAddr = None self.LinkedTxn = [] self.Line = [] diff --git a/quickbooks/objects/companycurrency.py b/quickbooks/objects/companycurrency.py index b4f788e..20f6b77 100644 --- a/quickbooks/objects/companycurrency.py +++ b/quickbooks/objects/companycurrency.py @@ -35,6 +35,6 @@ def to_ref(self): ref.name = self.Name ref.type = self.qbo_object_name - ref.value = self.Id + ref.value = self.Code return ref diff --git a/quickbooks/objects/customer.py b/quickbooks/objects/customer.py index 2bf0f19..87bf95a 100644 --- a/quickbooks/objects/customer.py +++ b/quickbooks/objects/customer.py @@ -71,6 +71,7 @@ def __init__(self): self.PaymentMethodRef = None self.ParentRef = None self.ARAccountRef = None + self.CurrencyRef = None def __str__(self): return self.DisplayName diff --git a/quickbooks/objects/detailline.py b/quickbooks/objects/detailline.py index f27449f..cf4ef57 100644 --- a/quickbooks/objects/detailline.py +++ b/quickbooks/objects/detailline.py @@ -102,6 +102,7 @@ def __init__(self): class SalesItemLineDetail(QuickbooksBaseObject): class_dict = { "ItemRef": Ref, + "ItemAccountRef": Ref, "ClassRef": Ref, "TaxCodeRef": Ref, "PriceLevelRef": Ref, @@ -117,6 +118,7 @@ def __init__(self): self.MarkupInfo = None self.ItemRef = None + self.ItemAccountRef = None self.ClassRef = None self.TaxCodeRef = None self.PriceLevelRef = None diff --git a/quickbooks/objects/employee.py b/quickbooks/objects/employee.py index 4e766f1..831ef86 100644 --- a/quickbooks/objects/employee.py +++ b/quickbooks/objects/employee.py @@ -1,4 +1,4 @@ -from .base import Address, PhoneNumber, QuickbooksManagedObject, QuickbooksTransactionEntity, Ref +from .base import Address, PhoneNumber, QuickbooksManagedObject, QuickbooksTransactionEntity, Ref, EmailAddress class Employee(QuickbooksManagedObject, QuickbooksTransactionEntity): @@ -8,7 +8,9 @@ class Employee(QuickbooksManagedObject, QuickbooksTransactionEntity): class_dict = { "PrimaryAddr": Address, - "PrimaryPhone": PhoneNumber + "PrimaryPhone": PhoneNumber, + "Mobile": PhoneNumber, + "PrimaryEmailAddr": EmailAddress, } qbo_object_name = "Employee" @@ -26,6 +28,7 @@ def __init__(self): self.EmployeeNumber = "" self.Title = "" self.BillRate = 0 + self.CostRate = 0 self.BirthDate = "" self.Gender = None self.HiredDate = "" @@ -36,6 +39,8 @@ def __init__(self): self.PrimaryAddr = None self.PrimaryPhone = None + self.Mobile = None + self.EmailAddress = None def __str__(self): return self.DisplayName diff --git a/quickbooks/objects/estimate.py b/quickbooks/objects/estimate.py index 8ece9fb..362937f 100644 --- a/quickbooks/objects/estimate.py +++ b/quickbooks/objects/estimate.py @@ -77,6 +77,7 @@ def __init__(self): self.ClassRef = None self.SalesTermRef = None self.ShipMethodRef = None + self.TrackingNum = "" self.CustomField = [] self.LinkedTxn = [] diff --git a/quickbooks/objects/payment.py b/quickbooks/objects/payment.py index 75cbf57..5783d16 100644 --- a/quickbooks/objects/payment.py +++ b/quickbooks/objects/payment.py @@ -4,7 +4,6 @@ from ..client import QuickBooks from .creditcardpayment import CreditCardPayment from ..mixins import DeleteMixin, VoidMixin -import json class PaymentLine(QuickbooksBaseObject): diff --git a/requirements.txt b/requirements.txt index 8ccc6b3..9e70944 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,4 +1,2 @@ -rauth>=0.7.3 requests_oauthlib>=1.3.1 -requests>=2.31.0 -simplejson>=3.19.1 \ No newline at end of file +requests>=2.31.0 \ No newline at end of file diff --git a/setup.py b/setup.py index 2642310..df570f8 100644 --- a/setup.py +++ b/setup.py @@ -10,7 +10,7 @@ def read(*parts): return fp.read() -VERSION = (0, 9, 5) +VERSION = (0, 9, 10) version = '.'.join(map(str, VERSION)) setup( @@ -33,9 +33,8 @@ def read(*parts): 'setuptools', # Not needed as installed by Uncat core # 'intuit-oauth @ git+https://github.com/uncategorizedexpense/oauth-pythonclient.git@master#egg=intuit-oauth', - 'rauth>=0.7.3', + 'requests_oauthlib>=1.3.1', 'requests>=2.31.0', - 'simplejson>=3.19.1', 'python-dateutil', ], @@ -51,6 +50,8 @@ def read(*parts): 'Programming Language :: Python :: 3.8', 'Programming Language :: Python :: 3.9', 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', ], packages=find_packages(exclude=("tests",)), ) diff --git a/tests/integration/test_account.py b/tests/integration/test_account.py index 4efde66..b90c953 100644 --- a/tests/integration/test_account.py +++ b/tests/integration/test_account.py @@ -7,6 +7,7 @@ class AccountTest(QuickbooksTestCase): def setUp(self): super(AccountTest, self).setUp() + self.time = datetime.now() self.account_number = datetime.now().strftime('%d%H%M') self.name = "Test Account {0}".format(self.account_number) @@ -32,3 +33,12 @@ def test_update(self): query_account = Account.get(account.Id, qb=self.qb_client) self.assertEqual(query_account.Name, "Updated Name {0}".format(self.account_number)) + + def test_create_using_from_json(self): + account = Account.from_json({ + "AcctNum": datetime.now().strftime('%d%H%M%S'), + "Name": "{} {}".format(self.name, self.time.strftime("%Y-%m-%d %H:%M:%S")), + "AccountSubType": "CashOnHand" + }) + + account.save(qb=self.qb_client) diff --git a/tests/integration/test_attachable.py b/tests/integration/test_attachable.py index eec0593..2eeffab 100644 --- a/tests/integration/test_attachable.py +++ b/tests/integration/test_attachable.py @@ -41,7 +41,11 @@ def test_update_note(self): def test_create_file(self): attachable = Attachable() - test_file = tempfile.NamedTemporaryFile(suffix=".txt") + test_file = tempfile.NamedTemporaryFile(mode='w+t', suffix=".txt", delete=False) + + with test_file as f: + f.write("File contents") + f.flush() vendor = Vendor.all(max_results=1, qb=self.qb_client)[0] @@ -49,12 +53,19 @@ def test_create_file(self): attachable_ref.EntityRef = vendor.to_ref() attachable.AttachableRef.append(attachable_ref) + attachable.Note = "Sample note" attachable.FileName = os.path.basename(test_file.name) attachable._FilePath = test_file.name attachable.ContentType = 'text/plain' attachable.save(qb=self.qb_client) + test_file.close() + os.unlink(test_file.name) + query_attachable = Attachable.get(attachable.Id, qb=self.qb_client) self.assertEqual(query_attachable.AttachableRef[0].EntityRef.value, vendor.Id) + self.assertEqual(query_attachable.Note, "Sample note") + + diff --git a/tests/integration/test_base.py b/tests/integration/test_base.py index a4df082..404d6a4 100644 --- a/tests/integration/test_base.py +++ b/tests/integration/test_base.py @@ -17,7 +17,7 @@ def setUp(self): ) self.qb_client = QuickBooks( - minorversion=65, + minorversion=73, auth_client=self.auth_client, refresh_token=os.environ.get('REFRESH_TOKEN'), company_id=os.environ.get('COMPANY_ID'), diff --git a/tests/integration/test_bill.py b/tests/integration/test_bill.py index 45b351b..324ac6c 100644 --- a/tests/integration/test_bill.py +++ b/tests/integration/test_bill.py @@ -1,6 +1,6 @@ from datetime import datetime -from quickbooks.objects.base import Ref +from quickbooks.objects.base import Ref, Address from quickbooks.objects.bill import Bill from quickbooks.objects.detailline import AccountBasedExpenseLine, AccountBasedExpenseLineDetail from quickbooks.objects.vendor import Vendor @@ -30,6 +30,15 @@ def test_create(self): vendor = Vendor.all(max_results=1, qb=self.qb_client)[0] bill.VendorRef = vendor.to_ref() + # Test undocumented VendorAddr field + bill.VendorAddr = Address() + bill.VendorAddr.Line1 = "123 Main" + bill.VendorAddr.Line2 = "Apartment 1" + bill.VendorAddr.City = "City" + bill.VendorAddr.Country = "U.S.A" + bill.VendorAddr.CountrySubDivisionCode = "CA" + bill.VendorAddr.PostalCode = "94030" + bill.save(qb=self.qb_client) query_bill = Bill.get(bill.Id, qb=self.qb_client) @@ -37,3 +46,13 @@ def test_create(self): self.assertEqual(query_bill.Id, bill.Id) self.assertEqual(len(query_bill.Line), 1) self.assertEqual(query_bill.Line[0].Amount, 200.0) + + self.assertEqual(query_bill.VendorAddr.Line1, bill.VendorAddr.Line1) + self.assertEqual(query_bill.VendorAddr.Line2, bill.VendorAddr.Line2) + self.assertEqual(query_bill.VendorAddr.City, bill.VendorAddr.City) + self.assertEqual(query_bill.VendorAddr.Country, bill.VendorAddr.Country) + self.assertEqual(query_bill.VendorAddr.CountrySubDivisionCode, bill.VendorAddr.CountrySubDivisionCode) + self.assertEqual(query_bill.VendorAddr.PostalCode, bill.VendorAddr.PostalCode) + + + diff --git a/tests/integration/test_estimate.py b/tests/integration/test_estimate.py index df9ffbe..c921014 100644 --- a/tests/integration/test_estimate.py +++ b/tests/integration/test_estimate.py @@ -93,6 +93,7 @@ def test_create(self): line2.DetailType = "DiscountLineDetail" estimate.Line.append(line2) + estimate.TrackingNum = "42" estimate.save(qb=self.qb_client) @@ -134,3 +135,4 @@ def test_create(self): estimate.Line[1].DiscountLineDetail.DiscountAccountRef.value) self.assertEqual(query_estimate.Line[2].DiscountLineDetail.DiscountAccountRef.name, estimate.Line[1].DiscountLineDetail.DiscountAccountRef.name) + self.assertEqual(query_estimate.TrackingNum, estimate.TrackingNum) \ No newline at end of file diff --git a/tests/integration/test_invoice.py b/tests/integration/test_invoice.py index c02e40e..8b93f1d 100644 --- a/tests/integration/test_invoice.py +++ b/tests/integration/test_invoice.py @@ -1,15 +1,17 @@ +from datetime import datetime + from quickbooks.objects.base import CustomerMemo from quickbooks.objects.customer import Customer from quickbooks.objects.detailline import SalesItemLine, SalesItemLineDetail from quickbooks.objects.invoice import Invoice from quickbooks.objects.item import Item +from quickbooks.objects.base import EmailAddress from tests.integration.test_base import QuickbooksTestCase import uuid -class InvoiceTest(QuickbooksTestCase): - def create_invoice(self, customer, request_id=None): - invoice = Invoice() +class InvoiceTest(QuickbooksTestCase): + def create_invoice_line(self): line = SalesItemLine() line.LineNum = 1 line.Description = "description" @@ -18,7 +20,11 @@ def create_invoice(self, customer, request_id=None): item = Item.all(max_results=1, qb=self.qb_client)[0] line.SalesItemLineDetail.ItemRef = item.to_ref() - invoice.Line.append(line) + return line + + def create_invoice(self, customer, request_id=None): + invoice = Invoice() + invoice.Line.append(self.create_invoice_line()) invoice.CustomerRef = customer.to_ref() @@ -86,3 +92,34 @@ def test_void(self): self.assertEqual(query_invoice.Balance, 0.0) self.assertEqual(query_invoice.TotalAmt, 0.0) self.assertIn('Voided', query_invoice.PrivateNote) + + def test_invoice_link(self): + # Sharable link for the invoice sent to external customers. + # The link is generated only for invoices with online payment enabled and having a valid customer email address. + # Include query param `include=invoiceLink` to get the link back on query response. + + # Create test customer + customer_name = datetime.now().strftime('%d%H%M%S') + customer = Customer() + customer.DisplayName = customer_name + customer.save(qb=self.qb_client) + + # Create an invoice with sharable link flags set + invoice = Invoice() + invoice.CustomerRef = customer.to_ref() + invoice.DueDate = '2024-12-31' + invoice.AllowOnlineCreditCardPayment = True + invoice.AllowOnlineACHPayment = True + invoice.Line.append(self.create_invoice_line()) + + # BillEmail must be set for Sharable link to work! + invoice.BillEmail = EmailAddress() + invoice.BillEmail.Address = 'test@email.com' + + invoice.save(qb=self.qb_client) + + # You must add 'include': 'invoiceLink' to the params when doing a query for the invoice + query_invoice = Invoice.get(invoice.Id, qb=self.qb_client, params={'include': 'invoiceLink'}) + + self.assertIsNotNone(query_invoice.InvoiceLink) + self.assertIn('https', query_invoice.InvoiceLink) diff --git a/tests/integration/test_recurringtransaction.py b/tests/integration/test_recurringtransaction.py index b4c93ea..e3588c8 100644 --- a/tests/integration/test_recurringtransaction.py +++ b/tests/integration/test_recurringtransaction.py @@ -7,6 +7,7 @@ from quickbooks.objects.vendor import Vendor from tests.integration.test_base import QuickbooksTestCase + class RecurringTransactionTest(QuickbooksTestCase): def setUp(self): super(RecurringTransactionTest, self).setUp() diff --git a/tests/unit/objects/test_companycurrency.py b/tests/unit/objects/test_companycurrency.py index adb217d..34a1a10 100644 --- a/tests/unit/objects/test_companycurrency.py +++ b/tests/unit/objects/test_companycurrency.py @@ -1,10 +1,8 @@ -from datetime import datetime - +import unittest from quickbooks.objects.companycurrency import CompanyCurrency -from tests.integration.test_base import QuickbooksUnitTestCase -class CompanyCurrencyTest(QuickbooksUnitTestCase): +class CompanyCurrencyTest(unittest.TestCase): def test_unicode(self): company_currency = CompanyCurrency() company_currency.Name = "test" @@ -15,10 +13,12 @@ def test_unicode(self): def test_to_ref(self): company_currency = CompanyCurrency() company_currency.Name = "test" + company_currency.Code = "USD" company_currency.Id = 23 ref = company_currency.to_ref() self.assertEqual(ref.name, "test") self.assertEqual(ref.type, "CompanyCurrency") - self.assertEqual(ref.value, 23) \ No newline at end of file + self.assertEqual(ref.value, "USD") + diff --git a/tests/unit/objects/test_creditmemo.py b/tests/unit/objects/test_creditmemo.py index 87de54e..0935fec 100644 --- a/tests/unit/objects/test_creditmemo.py +++ b/tests/unit/objects/test_creditmemo.py @@ -28,6 +28,14 @@ def test_valid_object_name(self): self.assertTrue(result) + def test_to_ref(self): + obj = CreditMemo() + obj.Id = 123 + + ref = obj.to_ref() + self.assertEqual(ref.value, obj.Id) + self.assertEqual(ref.type, "CreditMemo") + class DiscountLineDetailTests(unittest.TestCase): def test_init(self): diff --git a/tests/unit/objects/test_exchangerate.py b/tests/unit/objects/test_exchangerate.py index 6e00bd2..15488db 100644 --- a/tests/unit/objects/test_exchangerate.py +++ b/tests/unit/objects/test_exchangerate.py @@ -1,7 +1,7 @@ import unittest from quickbooks import QuickBooks -from quickbooks.objects.exchangerate import ExchangeRate +from quickbooks.objects.exchangerate import ExchangeRate, ExchangeRateMetaData class ExchangeRateTests(unittest.TestCase): @@ -9,7 +9,11 @@ def test_unicode(self): exchange_rate = ExchangeRate() exchange_rate.SourceCurrencyCode = "EUR" + exchange_rate.MetaData = ExchangeRateMetaData() + exchange_rate.MetaData.LastUpdatedTime = "1" + self.assertEqual(str(exchange_rate), "EUR") + self.assertEqual(exchange_rate.MetaData.LastUpdatedTime, "1") def test_valid_object_name(self): obj = ExchangeRate() diff --git a/tests/unit/objects/test_recurringtransaction.py b/tests/unit/objects/test_recurringtransaction.py index a9b766b..4320818 100644 --- a/tests/unit/objects/test_recurringtransaction.py +++ b/tests/unit/objects/test_recurringtransaction.py @@ -1,7 +1,8 @@ import unittest from quickbooks import QuickBooks -from quickbooks.objects.recurringtransaction import RecurringTransaction +from quickbooks.objects.recurringtransaction import RecurringTransaction, ScheduleInfo, RecurringInfo + class RecurringTransactionTests(unittest.TestCase): def test_valid_object_name(self): @@ -9,4 +10,20 @@ def test_valid_object_name(self): client = QuickBooks() result = client.isvalid_object_name(obj.qbo_object_name) - self.assertTrue(result) \ No newline at end of file + self.assertTrue(result) + + +class ScheduleInfoTest(unittest.TestCase): + def test_create(self): + obj = ScheduleInfo() + obj.DayOfMonth = "1" + + self.assertEqual(obj.DayOfMonth, "1") + + +class RecurringInfoTest(unittest.TestCase): + def test_create(self): + obj = RecurringInfo() + + self.assertEqual(obj.RecurType, "Automated") + diff --git a/tests/unit/test_client.py b/tests/unit/test_client.py index 6b4fe93..a145e4d 100644 --- a/tests/unit/test_client.py +++ b/tests/unit/test_client.py @@ -1,14 +1,14 @@ +import json from tests.integration.test_base import QuickbooksUnitTestCase try: - from mock import patch + from mock import patch, mock_open except ImportError: - from unittest.mock import patch + from unittest.mock import patch, mock_open from quickbooks.exceptions import QuickbooksException, SevereException, AuthorizationException -from quickbooks import client +from quickbooks import client, mixins from quickbooks.objects.salesreceipt import SalesReceipt -from intuitlib.client import AuthClient TEST_SIGNATURE = 'nfPLN16u3vMvv08ghDs+dOkLuirEVDy5wAeG/lmM2OA=' @@ -21,16 +21,8 @@ class ClientTest(QuickbooksUnitTestCase): def setUp(self): super(ClientTest, self).setUp() - self.auth_client = AuthClient( - client_id='CLIENTID', - client_secret='CLIENT_SECRET', - environment='sandbox', - redirect_uri='http://localhost:8000/callback', - ) - self.auth_client.access_token = 'ACCESS_TOKEN' - def tearDown(self): self.qb_client = client.QuickBooks() self.qb_client._drop() @@ -146,11 +138,20 @@ def test_get_single_object(self, make_req): qb_client.get_single_object("test", 1) url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/" - make_req.assert_called_with("GET", url, {}) + make_req.assert_called_with("GET", url, {}, params=None) + + @patch('quickbooks.client.QuickBooks.make_request') + def test_get_single_object_with_params(self, make_req): + qb_client = client.QuickBooks(auth_client=self.auth_client) + qb_client.company_id = "1234" + + qb_client.get_single_object("test", 1, params={'param':'value'}) + url = "https://sandbox-quickbooks.api.intuit.com/v3/company/1234/test/1/" + make_req.assert_called_with("GET", url, {}, params={'param':'value'}) @patch('quickbooks.client.QuickBooks.process_request') def test_make_request(self, process_request): - process_request.return_value = MockResponse() + process_request.return_value = MockResponseJson() qb_client = client.QuickBooks() qb_client.company_id = "1234" @@ -161,7 +162,6 @@ def test_make_request(self, process_request): "GET", url, data={}, headers={'Content-Type': 'application/json', 'Accept': 'application/json', 'User-Agent': 'python-quickbooks V3 library'}, params={}) - def test_handle_exceptions(self): qb_client = client.QuickBooks() error_data = { @@ -227,6 +227,22 @@ def test_download_pdf_not_authorized(self, process_request): self.assertRaises(AuthorizationException, receipt.download_pdf, self.qb_client) + @patch('quickbooks.client.QuickBooks.process_request') + def test_make_request_file_closed(self, process_request): + file_path = '/path/to/file.txt' + process_request.return_value = MockResponseJson() + with patch('builtins.open', mock_open(read_data=b'file content')) as mock_file: + qb_client = client.QuickBooks(auth_client=self.auth_client) + qb_client.make_request('POST', + 'https://sandbox-quickbooks.api.intuit.com/v3/company/COMPANY_ID/attachable', + request_body='{"ContentType": "text/plain"}', + file_path=file_path) + + mock_file.assert_called_once_with(file_path, 'rb') + mock_file.return_value.__enter__.return_value.read.assert_called_once() + mock_file.return_value.__exit__.assert_called_once() + process_request.assert_called_once() + class MockResponse(object): @property @@ -247,6 +263,18 @@ def json(self): def content(self): return '' +class MockResponseJson: + def __init__(self, json_data=None, status_code=200): + self.json_data = json_data or {} + self.status_code = status_code + + @property + def text(self): + return json.dumps(self.json_data, cls=mixins.DecimalEncoder) + + def json(self): + return self.json_data + class MockUnauthorizedResponse(object): @property diff --git a/tests/unit/test_mixins.py b/tests/unit/test_mixins.py index 017df5d..0c413bf 100644 --- a/tests/unit/test_mixins.py +++ b/tests/unit/test_mixins.py @@ -1,6 +1,3 @@ - -import os - import unittest from urllib.parse import quote @@ -13,8 +10,6 @@ except ImportError: from unittest.mock import patch -from quickbooks import client - from quickbooks.objects.base import PhoneNumber, QuickbooksBaseObject from quickbooks.objects.department import Department from quickbooks.objects.customer import Customer @@ -215,7 +210,7 @@ class ReadMixinTest(QuickbooksUnitTestCase): @patch('quickbooks.mixins.QuickBooks.get_single_object') def test_get(self, get_single_object): Department.get(1) - get_single_object.assert_called_once_with("Department", pk=1) + get_single_object.assert_called_once_with("Department", pk=1, params=None) def test_get_with_qb(self): with patch.object(self.qb_client, 'get_single_object') as get_single_object: