From 036488d65939cc3941274d665ee8609836475223 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 19 Mar 2025 11:34:07 -0500 Subject: [PATCH 1/8] update gitignore --- .gitignore | 173 ++++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 170 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index 8d56382..7021c2e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,4 +1,171 @@ +# Byte-compiled / optimized / DLL files +__pycache__/ +*.py[cod] +*$py.class + +# C extensions +*.so + +# Distribution / packaging +.Python +build/ +develop-eggs/ +dist/ +downloads/ +eggs/ +.eggs/ +lib/ +lib64/ +parts/ +sdist/ +var/ +wheels/ +share/python-wheels/ +*.egg-info/ +.installed.cfg +*.egg +MANIFEST + +# PyInstaller +# Usually these files are written by a python script from a template +# before PyInstaller builds the exe, so as to inject date/other infos into it. +*.manifest +*.spec + +# Installer logs +pip-log.txt +pip-delete-this-directory.txt + +# Unit test / coverage reports +htmlcov/ +.tox/ +.nox/ .coverage -tests/__pycache__/ -veryfi/__pycache__/ -.idea/* +.coverage.* +.cache +nosetests.xml +coverage.xml +*.cover +*.py,cover +.hypothesis/ +.pytest_cache/ +cover/ + +# Translations +*.mo +*.pot + +# Django stuff: +*.log +local_settings.py +db.sqlite3 +db.sqlite3-journal + +# Flask stuff: +instance/ +.webassets-cache + +# Scrapy stuff: +.scrapy + +# Sphinx documentation +docs/_build/ + +# PyBuilder +.pybuilder/ +target/ + +# Jupyter Notebook +.ipynb_checkpoints + +# IPython +profile_default/ +ipython_config.py + +# pyenv +# For a library or package, you might want to ignore these files since the code is +# intended to run in multiple environments; otherwise, check them in: +# .python-version + +# pipenv +# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. +# However, in case of collaboration, if having platform-specific dependencies or dependencies +# having no cross-platform support, pipenv may install dependencies that don't work, or not +# install all needed dependencies. +#Pipfile.lock + +# UV +# Similar to Pipfile.lock, it is generally recommended to include uv.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +#uv.lock + +# poetry +# Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. +# This is especially recommended for binary packages to ensure reproducibility, and is more +# commonly ignored for libraries. +# https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control +#poetry.lock + +# pdm +# Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. +#pdm.lock +# pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it +# in version control. +# https://pdm.fming.dev/latest/usage/project/#working-with-version-control +.pdm.toml +.pdm-python +.pdm-build/ + +# PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm +__pypackages__/ + +# Celery stuff +celerybeat-schedule +celerybeat.pid + +# SageMath parsed files +*.sage.py + +# Environments +.env +.venv +env/ +venv/ +ENV/ +env.bak/ +venv.bak/ + +# Spyder project settings +.spyderproject +.spyproject + +# Rope project settings +.ropeproject + +# mkdocs documentation +/site + +# mypy +.mypy_cache/ +.dmypy.json +dmypy.json + +# Pyre type checker +.pyre/ + +# pytype static type analyzer +.pytype/ + +# Cython debug symbols +cython_debug/ + +# PyCharm +# JetBrains specific template is maintained in a separate JetBrains.gitignore that can +# be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore +# and can be added to the global gitignore or merged into this file. For a more nuclear +# option (not recommended) you can uncomment the following to ignore the entire idea folder. +.idea/ + +# VSCode +.vscode/ \ No newline at end of file From 4de25b2be6bd176e6b121e322841c532a9163489 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 19 Mar 2025 11:34:46 -0500 Subject: [PATCH 2/8] refactor and add tests --- tests/{ => _documents}/test_line_items.py | 2 +- tests/_documents/test_tags.py | 45 +++ tests/test_a_docs.py | 126 +++++++++ tests/test_bank_statements.py | 288 ++++++++++++++++++++ tests/test_bussines_cards.py | 70 +++++ tests/test_checks.py | 85 ++++++ tests/{test_client.py => test_documents.py} | 122 +-------- tests/test_w2s.py | 106 +++++++ tests/test_w8s.py | 209 ++++++++++++++ tests/test_w9s.py | 72 +++++ 10 files changed, 1004 insertions(+), 121 deletions(-) rename tests/{ => _documents}/test_line_items.py (98%) create mode 100644 tests/_documents/test_tags.py create mode 100644 tests/test_a_docs.py create mode 100644 tests/test_bank_statements.py create mode 100644 tests/test_bussines_cards.py create mode 100644 tests/test_checks.py rename tests/{test_client.py => test_documents.py} (69%) create mode 100644 tests/test_w2s.py create mode 100644 tests/test_w8s.py create mode 100644 tests/test_w9s.py diff --git a/tests/test_line_items.py b/tests/_documents/test_line_items.py similarity index 98% rename from tests/test_line_items.py rename to tests/_documents/test_line_items.py index 07ac3e7..88fcac4 100644 --- a/tests/test_line_items.py +++ b/tests/_documents/test_line_items.py @@ -1,6 +1,6 @@ import responses -from veryfi import * +from veryfi import Client @responses.activate diff --git a/tests/_documents/test_tags.py b/tests/_documents/test_tags.py new file mode 100644 index 0000000..d9bd4ef --- /dev/null +++ b/tests/_documents/test_tags.py @@ -0,0 +1,45 @@ +import responses + +from veryfi import Client + + +@responses.activate +def test_tags(): + mock_doc_id = 169985445 + mock_resp = {"id": 6673474, "name": "tag_123"} + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.put( + f"{client.versioned_url}/partner/documents/{mock_doc_id}/tags/", + json=mock_resp, + status=200, + ) + d = client.add_tag(mock_doc_id, "tag_123") + assert d == mock_resp + + +@responses.activate +def test_replace_multiple_tags(): + mock_doc_id = 169985445 + mock_resp = {"id": 6673474, "tags": ["tag_1", "tag_2", "tag_3"]} + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.put( + f"{client.versioned_url}/partner/documents/{mock_doc_id}/", + json=mock_resp, + status=200, + ) + d = client.replace_tags(mock_doc_id, ["tag_1", "tag_2", "tag_3"]) + assert d == mock_resp + + +@responses.activate +def test_add_multiple_tags(): + mock_doc_id = 169985445 + mock_resp = {"id": 6673474, "tags": ["tag_1", "tag_2", "tag_3"]} + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.post( + f"{client.versioned_url}/partner/documents/{mock_doc_id}/tags/", + json=mock_resp, + status=200, + ) + d = client.add_tags(mock_doc_id, ["tag_1", "tag_2", "tag_3"]) + assert d == mock_resp diff --git a/tests/test_a_docs.py b/tests/test_a_docs.py new file mode 100644 index 0000000..e9216fc --- /dev/null +++ b/tests/test_a_docs.py @@ -0,0 +1,126 @@ +import pytest +import responses + +from veryfi import Client + + +@responses.activate +def test_process_a_doc(): + mock = { + "pdf_url": "https://scdn.veryfi.com/other-documents/919ba4778c039560/cf1363b8-a38f-47e8-b9ee-8105342121cd/7179c430-eb38-4251-b015-9ceb20129371.pdf?Expires=1727203608&Signature=ZSfZmJRLtJ6DeIRioIQSExufnR4fDvADq1Fs-x~WnbU1JueQ1PLtY~7b~Krk7eda6EAQkMBa2wamDDcE2lCvrutHCS3jUbhlFFhSuQd1XljbYjBlWOdxYyXpYMmluDlaWlkgm41vA92UD3LSsBPBLrBasotjqNYLGnTg87guXTtUG1rSWlK2FhHxzborReNdrpXUcDMs4-kkQ46tTDgFH~mCPkh5F9DSpm-UsyJ6SmJgm1SWfw09KbQizyp4lIwte1yumKXtORtTCKv5WFWRUFUWD6Kv1eIkh5XJ5jfMzSfaTEikZlYF4t08Lbp5Apk5-alOW-1yYIwqb5RqZhQ26w__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "id": 4559535, + "external_id": None, + "created_date": "2024-09-24 18:31:48", + "updated_date": "2024-09-24 18:31:48", + "img_thumbnail_url": "https://scdn.veryfi.com/other-documents/919ba4778c039560/cf1363b8-a38f-47e8-b9ee-8105342121cd/thumbnail.png?Expires=1727203608&Signature=SIRru1E-r1VT5KmufOC9A3UXlWzpgaZWUn0GhSj~veGagGAISV7sztEA7bER~kZlVnowRBSu19UaR8VeGfQ39uzUxEVlzdxPgjITt7IEgfGa~B-0EUI8izLDfRoOMkdRrOknLJKpCq87hz8fMn6wfKSgWxGgyCFKuvO2zcdla~fmtcTOrR4OMAPA3TX4Y4ZRnwCfUDQwNMw72Zihh9bxulzgjM6Cqffc7wta6wC84rYRlztPgGQj51ARcewG5s-IouvrJKoTAONLJZaq8CEc-iMh~TRzKf4MiI5HoheBFmjKb2NdoJFDpHR~~aLW8RxWkEV87JtglILAumkjrY7jjw__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "blueprint_name": "us_driver_license", + "template_name": "us_driver_license", + "address": "892 MOMONA ST HONOLULU, HI 96820", + "birth_date": "1981-06-03", + "expiration_date": "2008-06-03", + "eyes_color": "BRO", + "first_name": None, + "height": "5-10", + "issue_date": "1998-06-18", + "last_name": "McLovin", + "license_class": "3", + "license_number": "01-47-87441", + "sex": "M", + "state": "HAWAII", + "weight": "150", + } + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/any-documents/", + json=mock, + status=200, + ) + d = client.process_any_document( + blueprint_name="us_driver_license", + file_path="tests/assets/receipt_public.jpg", + delete_after_processing=True, + boost_mode=True, + ) + assert d == mock + + +@responses.activate +def test_process_document_url(): + mock = { + "pdf_url": "https://scdn.veryfi.com/other-documents/919ba4778c039560/cf1363b8-a38f-47e8-b9ee-8105342121cd/7179c430-eb38-4251-b015-9ceb20129371.pdf?Expires=1727203608&Signature=ZSfZmJRLtJ6DeIRioIQSExufnR4fDvADq1Fs-x~WnbU1JueQ1PLtY~7b~Krk7eda6EAQkMBa2wamDDcE2lCvrutHCS3jUbhlFFhSuQd1XljbYjBlWOdxYyXpYMmluDlaWlkgm41vA92UD3LSsBPBLrBasotjqNYLGnTg87guXTtUG1rSWlK2FhHxzborReNdrpXUcDMs4-kkQ46tTDgFH~mCPkh5F9DSpm-UsyJ6SmJgm1SWfw09KbQizyp4lIwte1yumKXtORtTCKv5WFWRUFUWD6Kv1eIkh5XJ5jfMzSfaTEikZlYF4t08Lbp5Apk5-alOW-1yYIwqb5RqZhQ26w__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "id": 4559535, + "external_id": None, + "created_date": "2024-09-24 18:31:48", + "updated_date": "2024-09-24 18:31:48", + "img_thumbnail_url": "https://scdn.veryfi.com/other-documents/919ba4778c039560/cf1363b8-a38f-47e8-b9ee-8105342121cd/thumbnail.png?Expires=1727203608&Signature=SIRru1E-r1VT5KmufOC9A3UXlWzpgaZWUn0GhSj~veGagGAISV7sztEA7bER~kZlVnowRBSu19UaR8VeGfQ39uzUxEVlzdxPgjITt7IEgfGa~B-0EUI8izLDfRoOMkdRrOknLJKpCq87hz8fMn6wfKSgWxGgyCFKuvO2zcdla~fmtcTOrR4OMAPA3TX4Y4ZRnwCfUDQwNMw72Zihh9bxulzgjM6Cqffc7wta6wC84rYRlztPgGQj51ARcewG5s-IouvrJKoTAONLJZaq8CEc-iMh~TRzKf4MiI5HoheBFmjKb2NdoJFDpHR~~aLW8RxWkEV87JtglILAumkjrY7jjw__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "blueprint_name": "us_driver_license", + "template_name": "us_driver_license", + "address": "892 MOMONA ST HONOLULU, HI 96820", + "birth_date": "1981-06-03", + "expiration_date": "2008-06-03", + "eyes_color": "BRO", + "first_name": None, + "height": "5-10", + "issue_date": "1998-06-18", + "last_name": "McLovin", + "license_class": "3", + "license_number": "01-47-87441", + "sex": "M", + "state": "HAWAII", + "weight": "150", + } + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/any-documents/", + json=mock, + status=200, + ) + d = client.process_any_document_url( + blueprint_name="us_driver_license", + file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg", + delete_after_processing=True, + max_pages_to_process=1, + boost_mode=True, + ) + assert d == mock + + +@responses.activate +def test_get_documents(): + mock = [ + { + "pdf_url": "https://scdn.veryfi.com/other-documents/919ba4778c039560/cf1363b8-a38f-47e8-b9ee-8105342121cd/7179c430-eb38-4251-b015-9ceb20129371.pdf?Expires=1727203608&Signature=ZSfZmJRLtJ6DeIRioIQSExufnR4fDvADq1Fs-x~WnbU1JueQ1PLtY~7b~Krk7eda6EAQkMBa2wamDDcE2lCvrutHCS3jUbhlFFhSuQd1XljbYjBlWOdxYyXpYMmluDlaWlkgm41vA92UD3LSsBPBLrBasotjqNYLGnTg87guXTtUG1rSWlK2FhHxzborReNdrpXUcDMs4-kkQ46tTDgFH~mCPkh5F9DSpm-UsyJ6SmJgm1SWfw09KbQizyp4lIwte1yumKXtORtTCKv5WFWRUFUWD6Kv1eIkh5XJ5jfMzSfaTEikZlYF4t08Lbp5Apk5-alOW-1yYIwqb5RqZhQ26w__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "id": 4559535, + "external_id": None, + "created_date": "2024-09-24 18:31:48", + "updated_date": "2024-09-24 18:31:48", + "img_thumbnail_url": "https://scdn.veryfi.com/other-documents/919ba4778c039560/cf1363b8-a38f-47e8-b9ee-8105342121cd/thumbnail.png?Expires=1727203608&Signature=SIRru1E-r1VT5KmufOC9A3UXlWzpgaZWUn0GhSj~veGagGAISV7sztEA7bER~kZlVnowRBSu19UaR8VeGfQ39uzUxEVlzdxPgjITt7IEgfGa~B-0EUI8izLDfRoOMkdRrOknLJKpCq87hz8fMn6wfKSgWxGgyCFKuvO2zcdla~fmtcTOrR4OMAPA3TX4Y4ZRnwCfUDQwNMw72Zihh9bxulzgjM6Cqffc7wta6wC84rYRlztPgGQj51ARcewG5s-IouvrJKoTAONLJZaq8CEc-iMh~TRzKf4MiI5HoheBFmjKb2NdoJFDpHR~~aLW8RxWkEV87JtglILAumkjrY7jjw__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "blueprint_name": "us_driver_license", + "template_name": "us_driver_license", + "address": "892 MOMONA ST HONOLULU, HI 96820", + "birth_date": "1981-06-03", + "expiration_date": "2008-06-03", + "eyes_color": "BRO", + "first_name": None, + "height": "5-10", + "issue_date": "1998-06-18", + "last_name": "McLovin", + "license_class": "3", + "license_number": "01-47-87441", + "sex": "M", + "state": "HAWAII", + "weight": "150", + } + ] + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.GET, + f"{client.versioned_url}/partner/any-documents/", + json=mock, + status=200, + ) + d = client.get_any_documents() + assert d == mock diff --git a/tests/test_bank_statements.py b/tests/test_bank_statements.py new file mode 100644 index 0000000..6f2d830 --- /dev/null +++ b/tests/test_bank_statements.py @@ -0,0 +1,288 @@ +import responses + +from veryfi import Client + + +MOCK = { + "transactions": [ + { + "order": 0, + "account_number": "1111111", + "balance": 1803.9, + "card_number": None, + "credit_amount": None, + "date": "2013-10-22", + "debit_amount": 190.4, + "description": "AUTOMATED PAY IN 650274051211-CHB\nCALL REF. NO. 3442, FROM", + "transaction_id": None, + "text": "22 Oct 2013 AUTOMATED PAY IN 650274051211-CHB\t\t\t\t190.40\t1803.9\nCALL REF. NO. 3442, FROM", + }, + { + "order": 1, + "account_number": "1111111", + "balance": 1613.5, + "card_number": None, + "credit_amount": None, + "date": "2013-10-22", + "debit_amount": 140, + "description": "DIGITAL BANKING\nA/C 22222222", + "transaction_id": None, + "text": "22 Oct 2013 DIGITAL BANKING\t\t\t\t\t\t140.00\t1613.5\nA/C 22222222", + }, + { + "order": 2, + "account_number": "1111111", + "balance": 1473.5, + "card_number": None, + "credit_amount": None, + "date": "2013-10-24", + "debit_amount": 132.3, + "description": "Amazon", + "transaction_id": None, + "text": "24 Oct 2013 Faster Payment\tAmazon\t\t\t\t\t132.30\t1473.5", + }, + { + "order": 3, + "account_number": "1111111", + "balance": 1341.2, + "card_number": None, + "credit_amount": None, + "date": "2013-10-24", + "debit_amount": 515.22, + "description": "Tebay Trading Co.", + "transaction_id": None, + "text": "24 Oct 2013 BACS\tTebay Trading Co.\t\t\t\t515.22\t1341.2", + }, + { + "order": 4, + "account_number": "1111111", + "balance": 825.98, + "card_number": None, + "credit_amount": None, + "date": "2013-10-25", + "debit_amount": 80, + "description": "Morrisons Petrol", + "transaction_id": None, + "text": "25 Oct 2013 Faster Payment\tMorrisons Petrol\t\t\t\t80.00\t825.98", + }, + { + "order": 5, + "account_number": "1111111", + "balance": 745.98, + "card_number": None, + "credit_amount": 20000, + "date": "2013-10-25", + "debit_amount": None, + "description": "Business Loan", + "transaction_id": None, + "text": "25 Oct 2013 BACS\tBusiness Loan\t\t20,000.00\t\t745.98", + }, + { + "order": 6, + "account_number": "1111111", + "balance": 20745.98, + "card_number": None, + "credit_amount": None, + "date": "2013-10-26", + "debit_amount": 2461.55, + "description": "James White Media", + "transaction_id": None, + "text": "26 Oct 2013 BACS\tJames White Media\t\t\t2,461.55\t20745.98", + }, + { + "order": 7, + "account_number": "1111111", + "balance": 18284.43, + "card_number": None, + "credit_amount": None, + "date": "2013-10-27", + "debit_amount": 100, + "description": "ATM High Street", + "transaction_id": None, + "text": "27 Oct 2013 Faster Payment\tATM High Street\t\t\t\t100.00\t18284.43", + }, + { + "order": 8, + "account_number": "1111111", + "balance": 18184.43, + "card_number": None, + "credit_amount": None, + "date": "2013-11-01", + "debit_amount": 150, + "description": "Acorn Advertising Studies", + "transaction_id": None, + "text": "01 Nov 2013 BACS\tAcorn Advertising Studies\t\t\t150.00\t18184.43", + }, + { + "order": 9, + "account_number": "1111111", + "balance": 18034.43, + "card_number": None, + "credit_amount": None, + "date": "2013-11-01", + "debit_amount": 177, + "description": "Marriott Hotel", + "transaction_id": None, + "text": "01 Nov 2013 BACS\tMarriott Hotel\t\t\t\t177.00\t18034.43", + }, + { + "order": 10, + "account_number": "1111111", + "balance": 17857.43, + "card_number": None, + "credit_amount": None, + "date": "2013-11-01", + "debit_amount": 122.22, + "description": "Abellio Scotrail Ltd", + "transaction_id": None, + "text": "01 Nov 2013 Faster Payment\tAbellio Scotrail Ltd\t\t\t\t122.22\t17857.43", + }, + { + "order": 11, + "account_number": "1111111", + "balance": 17735.21, + "card_number": None, + "credit_amount": None, + "date": "2013-11-01", + "debit_amount": 1200, + "description": "Cheque 0000234", + "transaction_id": None, + "text": "01 Nov 2013 CHQ\tCheque 0000234\t\t\t\t1,200.00\t17735.21", + }, + { + "order": 12, + "account_number": "1111111", + "balance": 16535.21, + "card_number": None, + "credit_amount": 9.33, + "date": "2013-12-01", + "debit_amount": None, + "description": "Interest Paid", + "transaction_id": None, + "text": "01 Dec 2013 Int. Bank\tInterest Paid\t\t\t9.33\t\t16535.21", + }, + { + "order": 13, + "account_number": "1111111", + "balance": 16544.54, + "card_number": None, + "credit_amount": None, + "date": "2013-12-01", + "debit_amount": 2470, + "description": "OVO Energy", + "transaction_id": None, + "text": "01 Dec 2013 DD\t\tOVO Energy\t\t\t\t2470.00\t16544.54", + }, + { + "order": 14, + "account_number": "1111111", + "balance": 14074.54, + "card_number": None, + "credit_amount": None, + "date": "2013-12-21", + "debit_amount": 10526.4, + "description": "Various Payment", + "transaction_id": None, + "text": "21 Dec 2013 BACS\tVarious Payment\t\t\t\t10,526.40\t14074.54", + }, + { + "order": 15, + "account_number": "1111111", + "balance": 3548.14, + "card_number": None, + "credit_amount": None, + "date": "2013-12-21", + "debit_amount": 1000, + "description": "HMRC", + "transaction_id": None, + "text": "21 Dec 2013 BACS\tHMRC\t\t\t\t\t1,000.00\t3548.14", + }, + { + "order": 16, + "account_number": "1111111", + "balance": 2548.14, + "card_number": None, + "credit_amount": None, + "date": "2013-12-21", + "debit_amount": 280, + "description": "DVLA", + "transaction_id": None, + "text": "21 Dec 2013 DD\t\tDVLA\t\t\t\t\t280.00\t2548.14", + }, + ], + "summaries": [{"name": "Paid Out", "total": 2684.1}, {"name": "Paid In", "total": 2180.4}], + "account_numbers": ["1111111"], + "routing_numbers": ["16-10-00"], + "pdf_url": "https://scdn.veryfi.com/bank_statements/919ba4778c039560/f02a38ed-e486-4d30-8354-23c25a0a4446/fe286c1b-bbdb-4a10-b9a8-81546f229de8.pdf?Expires=1727204326&Signature=K8v0yzgwW4NjvFC6k9smjmyznCirxI3z12ODX233hwtdnh9dY3DoqauItMoIkzcG6XL6y5sFoQPlisbck4FSyAXtEUGGGKgCgiezte3y4xMt43~zPR4WWbojPp4zGvZQpBwkxscGI-EFEcBPzLth2GGE0geYNg6R~nKeLn0mQ0rknTiqt4ras70-xC0KsgfwLdYa2xY8Kq56XtjgrsoKJSGIwFaUn0NmML8x~lTr3ifhp5s1t5KnylKDkaNynUNI2hCEUouqILknmmYmi8yLcL9BY3U9vZj0SbobqOnNbN41cMzPfGVm6WTBY~PCrAFNhal03L583hUhy5KHQjBzmQ__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "id": 4559568, + "external_id": None, + "created_date": "2024-09-24 18:43:46", + "updated_date": "2024-09-24 18:43:46", + "img_thumbnail_url": "https://scdn.veryfi.com/bank_statements/919ba4778c039560/f02a38ed-e486-4d30-8354-23c25a0a4446/thumbnail.png?Expires=1727204326&Signature=V9YEjDC-W-XaMLsWOFdKmC2X0h9moWf5Mh-j-5UEGNdeyf4dvYCCK6ByXubxeu-fzwTfCpgKl25y7CyCynsH~URsX~1wxsWE~bSLPA7CwkL54NpLmsmrksgAIdU67iV-O-ZDEwdOIQHBX5bUd2QaiGsK3wF2Z~xX5ouOMX3UkzPujYCBZtMB4pbidJmbuB6vC51y8V-yDbuRdHyoYA7ixNiczrSSuqpmkix6KC7ZGIu4eiIBPUnCpIlFc9~qaWkKeecJlLBeOM6SknJD~xTlYxGanbCWbvPYvIFeZX2sWUJPG2A9t~nknMsENymtGqrgy78kcweuv4uyUAbWMr-GxA__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "account_holder_address": "San Mateo", + "account_holder_name": "Mr Robot Roboto", + "account_number": "1111111", + "account_type": "CURRENT ACCOUNT", + "bank_address": "The Mound, Edinburgh EH1 1YZ.", + "bank_name": "Royal Bank of Scotland Plc.", + "bank_website": None, + "beginning_balance": 1803.9, + "due_date": None, + "ending_balance": 300.2, + "minimum_due": None, + "period_end_date": "2023-12-21", + "period_start_date": "2023-10-22", + "routing_number": "16-10-00", + "statement_date": None, + "statement_number": None, + "currency_code": "GBP", + "iban_number": "GB11RBOS 1610 0011 1111 11", + "swift": "RBOSGB2L", + "account_vat_number": None, + "bank_vat_number": "SC327000", +} + + +@responses.activate +def test_process_bank_statement_url(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/bank-statements/", + json=MOCK, + status=200, + ) + d = client.process_bank_statement_document_url( + file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg" + ) + assert d == MOCK + + +@responses.activate +def test_process_bank_statement(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/bank-statements/", + json=MOCK, + status=200, + ) + d = client.process_bank_statement_document(file_path="tests/assets/receipt_public.jpg") + assert d == MOCK + + +@responses.activate +def test_get_bank_statements(): + mock = [MOCK] + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.GET, + f"{client.versioned_url}/partner/bank-statements/", + json=mock, + status=200, + ) + d = client.get_bank_statements() + assert d == mock diff --git a/tests/test_bussines_cards.py b/tests/test_bussines_cards.py new file mode 100644 index 0000000..2ef78cb --- /dev/null +++ b/tests/test_bussines_cards.py @@ -0,0 +1,70 @@ +import responses + +from veryfi import Client + + +MOCK = { + "text": "Dmitry Birulia\ndmitry@veryfi.com\nVERYFI\twww.veryfi.com", + "id": 4662609, + "external_id": None, + "created_date": "2024-10-29 19:41:34", + "updated_date": "2024-10-29 19:41:34", + "organization": "VERYFI", + "logo_url": "https://cdn.veryfi.com/logos/us/421973497.png", + "img_url": "https://scdn.veryfi.com/business_cards/919ba4778c039560/235aaf5b-e464-4e03-adc2-443c39f1c053/d42e1e30-4170-4c81-927d-17c31317a94a.jpg?Expires=1730231794&Signature=bP807q99xtc0BcMPVD0SrnFw~aqr-zFUcJhlSEEKX2LnM2QnX4yDFdjITazqKbyJUjTxz1DtEuEN~fkN1ArE5C7u0Q70lLDsMcShRQgWylaQ45S3ulaECcwusdPnmJWczeGzyFj9CQ2RUvUfemuSffg8igP80~~MKdts0I9OJO4eg5FMV4Sh5cOg1y8H2DTbgPZHKO6VHMXQzBh8EW5PlM4IuCZAg5xKrJK2-XF~B8xCDzBECRMAVBjEu8hlm8SFs7D07LR9d6bXZMKv8egZ1HzyhTVdzm-TVBPNM328ou1qIGwLAOxq9PGJK3~2Vd9TSq5ONgPUesh8RAyc-NORcA__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "person": "Dmitry Birulia", + "parsed_name": {"family_name": "Birulia", "given_name": "Dmitry"}, + "title": None, + "email": "dmitry@veryfi.com", + "address": None, + "parsed_address": {}, + "mobile": None, + "phone": None, + "fax": None, + "web": "www.veryfi.com", +} + + +@responses.activate +def test_process_business_card_url(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/business-cards/", + json=MOCK, + status=200, + ) + d = client.process_bussines_card_document_url( + file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg" + ) + assert d == MOCK + + +@responses.activate +def test_process_business_card(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/business-cards/", + json=MOCK, + status=200, + ) + d = client.process_bussines_card_document(file_path="tests/assets/receipt_public.jpg") + assert d == MOCK + + +@responses.activate +def test_get_business_cards(): + mock = [MOCK] + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.GET, + f"{client.versioned_url}/partner/business-cards/", + json=mock, + status=200, + ) + d = client.get_business_cards() + assert d == mock diff --git a/tests/test_checks.py b/tests/test_checks.py new file mode 100644 index 0000000..b7a9fcd --- /dev/null +++ b/tests/test_checks.py @@ -0,0 +1,85 @@ +import responses + +from veryfi import Client + + +MOCK = { + "pdf_url": "https://scdn.veryfi.com/checks/919ba4778c039560/78535c60-e371-40fd-ae78-6d0c019b2c35/067e7057-e38b-4c32-9d87-720e0a8e232f.pdf?Expires=1730233783&Signature=fbR-arwLoH1YC8GVK52SvvilH59eHWIYp1o2WXw5UWr0s0CjKkrJ1Bx-PORKVzLbnJHOoYJDC4lU1iiqdq4~yDyz~-ygRHTNxyT9BJovFSzCBAc3Gnzv7uWMNIp-9mdV0QGk-Fu25eZfwd56Dfd2ZhG-EzreCRfh66r6338UF4EaHK5SG5b4i-NwkDaZ~qRZC6jNzYUJOGbXexYPbQxF5tMinc97ok~~fLQ--r0HWr7SvQyJisUqDnKS0DMTQujDz-7lStMJmvvlQX0jmpdcsq8DBIR6SnWZxHA7tM-ydD27Jt8l753X3uNtZuao61CeGSpQP09CnWlctTewm4IHKQ__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "id": 4662680, + "external_id": None, + "created_date": "2024-10-29 20:14:43", + "updated_date": "2024-10-29 20:14:43", + "img_thumbnail_url": "https://scdn.veryfi.com/checks/919ba4778c039560/78535c60-e371-40fd-ae78-6d0c019b2c35/thumbnail.jpg?Expires=1730233783&Signature=Sy8RUK0FLkeyhI3tpR8d60j-4BWyUH82D1frNU5FGGgGqumWWer6JgXbqM2eoHrgz04kwtKMwC-UZME0AZa-HtZr8j6a7TRO6M2uT0GoCHNQKv7rNcWUPajn4GsdU8VyY3b8KDx7WaGLd3VXP1TWAIqhW~DC07ZzjWbJ3K~8Ieyztt6Naijb~JbDFgUuIcNu8oikfoK3GAE8vzyElU5ctX4nmG1H-BEySCY-eIjf7GVwvaKjnZjab4h0Ox8SIdRyqVp~sj7dwlAtMlJmv6TxsNS14EDi91pxWPcvQlD7JCyQOH6brsgD0pkYVa4JFBF7yUTnGS4NXcCTiqkBhLh1Tw__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "text": "THIS DOCUMENT WAS PRINTED ON PAPER CONTAINING ULTRAVIOLET FIBERS AND AN ARTIFICIAL WATERMARK ON BACK\n\n\t70-2328/719 IL\nNationwide\nis on your side\t\t\tOctober 4, 2021\t\tCheck No. 118408359\nPO Box 2344\nBrea, CA 92822-2344\nPay: One Thousand Three Hundred Eight And 45/100 Dollars\t\t\t$1,308.45\n\nPay to the\tDmitry Birulia\nOrder of:\t733 Long Bridge\nSan Francisco CA 94158\n\n\tAUTHORIZED SIGNATURE\nMemo :F-602441-2021092406733\t\t\t\t\tVOID AFTER SIX MONTHS\n\n⑈0118408359⑈ ⑆031923284⑆ 8765129397⑈", + "meta": {}, + "amount": 1308.45, + "amount_text": "One Thousand Three Hundred Eight And 45/100 Dollars", + "bank_address": "Brea, CA 92822-2344", + "bank_name": None, + "fractional_routing_number": "70-2328/719", + "routing_from_fractional": "071923284", + "check_number": "0118408359", + "date": "2021-10-04", + "memo": "F-602441-2021092406733", + "payer_address": "PO Box 2344", + "payer_name": None, + "receiver_address": "733 Long Bridge\nSan Francisco CA 94158", + "receiver_name": "Dmitry Birulia", + "is_signed": True, + "is_endorsed": None, + "endorsement": { + "is_signed": None, + "is_mobile_or_remote_deposit_only": None + }, + "micr": { + "routing_number": "031923284", + "account_number": "8765129397", + "serial_number": None, + "raw": "C0118408359C A031923284A 8765129397C" + } +} + + +@responses.activate +def test_process_check_url(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/checks/", + json=MOCK, + status=200, + ) + d = client.process_check_url( + file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg" + ) + assert d == MOCK + + +@responses.activate +def test_process_check(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/checks/", + json=MOCK, + status=200, + ) + d = client.process_check(file_path="tests/assets/receipt_public.jpg") + assert d == MOCK + + +@responses.activate +def test_get_checks(): + mock = [MOCK] + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.GET, + f"{client.versioned_url}/partner/checks/", + json=mock, + status=200, + ) + d = client.get_checks() + assert d == mock diff --git a/tests/test_client.py b/tests/test_documents.py similarity index 69% rename from tests/test_client.py rename to tests/test_documents.py index 6c88123..78692dc 100644 --- a/tests/test_client.py +++ b/tests/test_documents.py @@ -1,7 +1,7 @@ +import pytest import responses -from veryfi import * -import pytest +from veryfi import Client @pytest.mark.parametrize("client_secret", [None, "s"]) @@ -196,82 +196,6 @@ def test_process_document_url(): assert d == mock -@responses.activate -def test_process_w9_document_url(): - mock = { - "account_numbers": "", - "address1": "28 E 3rd Ave, Suite 201", - "address2": "San Mateo, California, 94401", - "business_name": "", - "c_corp": 0, - "ein": "", - "exempt_payee_code": "", - "exemption": "", - "individual": 0, - "llc": 0, - "name": "Veryfi, Inc.", - "other": 0, - "other_description": "", - "partnership": 0, - "pdf_url": "https://scdn.veryfi.com/w9s/ec278ba0-31d6-4bd4-9d18-cb6a1232788e/output-1.pdf?Expires=1653031170&Signature=bftl34pf~Yni3ysaauqwL4BkfzgMPdAwMpw-SkjKZaxkgSt2~EYmX7NK~BGZ5IFUNdUIGBxTIsBsVWrP8LDQ3fME3kFM6qSn-udZp9Y8WJ-HbqQrIf1DwZQp-A2NSBCkRWgqAtYJo5dQW~UJJdCJx19ZIaYQZzYVQvuHmornzBStTV6D2qXQKUZpv9d5BrvTExZDnIxKy-ibyy09CfUPMc-lsVQLQEb-uQvud-JTf9Guy6k9Y4oT32HSvKcL0pMLvJqYC6mJUM2-5MJiBsYQSNs2e6s8xXcSBotiChMQwBg3RhGv5y-o8Aih1GNmBcvPHJIEyKOuiHeC9TUSELvp~w__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", - "requester": "AcMe Corporation 1010 Elm Str,\nMountain View, CA 94043", - "s_corp": 1, - "signature": 1, - "signature_date": "June 19, 2020", - "ssn": "", - "trust_estate": 0, - } - - client = Client(client_id="v", client_secret="w", username="o", api_key="c") - responses.add( - responses.POST, - f"{client.versioned_url}/partner/w9s/", - json=mock, - status=200, - ) - d = client.process_w9_document_url( - file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg", - ) - assert d == mock - - -@responses.activate -def test_process_w9_document(): - mock = { - "account_numbers": "", - "address1": "28 E 3rd Ave, Suite 201", - "address2": "San Mateo, California, 94401", - "business_name": "", - "c_corp": 0, - "ein": "", - "exempt_payee_code": "", - "exemption": "", - "individual": 0, - "llc": 0, - "name": "Veryfi, Inc.", - "other": 0, - "other_description": "", - "partnership": 0, - "pdf_url": "https://scdn.veryfi.com/w9s/ec278ba0-31d6-4bd4-9d18-cb6a1232788e/output-1.pdf?Expires=1653031170&Signature=bftl34pf~Yni3ysaauqwL4BkfzgMPdAwMpw-SkjKZaxkgSt2~EYmX7NK~BGZ5IFUNdUIGBxTIsBsVWrP8LDQ3fME3kFM6qSn-udZp9Y8WJ-HbqQrIf1DwZQp-A2NSBCkRWgqAtYJo5dQW~UJJdCJx19ZIaYQZzYVQvuHmornzBStTV6D2qXQKUZpv9d5BrvTExZDnIxKy-ibyy09CfUPMc-lsVQLQEb-uQvud-JTf9Guy6k9Y4oT32HSvKcL0pMLvJqYC6mJUM2-5MJiBsYQSNs2e6s8xXcSBotiChMQwBg3RhGv5y-o8Aih1GNmBcvPHJIEyKOuiHeC9TUSELvp~w__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", - "requester": "AcMe Corporation 1010 Elm Str,\nMountain View, CA 94043", - "s_corp": 1, - "signature": 1, - "signature_date": "June 19, 2020", - "ssn": "", - "trust_estate": 0, - } - - client = Client(client_id="v", client_secret="w", username="o", api_key="c") - responses.add( - responses.POST, - f"{client.versioned_url}/partner/w9s/", - json=mock, - status=200, - ) - d = client.process_w9_document(file_path="tests/assets/receipt_public.jpg") - assert d == mock - - @responses.activate def test_get_documents(): mock = [ @@ -367,45 +291,3 @@ def test_get_documents(): **{"created_lt": "2021-07-22+00:00:00"}, ) assert d == mock - - -@responses.activate -def test_tags(): - mock_doc_id = 169985445 - mock_resp = {"id": 6673474, "name": "tag_123"} - client = Client(client_id="v", client_secret="w", username="o", api_key="c") - responses.put( - f"{client.versioned_url}/partner/documents/{mock_doc_id}/tags/", - json=mock_resp, - status=200, - ) - d = client.add_tag(mock_doc_id, "tag_123") - assert d == mock_resp - - -@responses.activate -def test_replace_multiple_tags(): - mock_doc_id = 169985445 - mock_resp = {"id": 6673474, "tags": ["tag_1", "tag_2", "tag_3"]} - client = Client(client_id="v", client_secret="w", username="o", api_key="c") - responses.put( - f"{client.versioned_url}/partner/documents/{mock_doc_id}/", - json=mock_resp, - status=200, - ) - d = client.replace_tags(mock_doc_id, ["tag_1", "tag_2", "tag_3"]) - assert d == mock_resp - - -@responses.activate -def test_add_multiple_tags(): - mock_doc_id = 169985445 - mock_resp = {"id": 6673474, "tags": ["tag_1", "tag_2", "tag_3"]} - client = Client(client_id="v", client_secret="w", username="o", api_key="c") - responses.post( - f"{client.versioned_url}/partner/documents/{mock_doc_id}/tags/", - json=mock_resp, - status=200, - ) - d = client.add_tags(mock_doc_id, ["tag_1", "tag_2", "tag_3"]) - assert d == mock_resp diff --git a/tests/test_w2s.py b/tests/test_w2s.py new file mode 100644 index 0000000..b053eac --- /dev/null +++ b/tests/test_w2s.py @@ -0,0 +1,106 @@ +import responses + +from veryfi import Client + + +MOCK = { + "pdf_url": None, + "id": 4559395, + "external_id": None, + "created_date": "2024-09-24 18:04:25", + "updated_date": "2024-09-24 18:04:25", + "img_thumbnail_url": None, + "advance_eic_payment": None, + "employee_ssn": "123-45-6789", + "ein": "11-2233445", + "employer_name": "The Big Company", + "employer_address": "123 Main Street\nAnywhere, PA 12345", + "control_number": "A1B2", + "employee_name": "Jane A DOE", + "employee_address": "123 Elm Street\nAnywhere Else, PA 23456", + "wages_other_comps": 48500, + "federal_income_tax": 6835, + "ss_wages": 50000, + "ss_tax": 3100, + "medicare_wages": 50000, + "medicare_tax": 725, + "ss_tips": None, + "allocated_tips": None, + "dependent_care_benefits": None, + "non_qualified_plans": None, + "state": "PAL", + "employer_state_id": "1235", + "state_wages_tips": 50000, + "state_income_tax": 1535, + "local_wages_tips": 50000, + "local_income_tax": 750, + "locality_name": "MU", + "field_12a_col1": "D", + "field_12a_col2": 1500, + "field_12b_col1": "DD", + "field_12b_col2": 1000, + "field_12c_col1": "P", + "field_12c_col2": 4800, + "field_12d_col1": None, + "field_12d_col2": None, + "is_13a": False, + "is_13b": True, + "is_13c": False, + "states": [ + { + "state": "PAL", + "employer_state_id": "1235", + "state_wages_tips": 50000, + "state_income_tax": 1535, + "local_wages_tips": 50000, + "local_income_tax": 750, + "locality_name": "MU" + } + ], + "field_14_other": [] +} + + +@responses.activate +def test_process_w2_url(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/w2s/", + json=MOCK, + status=200, + ) + d = client.process_w2_document_url( + file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg" + ) + assert d == MOCK + + +@responses.activate +def test_process_w2(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/w2s/", + json=MOCK, + status=200, + ) + d = client.process_w2_document(file_path="tests/assets/receipt_public.jpg") + assert d == MOCK + + +@responses.activate +def test_get_w2s(): + mock = [MOCK] + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.GET, + f"{client.versioned_url}/partner/w2s/", + json=mock, + status=200, + ) + d = client.get_w2s() + assert d == mock diff --git a/tests/test_w8s.py b/tests/test_w8s.py new file mode 100644 index 0000000..0fc6a4b --- /dev/null +++ b/tests/test_w8s.py @@ -0,0 +1,209 @@ +import responses + +from veryfi import Client + + +MOCK = { + "pdf_url": "https://scdn.veryfi.com/w8s/919ba4778c039560/64ea51e9-4293-4a0e-99a6-8c3cd0a4f7ab/8468d271-2943-46e7-b9a2-3dd0372d3648.pdf?Expires=1730234368&Signature=e1bOil~yK4P4uKYLxX1NPfHA3PxiTxd-Ds4HVnjNXxY22D-ng2NGJQQrWAst5E0ionsdkkFPc7mKy0fp6MkmwnZF~I-j8e1P9fhbI-T-0NhiDji4gp6xt4~vm-i9MG34K~Xa3TWPA~kMbQ~Hj2gjiMMniXsH6HeqH99yfl-Vt2ZWEMdWl3~ZlMWEpnPVIzDXdDBc~uRYCOS0KiLD2pfNAORYwp1ayNiuhiJzucJPAfRuK00y0BoUEPBmBS-aLa62VhNYVKmUPtVNobS2MjcGcnqnBhOZlbw0B5VTLNqrSIgKSVy6I6Co4zAwLjgviQyoPArVtgmJR8UNdFRk9LuDqw__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "id": 4662698, + "external_id": None, + "created_date": "2024-10-29 20:24:28", + "updated_date": "2024-10-29 20:24:28", + "img_thumbnail_url": "https://scdn.veryfi.com/w8s/919ba4778c039560/64ea51e9-4293-4a0e-99a6-8c3cd0a4f7ab/thumbnail.jpg?Expires=1730234368&Signature=ch2MJSewRZV3LkSlMVKMK1~BXsUiO~wNT5bSllXXv1N85jGIqsJHYTkrTaL1fXSZERLXBC6DFIzOcgSYB~zPu3r3nzr7v2Q-WBc~jk8tSxRWGg4eYLkwW34h-RwhckXuiH6UCo6Q01SF6P4RAt9~YL4mIXOLmeahsjFQ-w0VHVuqsBQrsVJYoft7N-VXGgo-SRxKHBKX1eWEqYkV3hZJHslUxQvb0V1m3hEBuKHlj4gX5LcEHv-8wj90QlFujFUkRjJpMLqgsi3z-McQSOHNBc6fi3WqOWspiPzzUTQMqMswRPnkOMTlOIqNBoDO6oOrOIHR4CNtN6DZx9KjGTFSCQ__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "certify_checkbox": False, + "field_1_name": None, + "field_2_country": None, + "field_3_disregarded_entity_name": None, + "field_4_checkbox_central_issue_bank": False, + "field_4_checkbox_complex_trust": False, + "field_4_checkbox_corporation": False, + "field_4_checkbox_disregarded_entity": False, + "field_4_checkbox_estate": False, + "field_4_checkbox_foreign_government_controlled_entity": False, + "field_4_checkbox_foreign_government_integral_part": False, + "field_4_checkbox_grantor_trust": False, + "field_4_checkbox_hybrid_no": False, + "field_4_checkbox_hybrid_yes": False, + "field_4_checkbox_international_organization": False, + "field_4_checkbox_partnership": False, + "field_4_checkbox_private_foundation": False, + "field_4_checkbox_simple_trust": False, + "field_4_checkbox_tax_exempt_organization": False, + "field_5_checkbox_active_nffe": False, + "field_5_checkbox_certain_investment_entities_that_do_not_maintain_financial_accounts": False, + "field_5_checkbox_certified_deemed_compliant_ffi_low_value_accounts": False, + "field_5_checkbox_certified_deemed_compliant_limited_life_debt_investment_entity": False, + "field_5_checkbox_certified_deemed_compliant_nonregistering_local_bank": False, + "field_5_checkbox_certified_deemed_compliant_sponsored_closely_held_investment_vehicle": False, + "field_5_checkbox_direct_reporting_nffe": False, + "field_5_checkbox_entity_wholly_owned_exempt": False, + "field_5_checkbox_excepted_inter_affiliate_ffi": False, + "field_5_checkbox_excepted_nonfiancial_entity_bankruptcy": False, + "field_5_checkbox_excepted_nonfiancial_group_entity": False, + "field_5_checkbox_excepted_nonfiancial_start_up": False, + "field_5_checkbox_excepted_territory_nffe": False, + "field_5_checkbox_exempt_retirement_plans": False, + "field_5_checkbox_foreign_government": False, + "field_5_checkbox_international_organization": False, + "field_5_checkbox_nonparticipating_ffi": False, + "field_5_checkbox_nonprofit": False, + "field_5_checkbox_nonreporting_iga_ffi": False, + "field_5_checkbox_not_financial_account": False, + "field_5_checkbox_organization_501c": False, + "field_5_checkbox_owner_documented_ffi": True, + "field_5_checkbox_participating_ffi": False, + "field_5_checkbox_passive_nffe": False, + "field_5_checkbox_publicly_traded_nffe": False, + "field_5_checkbox_registered_deemed_compliant_ffi": False, + "field_5_checkbox_reporting_model_one_ffi": False, + "field_5_checkbox_reporting_model_two_ffi": False, + "field_5_checkbox_restricted_distributor": False, + "field_5_checkbox_sponsored_direct_reporting_nffe": False, + "field_5_checkbox_sponsored_ffi": False, + "field_5_checkbox_territory_financial_institutions": False, + "field_6_address": None, + "field_6_city": None, + "field_6_country": None, + "field_7_mailing_city": None, + "field_7_mailing_country": None, + "field_7_mailing_street": None, + "field_8_tin": None, + "field_9a_giin": None, + "field_9b_foreign_tin": None, + "field_9c_checkbox_tin_not_required": False, + "field_10_reference_number": None, + "field_11_checkbox_branch_nonparticipating_ffi": False, + "field_11_checkbox_participating_ffi": False, + "field_11_checkbox_reporting_model_one_ffi": False, + "field_11_checkbox_reporting_model_two_ffi": False, + "field_11_checkbox_us_branch": False, + "field_12_disregarded_entity_city": None, + "field_12_disregarded_entity_country": None, + "field_12_disregarded_entity_street": None, + "field_13_disregarded_entity_giin": None, + "field_14a_checkbox": False, + "field_14a_resident_of": None, + "field_14b_checkbox_active_trade_or_business_test": False, + "field_14b_checkbox_benefit_items": False, + "field_14b_checkbox_derivative_benefits_test": False, + "field_14b_checkbox_favorable": False, + "field_14b_checkbox_government": False, + "field_14b_checkbox_no_lob_article_in_treaty": False, + "field_14b_checkbox_other_tax_exempt": False, + "field_14b_checkbox_other": False, + "field_14b_checkbox_ownership_and_base_erosion_test": False, + "field_14b_checkbox_publicly_traded_corporation": False, + "field_14b_checkbox_subsidiary_of_publicly_traded_corporation": False, + "field_14b_checkbox_tax_exempt_pension": False, + "field_14b_other_article": None, + "field_14c_checkbox_dividends": False, + "field_15_special_rates_article": None, + "field_15_special_rates_explanation": None, + "field_15_special_rates_income_type": None, + "field_15_special_rates_percentage": None, + "field_16_name": None, + "field_17a_checkbox": False, + "field_17b_checkbox": False, + "field_18_checkbox": False, + "field_19_checkbox": False, + "field_20_name": None, + "field_21_checkbox": False, + "field_22_checkbox": False, + "field_23_checkbox": False, + "field_24a_checkbox": False, + "field_24b_checkbox": False, + "field_24c_checkbox": False, + "field_24d_checkbox": False, + "field_25a_checkbox": False, + "field_25b_checkbox": False, + "field_25c_checkbox": False, + "field_26_checkbox_model_one": False, + "field_26_checkbox_model_two": False, + "field_26_checkbox_trustee_foreign": False, + "field_26_checkbox_trustee_us": False, + "field_26_checkbox": False, + "field_26_country": None, + "field_26_treated_as": None, + "field_26_trustee_name": None, + "field_27_checkbox": False, + "field_28a_checkbox": False, + "field_28b_checkbox": False, + "field_29a_checkbox": False, + "field_29b_checkbox": False, + "field_29c_checkbox": False, + "field_29d_checkbox": False, + "field_29e_checkbox": False, + "field_29f_checkbox": False, + "field_30_checkbox": False, + "field_31_checkbox": False, + "field_32_checkbox": False, + "field_33_checkbox": False, + "field_33_date": None, + "field_34_checkbox": False, + "field_34_date": None, + "field_35_checkbox": False, + "field_35_date": None, + "field_36_checkbox": False, + "field_37a_checkbox": False, + "field_37a_name": None, + "field_37b_checkbox": False, + "field_37b_market_name": None, + "field_37b_name": None, + "field_38_checkbox": False, + "field_39_checkbox": False, + "field_40a_checkbox": False, + "field_40b_checkbox": False, + "field_40c_checkbox": False, + "field_41_checkbox": False, + "field_42_name": None, + "field_43_checkbox": False, + "passive_nffe_owners": [], + "signature_date": None, + "signature_name": None, + "signed": True +} + + +@responses.activate +def test_process_w8_url(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/w-8ben-e/", + json=MOCK, + status=200, + ) + d = client.process_w8_document_url( + file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg" + ) + assert d == MOCK + + +@responses.activate +def test_process_w8(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/w-8ben-e/", + json=MOCK, + status=200, + ) + d = client.process_w8_document(file_path="tests/assets/receipt_public.jpg") + assert d == MOCK + + +@responses.activate +def test_get_w8s(): + mock = [MOCK] + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.GET, + f"{client.versioned_url}/partner/w-8ben-e/", + json=mock, + status=200, + ) + d = client.get_w8s() + assert d == mock diff --git a/tests/test_w9s.py b/tests/test_w9s.py new file mode 100644 index 0000000..3ff2b79 --- /dev/null +++ b/tests/test_w9s.py @@ -0,0 +1,72 @@ +import responses + +from veryfi import Client + + +MOCK = { + "account_numbers": "", + "address1": "28 E 3rd Ave, Suite 201", + "address2": "San Mateo, California, 94401", + "business_name": "", + "c_corp": 0, + "ein": "", + "exempt_payee_code": "", + "exemption": "", + "individual": 0, + "llc": 0, + "name": "Veryfi, Inc.", + "other": 0, + "other_description": "", + "partnership": 0, + "pdf_url": "https://scdn.veryfi.com/w9s/ec278ba0-31d6-4bd4-9d18-cb6a1232788e/output-1.pdf?Expires=1653031170&Signature=bftl34pf~Yni3ysaauqwL4BkfzgMPdAwMpw-SkjKZaxkgSt2~EYmX7NK~BGZ5IFUNdUIGBxTIsBsVWrP8LDQ3fME3kFM6qSn-udZp9Y8WJ-HbqQrIf1DwZQp-A2NSBCkRWgqAtYJo5dQW~UJJdCJx19ZIaYQZzYVQvuHmornzBStTV6D2qXQKUZpv9d5BrvTExZDnIxKy-ibyy09CfUPMc-lsVQLQEb-uQvud-JTf9Guy6k9Y4oT32HSvKcL0pMLvJqYC6mJUM2-5MJiBsYQSNs2e6s8xXcSBotiChMQwBg3RhGv5y-o8Aih1GNmBcvPHJIEyKOuiHeC9TUSELvp~w__&Key-Pair-Id=APKAJCILBXEJFZF4DCHQ", + "requester": "AcMe Corporation 1010 Elm Str,\nMountain View, CA 94043", + "s_corp": 1, + "signature": 1, + "signature_date": "June 19, 2020", + "ssn": "", + "trust_estate": 0, +} + + +@responses.activate +def test_process_w9_document_url(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/w9s/", + json=MOCK, + status=200, + ) + d = client.process_w9_document_url( + file_url="http://cdn-dev.veryfi.com/testing/veryfi-python/receipt_public.jpg", + ) + assert d == MOCK + + +@responses.activate +def test_process_w9_document(): + + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.POST, + f"{client.versioned_url}/partner/w9s/", + json=MOCK, + status=200, + ) + d = client.process_w9_document(file_path="tests/assets/receipt_public.jpg") + assert d == MOCK + + +@responses.activate +def test_get_w9_documents(): + mock = [MOCK] + client = Client(client_id="v", client_secret="w", username="o", api_key="c") + responses.add( + responses.GET, + f"{client.versioned_url}/partner/w9s/", + json=mock, + status=200, + ) + d = client.get_w9s() + assert d == mock From f8b637bc56583220ce4fa276c068bba244febf6b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 19 Mar 2025 11:35:11 -0500 Subject: [PATCH 3/8] refactor and add missing endpoints --- veryfi/_documents/__init__.py | 0 veryfi/_documents/line_items.py | 73 ++++++ veryfi/_documents/pdf_split.py | 88 +++++++ veryfi/_documents/tags.py | 78 ++++++ veryfi/_w2s/__init__.py | 0 veryfi/_w2s/w2_split.py | 77 ++++++ veryfi/a_docs.py | 116 +++++++++ veryfi/bank_statements.py | 114 +++++++++ veryfi/bussines_cards.py | 88 +++++++ veryfi/checks.py | 171 +++++++++++++ veryfi/client.py | 415 +++----------------------------- veryfi/client_base.py | 108 +++++++++ veryfi/documents.py | 195 +++++++++++++++ veryfi/errors.py | 5 + veryfi/w2s.py | 108 +++++++++ veryfi/w8s.py | 109 +++++++++ veryfi/w9s.py | 110 +++++++++ 17 files changed, 1469 insertions(+), 386 deletions(-) create mode 100644 veryfi/_documents/__init__.py create mode 100644 veryfi/_documents/line_items.py create mode 100644 veryfi/_documents/pdf_split.py create mode 100644 veryfi/_documents/tags.py create mode 100644 veryfi/_w2s/__init__.py create mode 100644 veryfi/_w2s/w2_split.py create mode 100644 veryfi/a_docs.py create mode 100644 veryfi/bank_statements.py create mode 100644 veryfi/bussines_cards.py create mode 100644 veryfi/checks.py create mode 100644 veryfi/client_base.py create mode 100644 veryfi/documents.py create mode 100644 veryfi/w2s.py create mode 100644 veryfi/w8s.py create mode 100644 veryfi/w9s.py diff --git a/veryfi/_documents/__init__.py b/veryfi/_documents/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/veryfi/_documents/line_items.py b/veryfi/_documents/line_items.py new file mode 100644 index 0000000..d63be81 --- /dev/null +++ b/veryfi/_documents/line_items.py @@ -0,0 +1,73 @@ +from typing import * + +from veryfi.client_base import Client + + +class LineItems: + def __init__(self, client: Client): + self.client = client + + def get_line_items(self, document_id): + """ + Retrieve all line items for a document. + https://docs.veryfi.com/api/receipts-invoices/get-document-line-items/ + + :param document_id: ID of the document you'd like to retrieve + :return: List of line items extracted from the document + """ + return self.client._request("GET", f"/documents/{document_id}/line-items/") + + def get_line_item(self, document_id, line_item_id): + """ + Retrieve a line item for existing document by ID. + https://docs.veryfi.com/api/receipts-invoices/get-a-line-item/ + + :param document_id: ID of the document you'd like to retrieve + :param line_item_id: ID of the line item you'd like to retrieve + :return: Line item extracted from the document + """ + return self.client._request("GET", f"/documents/{document_id}/line-items/{line_item_id}") + + def add_line_item(self, document_id: int, payload: Dict) -> Dict: + """ + Add a new line item on an existing document. + https://docs.veryfi.com/api/receipts-invoices/create-a-line-item/ + + :param document_id: ID of the document you'd like to update + :param payload: line item object to add + :return: Added line item data + """ + return self.client._request("POST", f"/documents/{document_id}/line-items/", payload) + + def update_line_item(self, document_id: int, line_item_id: int, payload: Dict) -> Dict: + """ + Update an existing line item on an existing document. + https://docs.veryfi.com/api/receipts-invoices/update-a-line-item/ + + :param document_id: ID of the document you'd like to update + :param line_item_id: ID of the line item you'd like to update + :param payload: line item object to update + :return: Line item data with updated fields, if fields are writable. Otherwise line item data with unchanged fields. + """ + return self.client._request( + "PUT", f"/documents/{document_id}/line-items/{line_item_id}", payload + ) + + def delete_line_items(self, document_id): + """ + Delete all line items on an existing document. + https://docs.veryfi.com/api/receipts-invoices/delete-all-document-line-items/ + + :param document_id: ID of the document you'd like to delete + """ + self.client._request("DELETE", f"/documents/{document_id}/line-items/") + + def delete_line_item(self, document_id, line_item_id): + """ + Delete an existing line item on an existing document. + https://docs.veryfi.com/api/receipts-invoices/delete-a-line-item/ + + :param document_id: ID of the document you'd like to delete + :param line_item_id: ID of the line item you'd like to delete + """ + self.client._request("DELETE", f"/documents/{document_id}/line-items/{line_item_id}") diff --git a/veryfi/_documents/pdf_split.py b/veryfi/_documents/pdf_split.py new file mode 100644 index 0000000..652b316 --- /dev/null +++ b/veryfi/_documents/pdf_split.py @@ -0,0 +1,88 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class PDFSplit: + def __init__(self, client: Client): + self.client = client + + def get_pdf(self, **kwargs): + """ + Get a Submitted PDF endpoint allows you to retrieve a collection of previously processed. + https://docs.veryfi.com/api/receipts-invoices/get-submitted-pdf/ + + :param kwargs: Additional query parameters. + :return: The processed Document response. + """ + endpoint_name = "/documents-set/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def get_documents_from_pdf(self, document_id: int): + """ + Get Documents from PDF endpoint allows you to retrieve a collection of previously processed documents. + https://docs.veryfi.com/api/receipts-invoices/get-documents-from-pdf/ + :param document_id: ID of the document you'd like to retrieve + :return: The processed Document response. + """ + endpoint_name = f"/documents-set/{document_id}" + return self.client._request("GET", endpoint_name, {}) + + def split_and_process_pdf( + self, + file_path: str, + categories: Optional[List] = None, + **kwargs, + ) -> Dict: + """ + Process a document and extract all the fields from it + https://docs.veryfi.com/api/receipts-invoices/split-and-process-a-pdf/ + + :param file_path: Path on disk to a file to submit for data extraction + :param categories: List of categories Veryfi can use to categorize the document + :param kwargs: Additional body parameters + :return: Data extracted from the document + """ + endpoint_name = "/documents-set/" + categories = categories or [] + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + "categories": categories, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def split_and_process_pdf_url( + self, + file_url: Optional[str] = None, + categories: Optional[List[str]] = None, + max_pages_to_process: Optional[int] = None, + file_urls: Optional[List[str]] = None, + **kwargs, + ) -> Dict: + """Process Document from url and extract all the fields from it. + https://docs.veryfi.com/api/receipts-invoices/split-and-process-a-pdf/ + + :param file_url: Required if file_urls isn't specified. Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_urls: Required if file_url isn't specifies. List of publicly accessible URLs to multiple files, e.g. ["https://cdn.example.com/receipt1.jpg", "https://cdn.example.com/receipt2.jpg"] + :param categories: List of categories to use when categorizing the document + :param max_pages_to_process: When sending a long document to Veryfi for processing, this parameter controls how many pages of the document will be read and processed, starting from page 1. + :param kwargs: Additional body parameters + :return: Data extracted from the document. + """ + endpoint_name = "/documents-set/" + categories = categories or [] + request_arguments = { + "categories": categories, + "file_url": file_url, + "file_urls": file_urls, + "max_pages_to_process": max_pages_to_process, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) diff --git a/veryfi/_documents/tags.py b/veryfi/_documents/tags.py new file mode 100644 index 0000000..0cdfb6e --- /dev/null +++ b/veryfi/_documents/tags.py @@ -0,0 +1,78 @@ +from typing import * +from veryfi.client_base import Client + + +class Tags: + def __init__(self, client: Client): + self.client = client + + def get_tags(self, document_id): + """ + Return all Tag assigned to a specific Document. + https://docs.veryfi.com/api/receipts-invoices/get-document-tags/ + + :param document_id: ID of the document you'd like to get + :return: Added tags data + """ + endpoint_name = f"/documents/{document_id}/tags" + return self.client._request("GET", endpoint_name, {}) + + def add_tag(self, document_id, tag_name): + """ + Add a new tag on an existing document. + https://docs.veryfi.com/api/receipts-invoices/add-a-tag-to-a-document/ + + :param document_id: ID of the document you'd like to update + :param tag_name: name of the new tag + :return: Added tag data + """ + endpoint_name = f"/documents/{document_id}/tags/" + request_arguments = {"name": tag_name} + return self.client._request("PUT", endpoint_name, request_arguments) + + def replace_tags(self, document_id, tags): + """ + Replace multiple tags on an existing document. + https://docs.veryfi.com/api/receipts-invoices/update-a-document/ + + :param document_id: ID of the document you'd like to update + :param tags: array of strings + :return: Added tags data + """ + endpoint_name = f"/documents/{document_id}/" + request_arguments = {"tags": tags} + return self.client._request("PUT", endpoint_name, request_arguments) + + def add_tags(self, document_id, tags): + """ + Add multiple tags on an existing document. + https://docs.veryfi.com/api/receipts-invoices/add-tags-to-a-document/ + + :param document_id: ID of the document you'd like to update + :param tags: array of strings + :return: Added tags data + """ + endpoint_name = f"/documents/{document_id}/tags/" + request_arguments = {"tags": tags} + return self.client._request("POST", endpoint_name, request_arguments) + + def delete_tag(self, document_id, tag_id): + """ + Unlink a tag from the list of tags assigned to a specific Document. + https://docs.veryfi.com/api/receipts-invoices/unlink-a-tag-from-a-document/ + + :param document_id: ID of the document + :param tag_id: ID of the tag you'd like to unlink + """ + endpoint_name = f"/documents/{document_id}/tags/{tag_id}" + self.client._request("DELETE", endpoint_name, {}) + + def delete_tags(self, document_id): + """ + Unlink all tags assigned to a specific Document. + https://docs.veryfi.com/api/receipts-invoices/unlink-all-tags-from-a-document/ + + :param document_id: ID of the document + """ + endpoint_name = f"/documents/{document_id}/tags" + self.client._request("DELETE", endpoint_name, {}) diff --git a/veryfi/_w2s/__init__.py b/veryfi/_w2s/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/veryfi/_w2s/w2_split.py b/veryfi/_w2s/w2_split.py new file mode 100644 index 0000000..bd6155f --- /dev/null +++ b/veryfi/_w2s/w2_split.py @@ -0,0 +1,77 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class W2Split: + def __init__(self, client: Client): + self.client = client + + def get_documents_from_w2(self, document_id: int) -> Dict: + """ + Veryfi's Get Documents from W-2 endpoint allows you to retrieve a collection of previously processed W-2s. + https://docs.veryfi.com/api/get-documents-from-w-2/ + + :param document_id: The unique identifier of the document. + :return: Document Response + """ + endpoint_name = f"/w2s-set/{document_id}/" + return self.client._request("GET", endpoint_name, {}) + + def get_list_of_w2s(self, **kwargs) -> Dict: + """ + Veryfi's Get List of W-2s endpoint allows you to retrieve a collection of previously processed W-2s. + https://docs.veryfi.com/api/get-list-of-documents-from-w-2/ + + :param kwargs: Query parameters + :return: List of W-2s + """ + endpoint_name = "/w2s-set/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def split_and_process_w2(self, file_path: str, **kwargs) -> Dict: + """ + Process a document and extract all the fields from it + https://docs.veryfi.com/api/split-and-process-a-w-2/ + + :param file_path: Path on disk to a file to submit for data extraction + :param kwargs: Additional body parameters + :return: Data extracted from the document + """ + endpoint_name = "/w2s-set/" + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def split_and_process_w2_url( + self, + file_url: Optional[str] = None, + file_urls: Optional[List[str]] = None, + max_pages_to_process: Optional[int] = None, + **kwargs, + ) -> Dict: + """Process Document from url and extract all the fields from it. + https://docs.veryfi.com/api/split-and-process-a-w-2/ + + :param file_url: Required if file_urls isn't specified. Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_urls: Required if file_url isn't specifies. List of publicly accessible URLs to multiple files, e.g. ["https://cdn.example.com/receipt1.jpg", "https://cdn.example.com/receipt2.jpg"] + :param max_pages_to_process: When sending a long document to Veryfi for processing, this parameter controls how many pages of the document will be read and processed, starting from page 1. + :param kwargs: Additional body parameters + :return: Data extracted from the document. + """ + endpoint_name = "/w2s-set/" + request_arguments = { + "file_url": file_url, + "file_urls": file_urls, + "max_pages_to_process": max_pages_to_process, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) diff --git a/veryfi/a_docs.py b/veryfi/a_docs.py new file mode 100644 index 0000000..f796938 --- /dev/null +++ b/veryfi/a_docs.py @@ -0,0 +1,116 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class ADocs: + def __init__(self, client: Client): + self.client = client + + def process_any_document_url( + self, blueprint_name: str, file_url: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: + """ + Process Any Document from url and extract all the fields from it. + https://docs.veryfi.com/api/anydocs/process-A-doc/ + + :param blueprint_name: The blueprint name which was used to extract the data. Same as blueprint_name. + :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the document. + """ + if file_name is None: + file_name = os.path.basename(file_url) + endpoint_name = "/any-documents/" + request_arguments = { + "blueprint_name": blueprint_name, + "file_name": file_name, + "file_url": file_url, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_any_document( + self, blueprint_name: str, file_path: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: + """ + Process Any Document from url and extract all the fields from it. + https://docs.veryfi.com/api/anydocs/process-A-doc/ + + :param blueprint_name: The blueprint name which was used to extract the data. Same as blueprint_name. + :param file_path: Path on disk to a file to submit for data extraction + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the document. + """ + endpoint_name = "/any-documents/" + if file_name is None: + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "blueprint_name": blueprint_name, + "file_name": file_name, + "file_data": base64_encoded_string, + } + + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def get_any_document(self, document_id: int, **kwargs) -> Dict: + """ + Get aDocs endpoint allows you to retrieve a previously processed any doc. + https://docs.veryfi.com/api/anydocs/get-a-A-doc/ + + :param document_id: The unique identifier of the document. + :param kwargs: Additional query parameters + :return: Document Data + """ + endpoint_name = f"/any-documents/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def get_any_documents( + self, + created_date__gt: Optional[str] = None, + created_date__gte: Optional[str] = None, + created_date__lt: Optional[str] = None, + created_date__lte: Optional[str] = None, + **kwargs, + ) -> List[Dict]: + """ + Get a list of documents + https://docs.veryfi.com/api/anydocs/get-A-docs/ + + :param created_date__gt: Search for documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__gte: Search for documents with a created date greater than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__lt: Search for documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param created_date__lte: Search for documents with a created date less than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param kwargs: Additional query parameters + :return: List of previously processed documents + """ + query_params = {} + if created_date__gt: + query_params["created_date__gt"] = created_date__gt + if created_date__gte: + query_params["created_date__gte"] = created_date__gte + if created_date__lt: + query_params["created_date__lt"] = created_date__lt + if created_date__lte: + query_params["created_date__lte"] = created_date__lte + query_params.update(kwargs) + + endpoint_name = "/any-documents/" + return self.client._request("GET", endpoint_name, {}, query_params) + + def delete_any_document(self, document_id: int): + """ + Delete a document. + https://docs.veryfi.com/api/anydocs/delete-a-A-doc/ + + :param document_id: The unique identifier of the document. + """ + endpoint_name = f"/any-documents/{document_id}/" + self.client._request("DELETE", endpoint_name, {}) diff --git a/veryfi/bank_statements.py b/veryfi/bank_statements.py new file mode 100644 index 0000000..277389d --- /dev/null +++ b/veryfi/bank_statements.py @@ -0,0 +1,114 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class BankStatements: + def __init__(self, client: Client): + self.client = client + + def process_bank_statement_document_url( + self, file_url: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: + """ + Process bank statement document from url and extract all the fields from it. + https://docs.veryfi.com/api/bank-statements/process-a-bank-statement/ + + :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters. + :return: Data extracted from the bank statement. + """ + if file_name is None: + file_name = os.path.basename(file_url) + endpoint_name = "/bank-statements/" + request_arguments = { + "file_name": file_name, + "file_url": file_url, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_bank_statement_document( + self, file_path: str, file_name: Optional[str] = None, **kwargs + ): + """ + Process bank statement document from url and extract all the fields from it. + https://docs.veryfi.com/api/bank-statements/process-a-bank-statement/ + + :param file_path: Path on disk to a file to submit for data extraction + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters. + :return: Data extracted from the bank statement. + """ + endpoint_name = "/bank-statements/" + if file_name is None: + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + document = self.client._request("POST", endpoint_name, request_arguments) + return document + + def get_bank_statement(self, document_id: int, **kwargs) -> Dict: + """ + Get bank statement endpoint allows you to retrieve a previously processed bank statement. + https://docs.veryfi.com/api/bank-statements/get-a-bank-statement/ + + :param document_id: The unique identifier of the document. + :param kwargs: Additional query parameters. + :return: Document Data + """ + endpoint_name = f"/bank-statements/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def get_bank_statements( + self, + created_date__gt: Optional[str] = None, + created_date__gte: Optional[str] = None, + created_date__lt: Optional[str] = None, + created_date__lte: Optional[str] = None, + **kwargs, + ): + """ + Get list of bank statement documents. + https://docs.veryfi.com/api/bank-statements/get-bank-statements/ + + :param created_date__gt: Search for bank statement documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created__gt and created__gte in a single request. + :param created_date__gte: Search for bank statement documents with a created date greater than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created__gt and created__gte in a single request. + :param created_date__lt: Search for bank statement documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created__lt and created__lte in a single request. + :param created_date__lte: Search for bank statement documents with a created date less than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created__lt and created__lte in a single request. + :param kwargs: Additional query parameters. + :return: List of previously processed documents + """ + + query_params = {} + if created_date__gt: + query_params["created_date__gt"] = created_date__gt + if created_date__gte: + query_params["created_date__gte"] = created_date__gte + if created_date__lt: + query_params["created_date__lt"] = created_date__lt + if created_date__lte: + query_params["created_date__lte"] = created_date__lte + query_params.update(kwargs) + + endpoint_name = "/bank-statements/" + return self.client._request("GET", endpoint_name, {}, query_params) + + def delete_bank_statement(self, document_id: int): + """ + Delete a bank statement document. + https://docs.veryfi.com/api/bank-statements/delete-a-bank-statement/ + + :param document_id: The unique identifier of the document. + """ + + endpoint_name = f"/bank-statements/{document_id}/" + self.client._request("DELETE", endpoint_name, {}) diff --git a/veryfi/bussines_cards.py b/veryfi/bussines_cards.py new file mode 100644 index 0000000..f1291fa --- /dev/null +++ b/veryfi/bussines_cards.py @@ -0,0 +1,88 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class BussinesCards: + def __init__(self, client: Client): + self.client = client + + def process_bussines_card_document_url( + self, file_url: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: + """ + Process bussiness card from url and extract all the fields from it. + https://docs.veryfi.com/api/business-cards/process-a-business-card/ + + :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the business card. + """ + if file_name is None: + file_name = os.path.basename(file_url) + endpoint_name = "/business-cards/" + request_arguments = { + "file_name": file_name, + "file_url": file_url, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_bussines_card_document( + self, file_path: str, file_name: Optional[str] = None, **kwargs + ): + """ + Process bussiness card from url and extract all the fields from it. + https://docs.veryfi.com/api/business-cards/process-a-business-card/ + + :param file_path: Path on disk to a file to submit for data extraction + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the business card. + """ + endpoint_name = "/business-cards/" + if file_name is None: + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def get_business_cards(self, **kwargs: Dict): + """ + Get list of business card documents. + https://docs.veryfi.com/api/business-cards/get-business-cards/ + + :param kwargs: Additional query parameters + :return: List of previously processed documents + """ + endpoint_name = "/business-cards/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def get_business_card(self, document_id: int, **kwargs: Dict): + """ + Get a business card document. + https://docs.veryfi.com/api/business-cards/get-a-business-card/ + + :param document_id: The unique identifier of the document. + :param kwargs: Additional query parameters + """ + endpoint_name = f"/business-cards/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def delete_business_card(self, document_id: int): + """ + Delete a business card document. + https://docs.veryfi.com/api/business-cards/delete-a-business-card/ + + :param document_id: The unique identifier of the document. + """ + endpoint_name = f"/business-cards/{document_id}/" + self.client._request("DELETE", endpoint_name, {}) diff --git a/veryfi/checks.py b/veryfi/checks.py new file mode 100644 index 0000000..ff2e16d --- /dev/null +++ b/veryfi/checks.py @@ -0,0 +1,171 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class Checks: + def __init__(self, client: Client): + self.client = client + + def get_checks( + self, + created_date__gt: Optional[str] = None, + created_date__gte: Optional[str] = None, + created_date__lt: Optional[str] = None, + created_date__lte: Optional[str] = None, + **kwargs, + ) -> List[Dict]: + """ + Get list of checks + https://docs.veryfi.com/api/checks/get-checks/ + + :param created_date__gt: Search for checks documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__gte: Search for checks documents with a created date greater than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__lt: Search for checks documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param created_date__lte: Search for checks documents with a created date less than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param kwargs: Additional query parameters + :return: List of previously processed documents + """ + + query_params = {} + if created_date__gt: + query_params["created_date__gt"] = created_date__gt + if created_date__gte: + query_params["created_date__gte"] = created_date__gte + if created_date__lt: + query_params["created_date__lt"] = created_date__lt + if created_date__lte: + query_params["created_date__lte"] = created_date__lte + query_params.update(kwargs) + + endpoint_name = "/checks/" + return self.client._request("GET", endpoint_name, {}, query_params) + + def get_check(self, document_id: int, **kwargs) -> Dict: + """ + Retrieve a check document by ID + https://docs.veryfi.com/api/checks/get-a-check/ + + :param document_id: ID of the document you'd like to retrieve + :param kwargs: Additional query parameters + :return: Data extracted from the Document + """ + endpoint_name = f"/checks/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def process_check( + self, + file_path: str, + **kwargs, + ): + """ + Process a check document and extract all the fields from it + https://docs.veryfi.com/api/checks/process-a-check/ + + :param file_path: Path on disk to a file to submit for data extraction + :param kwargs: Additional body parameters + :return: Data extracted from the document + """ + endpoint_name = "/checks/" + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_check_url( + self, + file_url: Optional[str] = None, + file_urls: Optional[List[str]] = None, + **kwargs, + ) -> Dict: + """ + Process a check document from url and extract all the fields from it. + https://docs.veryfi.com/api/checks/process-a-check/ + + :param file_url: Required if file_urls isn't specified. Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_urls: Required if file_url isn't specifies. List of publicly accessible URLs to multiple files, e.g. ["https://cdn.example.com/receipt1.jpg", "https://cdn.example.com/receipt2.jpg"] + :param kwargs: Additional body parameters. + :return: Data extracted from the document. + """ + endpoint_name = "/checks/" + request_arguments = { + "file_url": file_url, + "file_urls": file_urls, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_check_with_remittance(self, file_path: str, **kwargs) -> Dict: + """ + Process a check document with remittance and extract all the fields from it + https://docs.veryfi.com/api/checks/process-a-check-with-remittance/ + + :param file_path: Path on disk to a file to submit for data extraction + :param kwargs: Additional body parameters + :return: Data extracted from the document and check + """ + endpoint_name = "/check-with-document/" + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_check_with_remittance_url( + self, + file_url: str, + file_urls: Optional[List[str]] = None, + **kwargs, + ) -> Dict: + """ + Process a check document with remittance from url and extract all the fields from it + https://docs.veryfi.com/api/checks/process-a-check-with-remittance/ + + :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_urls: Optional list of publicly accessible URLs to multiple files, e.g. ["https://cdn.example.com/receipt1.jpg", "https://cdn.example.com/receipt2.jpg"] + :param kwargs: Additional body parameters + :return: Data extracted from the document and check + """ + endpoint_name = "/check-with-document/" + request_arguments = { + "file_url": file_url, + "file_urls": file_urls, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def update_check(self, document_id: int, **kwargs) -> Dict: + """ + Update data for a previously processed document, including almost any field like `vendor`, `date`, `notes` and etc. + https://docs.veryfi.com/api/checks/update-a-check/ + + ```veryfi_client.update_check(id, date="2021-01-01", notes="look what I did")``` + + :param document_id: ID of the document you'd like to update + :param kwargs: fields to update + :return: A document json with updated fields, if fields are writable. Otherwise a document with unchanged fields. + """ + + endpoint_name = f"/checks/{document_id}/" + return self.client._request("PUT", endpoint_name, kwargs) + + def delete_check(self, document_id: int): + """ + Delete a check document from Veryfi + https://docs.veryfi.com/api/checks/delete-a-check/ + + :param document_id: ID of the check document you'd like to delete + """ + endpoint_name = f"/checks/{document_id}/" + self.client._request("DELETE", endpoint_name, {}) diff --git a/veryfi/client.py b/veryfi/client.py index 06db584..def8e0b 100644 --- a/veryfi/client.py +++ b/veryfi/client.py @@ -1,397 +1,40 @@ -import base64 -import hashlib -import hmac -import json -import os -import time -from typing import * +from veryfi.client_base import Client as ClientBase -import requests +from veryfi.a_docs import ADocs +from veryfi.bank_statements import BankStatements +from veryfi.bussines_cards import BussinesCards +from veryfi.checks import Checks +from veryfi.documents import Documents +from veryfi.w2s import W2s +from veryfi.w8s import W8s +from veryfi.w9s import W9s -from veryfi.errors import VeryfiClientError - - -class Client: - API_VERSION = "v8" - API_TIMEOUT = 30 - BASE_URL = "https://api.veryfi.com/api/" - CATEGORIES = [ - "Advertising & Marketing", - "Automotive", - "Bank Charges & Fees", - "Legal & Professional Services", - "Insurance", - "Meals & Entertainment", - "Office Supplies & Software", - "Taxes & Licenses", - "Travel", - "Rent & Lease", - "Repairs & Maintenance", - "Payroll", - "Utilities", - "Job Supplies", - "Grocery", - ] +class Client(ClientBase, ADocs, BankStatements, BussinesCards, Checks, Documents, W2s, W8s, W9s): def __init__( self, client_id, client_secret, username, api_key, - base_url=BASE_URL, - timeout=API_TIMEOUT, + base_url=ClientBase.BASE_URL, + api_version=ClientBase.API_VERSION, + timeout=ClientBase.API_TIMEOUT, ): - self.client_id = client_id - self.client_secret = client_secret - self.username = username - self.api_key = api_key - self.base_url = base_url - self.api_version = "v8" - self.versioned_url = self.base_url + self.api_version - self.timeout = timeout - self.headers = {} - self._session = requests.Session() - - def _get_headers(self) -> Dict: - """ - Prepares the headers needed for a request. - :return: Dictionary with headers - """ - final_headers = { - "User-Agent": "Python Veryfi-Python/3.4.1", - "Accept": "application/json", - "Content-Type": "application/json", - "Client-Id": self.client_id, - } - final_headers.update({"Authorization": f"apikey {self.username}:{self.api_key}"}) - - return final_headers - - def _request(self, http_verb, endpoint_name, request_arguments=None): - """ - Submit the HTTP request. - :param http_verb: HTTP Method - :param endpoint_name: Endpoint name such as 'documents', 'users', etc. - :param request_arguments: JSON payload to send to Veryfi - :return: A JSON of the response data. - """ - headers = self._get_headers() - api_url = f"{self.versioned_url}/partner{endpoint_name}" - request_arguments = request_arguments or {} - if self.client_secret: - timestamp = int(time.time() * 1000) - signature = self._generate_signature(request_arguments, timestamp=timestamp) - headers.update( - { - "X-Veryfi-Request-Timestamp": str(timestamp), - "X-Veryfi-Request-Signature": signature, - } - ) - - response = self._session.request( - http_verb, - url=api_url, - headers=headers, - data=json.dumps(request_arguments), - timeout=self.timeout, + super().__init__( + client_id=client_id, + client_secret=client_secret, + username=username, + api_key=api_key, + base_url=base_url, + api_version=api_version, + timeout=timeout, ) - - if response.status_code not in [200, 201, 202, 204]: - raise VeryfiClientError.from_response(response) - - return response.json() - - def _generate_signature(self, payload_params, timestamp): - """ - Generate unique signature for payload params. - :param payload_params: JSON params to be sent to API request - :param timestamp: Unix Long timestamp - :return: Unique signature generated using the client_secret and the payload - """ - payload = f"timestamp:{timestamp}" - for key in payload_params.keys(): - value = payload_params[key] - payload = f"{payload},{key}:{value}" - - secret_bytes = bytes(self.client_secret, "utf-8") - payload_bytes = bytes(payload, "utf-8") - tmp_signature = hmac.new(secret_bytes, msg=payload_bytes, digestmod=hashlib.sha256).digest() - base64_signature = base64.b64encode(tmp_signature).decode("utf-8").strip() - return base64_signature - - def get_documents( - self, - q: Optional[str] = None, - external_id: Optional[str] = None, - tag: Optional[str] = None, - created_gt: Optional[str] = None, - created_gte: Optional[str] = None, - created_lt: Optional[str] = None, - created_lte: Optional[str] = None, - **kwargs: Dict, - ): - """ - Get list of documents. Please refer to https://docs.veryfi.com/api/receipts-invoices/search-documents/ - :return: List of previously processed documents - """ - endpoint_name = "/documents/" - - request_params = {} - if q: - request_params["q"] = q - if external_id: - request_params["external_id"] = external_id - if tag: - request_params["tag"] = tag - if created_gt: - request_params["created__gt"] = created_gt - if created_gte: - request_params["created__gte"] = created_gte - if created_lt: - request_params["created__lt"] = created_lt - if created_lte: - request_params["created__lte"] = created_lte - request_params.update(kwargs) - if request_params: - endpoint_name += "?" + "&".join(f"{k}={v}" for k, v in request_params.items()) - - documents = self._request("GET", endpoint_name, {}) - - if "documents" in documents: - return documents["documents"] - return documents - - def get_document(self, document_id, **kwargs: Dict): - """ - Retrieve document by ID - https://docs.veryfi.com/api/receipts-invoices/get-a-document/ - :param document_id: ID of the document you'd like to retrieve - :return: Data extracted from the Document - """ - return self._request("GET", f"/documents/{document_id}/", kwargs) - - def process_document( - self, - file_path: str, - categories: Optional[List] = None, - delete_after_processing: bool = False, - **kwargs: Dict, - ): - """ - Process a document and extract all the fields from it. - https://docs.veryfi.com/api/receipts-invoices/process-a-document/ - :param file_path: Path on disk to a file to submit for data extraction - :param categories: List of categories Veryfi can use to categorize the document - :param delete_after_processing: Delete this document from Veryfi after data has been extracted - :param kwargs: Additional request parameters - - :return: Data extracted from the document - """ - if not categories: - categories = self.CATEGORIES - file_name = os.path.basename(file_path) - with open(file_path, "rb") as image_file: - base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") - request_arguments = { - "file_name": file_name, - "file_data": base64_encoded_string, - "categories": categories, - "auto_delete": delete_after_processing, - } - request_arguments.update(kwargs) - return self._request("POST", "/documents/", request_arguments) - - def process_document_url( - self, - file_url: Optional[str] = None, - categories: Optional[List[str]] = None, - delete_after_processing=False, - boost_mode: int = 0, - external_id: Optional[str] = None, - max_pages_to_process: Optional[int] = None, - file_urls: Optional[List[str]] = None, - **kwargs: Dict, - ) -> Dict: - """Process Document from url and extract all the fields from it. - https://docs.veryfi.com/api/receipts-invoices/process-a-document/ - :param file_url: Required if file_urls isn't specified. Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". - :param file_urls: Required if file_url isn't specifies. List of publicly accessible URLs to multiple files, e.g. ["https://cdn.example.com/receipt1.jpg", "https://cdn.example.com/receipt2.jpg"] - :param categories: List of categories to use when categorizing the document - :param delete_after_processing: Delete this document from Veryfi after data has been extracted - :param max_pages_to_process: When sending a long document to Veryfi for processing, this parameter controls how many pages of the document will be read and processed, starting from page 1. - :param boost_mode: Flag that tells Veryfi whether boost mode should be enabled. When set to 1, Veryfi will skip data enrichment steps, but will process the document faster. Default value for this flag is 0 - :param external_id: Optional custom document identifier. Use this if you would like to assign your own ID to documents - :param kwargs: Additional request parameters - - :return: Data extracted from the document. - """ - endpoint_name = "/documents/" - request_arguments = { - "auto_delete": delete_after_processing, - "boost_mode": boost_mode, - "categories": categories, - "external_id": external_id, - "file_url": file_url, - "file_urls": file_urls, - "max_pages_to_process": max_pages_to_process, - } - request_arguments.update(kwargs) - return self._request("POST", endpoint_name, request_arguments) - - def process_w9_document_url( - self, file_url: str, file_name: Optional[str] = None, **kwargs - ) -> Dict: - """ - Process W9 Document from url and extract all the fields from it. - https://docs.veryfi.com/api/w9s/process-a-w-9/ - - :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". - :param file_name: Optional name of file, eg. receipt.jpg - :param kwargs: Additional request parameters - - :return: Data extracted from the document. - """ - if file_name is None: - file_name = os.path.basename(file_url) - endpoint_name = "/w9s/" - request_arguments = { - "file_name": file_name, - "file_url": file_url, - } - request_arguments.update(kwargs) - return self._request("POST", endpoint_name, request_arguments) - - def process_w9_document(self, file_path: str, file_name: Optional[str] = None, **kwargs): - """ - Process W9 Document from url and extract all the fields from it. - https://docs.veryfi.com/api/w9s/process-a-w-9/ - - :param file_path: Path on disk to a file to submit for data extraction - :param file_name: Optional name of file, eg. receipt.jpg - :param kwargs: Additional request parameters - - :return: Data extracted from the document. - """ - endpoint_name = "/w9s/" - if file_name is None: - file_name = os.path.basename(file_path) - with open(file_path, "rb") as image_file: - base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") - request_arguments = { - "file_name": file_name, - "file_data": base64_encoded_string, - } - request_arguments.update(kwargs) - document = self._request("POST", endpoint_name, request_arguments) - return document - - def delete_document(self, document_id): - """ - Delete Document from Veryfi - https://docs.veryfi.com/api/receipts-invoices/delete-a-document/ - :param document_id: ID of the document you'd like to delete - """ - self._request("DELETE", f"/documents/{document_id}/", {"id": document_id}) - - def update_document(self, document_id: int, **kwargs) -> Dict: - """ - Update data for a previously processed document, including almost any field like `vendor`, `date`, `notes` and etc. - https://docs.veryfi.com/api/receipts-invoices/update-a-document/ - - ```veryfi_client.update_document(id, date="2021-01-01", notes="look what I did")``` - - :param document_id: ID of the document you'd like to update - :param kwargs: fields to update - - :return: A document json with updated fields, if fields are writable. Otherwise a document with unchanged fields. - """ - return self._request("PUT", f"/documents/{document_id}/", kwargs) - - def get_line_items(self, document_id): - """ - Retrieve all line items for a document. - https://docs.veryfi.com/api/receipts-invoices/get-document-line-items/ - :param document_id: ID of the document you'd like to retrieve - :return: List of line items extracted from the document - """ - return self._request("GET", f"/documents/{document_id}/line-items/") - - def get_line_item(self, document_id, line_item_id): - """ - Retrieve a line item for existing document by ID. - https://docs.veryfi.com/api/receipts-invoices/get-a-line-item/ - :param document_id: ID of the document you'd like to retrieve - :param line_item_id: ID of the line item you'd like to retrieve - :return: Line item extracted from the document - """ - return self._request("GET", f"/documents/{document_id}/line-items/{line_item_id}") - - def add_line_item(self, document_id: int, payload: Dict) -> Dict: - """ - Add a new line item on an existing document. - https://docs.veryfi.com/api/receipts-invoices/create-a-line-item/ - :param document_id: ID of the document you'd like to update - :param payload: line item object to add - :return: Added line item data - """ - return self._request("POST", f"/documents/{document_id}/line-items/", payload) - - def update_line_item(self, document_id: int, line_item_id: int, payload: Dict) -> Dict: - """ - Update an existing line item on an existing document. - https://docs.veryfi.com/api/receipts-invoices/update-a-line-item/ - :param document_id: ID of the document you'd like to update - :param line_item_id: ID of the line item you'd like to update - :param payload: line item object to update - - :return: Line item data with updated fields, if fields are writable. Otherwise line item data with unchanged fields. - """ - return self._request("PUT", f"/documents/{document_id}/line-items/{line_item_id}", payload) - - def delete_line_items(self, document_id): - """ - Delete all line items on an existing document. - https://docs.veryfi.com/api/receipts-invoices/delete-all-document-line-items/ - :param document_id: ID of the document you'd like to delete - """ - self._request("DELETE", f"/documents/{document_id}/line-items/") - - def delete_line_item(self, document_id, line_item_id): - """ - Delete an existing line item on an existing document. - https://docs.veryfi.com/api/receipts-invoices/delete-a-line-item/ - - :param document_id: ID of the document you'd like to delete - :param line_item_id: ID of the line item you'd like to delete - """ - self._request("DELETE", f"/documents/{document_id}/line-items/{line_item_id}") - - def add_tag(self, document_id, tag_name): - """ - Add a new tag on an existing document. - https://docs.veryfi.com/api/receipts-invoices/add-a-tag-to-a-document/ - :param document_id: ID of the document you'd like to update - :param tag_name: name of the new tag - :return: Added tag data - """ - return self._request("PUT", f"/documents/{document_id}/tags/", {"name": tag_name}) - - def replace_tags(self, document_id, tags): - """ - Replace multiple tags on an existing document. - https://docs.veryfi.com/api/receipts-invoices/update-a-document/ - :param document_id: ID of the document you'd like to update - :param tags: array of strings - :return: Added tags data - """ - return self._request("PUT", f"/documents/{document_id}/", {"tags": tags}) - - def add_tags(self, document_id, tags): - """ - Add multiple tags on an existing document. - :param document_id: ID of the document you'd like to update - :param tags: array of strings - :return: Added tags data - """ - return self._request("POST", f"/documents/{document_id}/tags/", {"tags": tags}) + ADocs.__init__(self, super()) + BankStatements.__init__(self, super()) + BussinesCards.__init__(self, super()) + Checks.__init__(self, super()) + Documents.__init__(self, super()) + W2s.__init__(self, super()) + W8s.__init__(self, super()) + W9s.__init__(self, super()) diff --git a/veryfi/client_base.py b/veryfi/client_base.py new file mode 100644 index 0000000..2408cdb --- /dev/null +++ b/veryfi/client_base.py @@ -0,0 +1,108 @@ +import requests +import base64 +import hashlib +import hmac +import json +import time +from typing import * +from urllib.parse import urlencode + +from veryfi.errors import VeryfiClientError + + +class Client: + + API_VERSION = "v8" + API_TIMEOUT = 30 + BASE_URL = "https://api.veryfi.com/api/" + + def __init__( + self, + client_id, + client_secret, + username, + api_key, + base_url=BASE_URL, + api_version=API_VERSION, + timeout=API_TIMEOUT, + ): + self.client_id = client_id + self.client_secret = client_secret + self.username = username + self.api_key = api_key + self.base_url = base_url + self.api_version = api_version + self.versioned_url = self.base_url + self.api_version + self.timeout = timeout + self.headers = {} + self._session = requests.Session() + + def _get_headers(self) -> Dict: + """ + Prepares the headers needed for a request. + :return: Dictionary with headers + """ + final_headers = { + "User-Agent": "Python Veryfi-Python/3.4.1", + "Accept": "application/json", + "Content-Type": "application/json", + "Client-Id": self.client_id, + } + + final_headers.update({"Authorization": f"apikey {self.username}:{self.api_key}"}) + + return final_headers + + def _request(self, http_verb, endpoint_name, request_arguments=None, query_params=None): + """ + Submit the HTTP request. + :param http_verb: HTTP Method + :param endpoint_name: Endpoint name such as 'documents', 'users', etc. + :param request_arguments: JSON payload to send to Veryfi + :return: A JSON of the response data. + """ + headers = self._get_headers() + api_url = "{0}/partner{1}".format(self.versioned_url, endpoint_name) + request_arguments = request_arguments or {} + + if self.client_secret: + timestamp = int(time.time() * 1000) + signature = self._generate_signature(request_arguments, timestamp=timestamp) + headers.update( + { + "X-Veryfi-Request-Timestamp": str(timestamp), + "X-Veryfi-Request-Signature": signature, + } + ) + + response = self._session.request( + http_verb, + url=api_url, + params=query_params or None, + headers=headers, + data=json.dumps(request_arguments), + timeout=self.timeout, + ) + + if response.status_code not in [200, 201, 202, 204]: + raise VeryfiClientError.from_response(response) + + return response.json() + + def _generate_signature(self, payload_params, timestamp): + """ + Generate unique signature for payload params. + :param payload_params: JSON params to be sent to API request + :param timestamp: Unix Long timestamp + :return: Unique signature generated using the client_secret and the payload + """ + payload = f"timestamp:{timestamp}" + for key in payload_params.keys(): + value = payload_params[key] + payload = f"{payload},{key}:{value}" + + secret_bytes = bytes(self.client_secret, "utf-8") + payload_bytes = bytes(payload, "utf-8") + tmp_signature = hmac.new(secret_bytes, msg=payload_bytes, digestmod=hashlib.sha256).digest() + base64_signature = base64.b64encode(tmp_signature).decode("utf-8").strip() + return base64_signature diff --git a/veryfi/documents.py b/veryfi/documents.py new file mode 100644 index 0000000..e1e7085 --- /dev/null +++ b/veryfi/documents.py @@ -0,0 +1,195 @@ +import os +import base64 +from typing import * + +from veryfi._documents.line_items import LineItems +from veryfi._documents.tags import Tags +from veryfi._documents.pdf_split import PDFSplit +from veryfi.client_base import Client + + +class Documents(Tags, LineItems, PDFSplit): + + DEFAULT_CATEGORIES = [ + "Advertising & Marketing", + "Automotive", + "Bank Charges & Fees", + "Legal & Professional Services", + "Insurance", + "Meals & Entertainment", + "Office Supplies & Software", + "Taxes & Licenses", + "Travel", + "Rent & Lease", + "Repairs & Maintenance", + "Payroll", + "Utilities", + "Job Supplies", + "Grocery", + ] + + def __init__(self, client: Client): + self.client = client + LineItems.__init__(self, self.client) + Tags.__init__(self, self.client) + PDFSplit.__init__(self, self.client) + + def get_documents( + self, + q: Optional[str] = None, + external_id: Optional[str] = None, + tag: Optional[str] = None, + created_gt: Optional[str] = None, + created_gte: Optional[str] = None, + created_lt: Optional[str] = None, + created_lte: Optional[str] = None, + **kwargs, + ) -> Dict: + """ + Get list of documents. + https://docs.veryfi.com/api/receipts-invoices/search-documents/ + + :param q: Search query + :param external_id: Search by external ID + :param tag: Search by tag + :param created_gt: Search by created date greater than + :param created_gte: Search by created date greater than or equal to + :param created_lt: Search by created date less than + :param created_lte: Search by created date less than or equal to + :param kwargs: Additional query parameters + :return: List of previously processed documents + """ + query_params = {} + if q: + query_params["q"] = q + if external_id: + query_params["external_id"] = external_id + if tag: + query_params["tag"] = tag + if created_gt: + query_params["created__gt"] = created_gt + if created_gte: + query_params["created__gte"] = created_gte + if created_lt: + query_params["created__lt"] = created_lt + if created_lte: + query_params["created__lte"] = created_lte + query_params.update(kwargs) + + endpoint_name = "/documents/" + return self.client._request("GET", endpoint_name, {}, query_params) + + def get_document(self, document_id, **kwargs) -> Dict: + """ + Retrieve document by ID + https://docs.veryfi.com/api/receipts-invoices/get-a-document/ + + :param document_id: ID of the document you'd like to retrieve + :param kwargs: Additional query parameters + :return: Data extracted from the Document + """ + endpoint_name = f"/documents/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def process_document( + self, + file_path: str, + categories: Optional[List] = None, + delete_after_processing: bool = False, + **kwargs, + ) -> Dict: + """ + Process a document and extract all the fields from it. + https://docs.veryfi.com/api/receipts-invoices/process-a-document/ + + :param file_path: Path on disk to a file to submit for data extraction + :param categories: List of categories Veryfi can use to categorize the document + :param delete_after_processing: Delete this document from Veryfi after data has been extracted + :param kwargs: Additional body parameters + :return: Data extracted from the document + """ + if not categories: + categories = self.DEFAULT_CATEGORIES + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + "categories": categories, + "auto_delete": delete_after_processing, + } + request_arguments.update(kwargs) + return self.client._request("POST", "/documents/", request_arguments) + + def process_document_url( + self, + file_url: Optional[str] = None, + categories: Optional[List[str]] = None, + delete_after_processing: bool = False, + boost_mode: bool = False, + external_id: Optional[str] = None, + max_pages_to_process: Optional[int] = None, + file_urls: Optional[List[str]] = None, + **kwargs, + ) -> Dict: + """Process Document from url and extract all the fields from it. + https://docs.veryfi.com/api/receipts-invoices/process-a-document/ + + :param file_url: Required if file_urls isn't specified. Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_urls: Required if file_url isn't specifies. List of publicly accessible URLs to multiple files, e.g. ["https://cdn.example.com/receipt1.jpg", "https://cdn.example.com/receipt2.jpg"] + :param categories: List of categories to use when categorizing the document + :param delete_after_processing: Delete this document from Veryfi after data has been extracted + :param max_pages_to_process: When sending a long document to Veryfi for processing, this parameter controls how many pages of the document will be read and processed, starting from page 1. + :param boost_mode: Flag that tells Veryfi whether boost mode should be enabled. When set to 1, Veryfi will skip data enrichment steps, but will process the document faster. Default value for this flag is 0 + :param external_id: Optional custom document identifier. Use this if you would like to assign your own ID to documents + :param kwargs: Additional body parameters + :return: Data extracted from the document. + """ + endpoint_name = "/documents/" + request_arguments = { + "auto_delete": delete_after_processing, + "boost_mode": boost_mode, + "categories": categories, + "external_id": external_id, + "file_url": file_url, + "file_urls": file_urls, + "max_pages_to_process": max_pages_to_process, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_documents_bulk(self, file_urls: List[str]) -> List[int]: + """ + Process multiple documents from urls and extract all the fields from it. + If you want to use this endpoint, please contact support@veryfi.com first. Veryfi's Bulk upload allows you to process multiple Documents. + https://docs.veryfi.com/api/receipts-invoices/bulk-process-multiple-documents/ + + :param file_urls: List of publicly accessible URLs to multiple files, e.g. ["https://cdn.example.com/receipt1.jpg", "https://cdn.example.com/receipt2.jpg"] + :return: List of document IDs being processed + """ + endpoint_name = "/documents/bulk/" + request_arguments = {"file_urls": file_urls} + return self.client._request("POST", endpoint_name, request_arguments) + + def delete_document(self, document_id: int): + """ + Delete Document from Veryfi + https://docs.veryfi.com/api/receipts-invoices/delete-a-document/ + + :param document_id: ID of the document you'd like to delete + """ + self.client._request("DELETE", f"/documents/{document_id}/", {"id": document_id}) + + def update_document(self, document_id: int, **kwargs) -> Dict: + """ + Update data for a previously processed document, including almost any field like `vendor`, `date`, `notes` and etc. + https://docs.veryfi.com/api/receipts-invoices/update-a-document/ + + ```veryfi_client.update_document(id, date="2021-01-01", notes="look what I did")``` + + :param document_id: ID of the document you'd like to update + :param kwargs: fields to update + :return: A document json with updated fields, if fields are writable. Otherwise a document with unchanged fields. + """ + return self.client._request("PUT", f"/documents/{document_id}/", kwargs) diff --git a/veryfi/errors.py b/veryfi/errors.py index 96a3510..92abbe1 100644 --- a/veryfi/errors.py +++ b/veryfi/errors.py @@ -48,6 +48,10 @@ class InternalError(VeryfiClientError): pass +class ServiceUnavailable(VeryfiClientError): + pass + + _error_map = { 400: BadRequest, 404: ResourceNotFound, @@ -55,4 +59,5 @@ class InternalError(VeryfiClientError): 405: UnexpectedHTTPMethod, 409: AccessLimitReached, 500: InternalError, + 503: ServiceUnavailable, } diff --git a/veryfi/w2s.py b/veryfi/w2s.py new file mode 100644 index 0000000..89bdb52 --- /dev/null +++ b/veryfi/w2s.py @@ -0,0 +1,108 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client +from veryfi._w2s.w2_split import W2Split + + +class W2s(W2Split): + def __init__(self, client: Client): + self.client = client + + def process_w2_document_url( + self, file_url: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: + """ + Process W2 Document from url and extract all the fields from it. + https://docs.veryfi.com/api/w2s/process-a-w-2/ + + :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the w2. + """ + if file_name is None: + file_name = os.path.basename(file_url) + endpoint_name = "/w2s/" + request_arguments = { + "file_name": file_name, + "file_url": file_url, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_w2_document(self, file_path: str, file_name: Optional[str] = None, **kwargs): + """ + Process W2 Document from url and extract all the fields from it. + https://docs.veryfi.com/api/w2s/process-a-w-2/ + + :param file_path: Path on disk to a file to submit for data extraction + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the w2. + """ + endpoint_name = "/w2s/" + if file_name is None: + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def get_w2(self, document_id: int, **kwargs) -> Dict: + """ + Get W-2 endpoint allows you to retrieve a previously processed W-2 + https://docs.veryfi.com/api/w2s/get-a-w-2/ + + :param document_id: The unique identifier of the document. + :param kwargs: Additional query parameters + :return: Document Data + """ + endpoint_name = f"/w2s/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def get_w2s( + self, + created_date_gt: Optional[str] = None, + created_date_gte: Optional[str] = None, + created_date_lt: Optional[str] = None, + created_date_lte: Optional[str] = None, + **kwargs: Dict, + ): + """ + Get list of w2s documents. + https://docs.veryfi.com/api/w2s/get-w-2-s/ + + :param created_date__gt: Search for w2s documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__gte: Search for w2s documents with a created date greater than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__lt: Search for w2s documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param created_date__lte: Search for w2s documents with a created date less than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param kwargs: Additional query parameters + :return: List of previously processed documents + """ + query_params = {} + if created_date_gt: + query_params["created_date__gt"] = created_date_gt + if created_date_gte: + query_params["created_date__gte"] = created_date_gte + if created_date_lt: + query_params["created_date__lt"] = created_date_lt + if created_date_lte: + query_params["created_date__lte"] = created_date_lte + query_params.update(kwargs) + + endpoint_name = "/w2s/" + return self.client._request("GET", endpoint_name, {}, query_params) + + def delete_w2(self, document_id: int): + """ + Delete a w2 document. + https://docs.veryfi.com/api/w2s/delete-a-w-2/ + """ + endpoint_name = f"/w2s/{document_id}/" + self.client._request("DELETE", endpoint_name, {}) diff --git a/veryfi/w8s.py b/veryfi/w8s.py new file mode 100644 index 0000000..a0fbf04 --- /dev/null +++ b/veryfi/w8s.py @@ -0,0 +1,109 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class W8s: + def __init__(self, client: Client): + self.client = client + + def process_w8_document_url( + self, file_url: str, file_name: Optional[str] = None, **kwargs: Dict + ) -> Dict: + """ + Process W2 Document from url and extract all the fields from it. + https://docs.veryfi.com/api/w-8ben-e/process-a-w-8-ben-e/ + + :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the w8. + """ + if file_name is None: + file_name = os.path.basename(file_url) + endpoint_name = "/w-8ben-e/" + request_arguments = { + "file_name": file_name, + "file_url": file_url, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_w8_document(self, file_path: str, file_name: Optional[str] = None, **kwargs): + """ + Process W8 Document from url and extract all the fields from it. + https://docs.veryfi.com/api/w-8ben-e/process-a-w-8-ben-e/ + + :param file_path: Path on disk to a file to submit for data extraction + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional body parameters + :return: Data extracted from the w8. + """ + endpoint_name = "/w-8ben-e/" + if file_name is None: + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def get_w8(self, document_id: int, **kwargs) -> Dict: + """ + Get W-8 endpoint allows you to retrieve a previously processed W-8 + https://docs.veryfi.com/api/w-8ben-e/get-a-w-8-ben-e/ + + :param document_id: The unique identifier of the document. + :param kwargs: Additional query parameters + :return: Document Data + """ + endpoint_name = f"/w-8ben-e/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def get_w8s( + self, + created_date_gt: Optional[str] = None, + created_date_gte: Optional[str] = None, + created_date_lt: Optional[str] = None, + created_date_lte: Optional[str] = None, + **kwargs: Dict, + ): + """ + Get list of w8s documents + https://docs.veryfi.com/api/w-8ben-e/get-w-8-ben-es/ + + :param created_date__gt: Search for w8s documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__gte: Search for w8s documents with a created date greater than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__lt: Search for w8s documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param created_date__lte: Search for w8s documents with a created date less than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param kwargs: Additional query parameters + :return: List of previously processed documents + """ + query_params = {} + if created_date_gt: + query_params["created_date__gt"] = created_date_gt + if created_date_gte: + query_params["created_date__gte"] = created_date_gte + if created_date_lt: + query_params["created_date__lt"] = created_date_lt + if created_date_lte: + query_params["created_date__lte"] = created_date_lte + query_params.update(kwargs) + + endpoint_name = "/w-8ben-e/" + return self.client._request("GET", endpoint_name, {}, query_params) + + def delete_w8(self, document_id): + """ + Delete Document from Veryfi + https://docs.veryfi.com/api/w-8ben-e/delete-a-w-8-ben-e/ + + :param document_id: ID of the document you'd like to delete + """ + endpoint_name = f"/w-8ben-e/{document_id}/" + self.client._request("DELETE", endpoint_name, {}) diff --git a/veryfi/w9s.py b/veryfi/w9s.py new file mode 100644 index 0000000..9bfc1cc --- /dev/null +++ b/veryfi/w9s.py @@ -0,0 +1,110 @@ +import os +import base64 +from typing import * + +from veryfi.client_base import Client + + +class W9s: + def __init__(self, client: Client): + self.client = client + + def process_w9_document_url( + self, file_url: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: + """ + Process W9 Document from url and extract all the fields from it. + https://docs.veryfi.com/api/w9s/process-a-w-9/ + + :param file_url: Publicly accessible URL to a file, e.g. "https://cdn.example.com/receipt.jpg". + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional request parameters + :return: Data extracted from the document. + """ + if file_name is None: + file_name = os.path.basename(file_url) + endpoint_name = "/w9s/" + request_arguments = { + "file_name": file_name, + "file_url": file_url, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def process_w9_document(self, file_path: str, file_name: Optional[str] = None, **kwargs): + """ + Process W9 Document from url and extract all the fields from it. + https://docs.veryfi.com/api/w9s/process-a-w-9/ + + :param file_path: Path on disk to a file to submit for data extraction + :param file_name: Optional name of file, eg. receipt.jpg + :param kwargs: Additional request parameters + :return: Data extracted from the document. + """ + endpoint_name = "/w9s/" + if file_name is None: + file_name = os.path.basename(file_path) + with open(file_path, "rb") as image_file: + base64_encoded_string = base64.b64encode(image_file.read()).decode("utf-8") + request_arguments = { + "file_name": file_name, + "file_data": base64_encoded_string, + } + request_arguments.update(kwargs) + return self.client._request("POST", endpoint_name, request_arguments) + + def get_w9(self, document_id: int, **kwargs) -> Dict: + """ + Get a W-9 endpoint allows you to retrieve a previously processed W-9 + https://docs.veryfi.com/api/w9s/get-a-w-9/ + + :param document_id: The unique identifier of the document. + :param bounding_boxes: A field used to determine whether or not to return bounding_box and bounding_region for extracted fields in the Document response. + :param confidence_details: A field used to determine whether or not to return the score and ocr_score fields in the Document response. + :return: Document Data + """ + endpoint_name = f"/w9s/{document_id}/" + return self.client._request("GET", endpoint_name, {}, kwargs) + + def get_w9s( + self, + created_date_gt: Optional[str] = None, + created_date_gte: Optional[str] = None, + created_date_lt: Optional[str] = None, + created_date_lte: Optional[str] = None, + **kwargs: Dict, + ): + """ + Get list of w9s documents + https://docs.veryfi.com/api/w9s/get-w-9-s/ + + :param created_date__gt: Search for w9s documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__gte: Search for w9s documents with a created date greater than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__gt and created_date__gte in a single request. + :param created_date__lt: Search for w9s documents with a created date greater than this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param created_date__lte: Search for w9s documents with a created date less than or equal to this one. Format YYYY-MM-DD+HH:MM:SS. Don't send both created_date__lt and created_date__lte in a single request. + :param kwargs: Additional request parameters + :return: List of previously processed documents + """ + query_params = {} + if created_date_gt: + query_params["created_date__gt"] = created_date_gt + if created_date_gte: + query_params["created_date__gte"] = created_date_gte + if created_date_lt: + query_params["created_date__lt"] = created_date_lt + if created_date_lte: + query_params["created_date__lte"] = created_date_lte + query_params.update(kwargs) + + endpoint_name = "/w9s/" + return self.client._request("GET", endpoint_name, {}, query_params) + + def delete_w9(self, document_id: int): + """ + Delete a W-9 document. + https://docs.veryfi.com/api/w9s/delete-a-w-9/ + + :param document_id: The unique identifier of the document. + """ + endpoint_name = f"/w9s/{document_id}/" + self.client._request("DELETE", endpoint_name, {}) From 6c03408eec8f873f383bc7ef167bcc7ae754bf7d Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 19 Mar 2025 11:35:29 -0500 Subject: [PATCH 4/8] lower coverage --- tox.ini | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/tox.ini b/tox.ini index 46bd383..cc6481a 100644 --- a/tox.ini +++ b/tox.ini @@ -43,7 +43,7 @@ source = veryfi [coverage:report] -fail_under = 82 +fail_under = 75 precision = 2 skip_covered = True skip_empty = True From b050686c13a02e18ccfd7ca9e70a69885b9613d0 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 19 Mar 2025 11:39:13 -0500 Subject: [PATCH 5/8] black format --- tests/test_checks.py | 9 +++------ tests/test_w2s.py | 16 ++++++++-------- tests/test_w8s.py | 2 +- 3 files changed, 12 insertions(+), 15 deletions(-) diff --git a/tests/test_checks.py b/tests/test_checks.py index b7a9fcd..bf3300c 100644 --- a/tests/test_checks.py +++ b/tests/test_checks.py @@ -27,16 +27,13 @@ "receiver_name": "Dmitry Birulia", "is_signed": True, "is_endorsed": None, - "endorsement": { - "is_signed": None, - "is_mobile_or_remote_deposit_only": None - }, + "endorsement": {"is_signed": None, "is_mobile_or_remote_deposit_only": None}, "micr": { "routing_number": "031923284", "account_number": "8765129397", "serial_number": None, - "raw": "C0118408359C A031923284A 8765129397C" - } + "raw": "C0118408359C A031923284A 8765129397C", + }, } diff --git a/tests/test_w2s.py b/tests/test_w2s.py index b053eac..d5249dd 100644 --- a/tests/test_w2s.py +++ b/tests/test_w2s.py @@ -48,16 +48,16 @@ "is_13c": False, "states": [ { - "state": "PAL", - "employer_state_id": "1235", - "state_wages_tips": 50000, - "state_income_tax": 1535, - "local_wages_tips": 50000, - "local_income_tax": 750, - "locality_name": "MU" + "state": "PAL", + "employer_state_id": "1235", + "state_wages_tips": 50000, + "state_income_tax": 1535, + "local_wages_tips": 50000, + "local_income_tax": 750, + "locality_name": "MU", } ], - "field_14_other": [] + "field_14_other": [], } diff --git a/tests/test_w8s.py b/tests/test_w8s.py index 0fc6a4b..019158c 100644 --- a/tests/test_w8s.py +++ b/tests/test_w8s.py @@ -160,7 +160,7 @@ "passive_nffe_owners": [], "signature_date": None, "signature_name": None, - "signed": True + "signed": True, } From a27a1b6336279b5b5e2511eb6784cdbff018836b Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 19 Mar 2025 13:51:20 -0500 Subject: [PATCH 6/8] improve imports --- tests/test_errors.py | 4 ++-- veryfi/_documents/line_items.py | 10 +++++----- veryfi/_documents/pdf_split.py | 2 +- veryfi/_documents/tags.py | 1 - veryfi/_w2s/w2_split.py | 2 +- veryfi/a_docs.py | 2 +- veryfi/bank_statements.py | 4 ++-- veryfi/bussines_cards.py | 8 ++++---- veryfi/checks.py | 6 +++--- veryfi/client.py | 14 +++++++------- veryfi/client_base.py | 29 +++++++++++++++++------------ veryfi/documents.py | 4 ++-- veryfi/w2s.py | 6 ++++-- veryfi/w8s.py | 10 ++++++---- veryfi/w9s.py | 8 +++++--- 15 files changed, 60 insertions(+), 50 deletions(-) diff --git a/tests/test_errors.py b/tests/test_errors.py index 1bd22c7..04628a1 100644 --- a/tests/test_errors.py +++ b/tests/test_errors.py @@ -1,8 +1,8 @@ import responses import requests -from veryfi.errors import * -from veryfi import * +from veryfi.errors import BadRequest, VeryfiClientError +from veryfi import Client import pytest diff --git a/veryfi/_documents/line_items.py b/veryfi/_documents/line_items.py index d63be81..8ed9545 100644 --- a/veryfi/_documents/line_items.py +++ b/veryfi/_documents/line_items.py @@ -1,4 +1,4 @@ -from typing import * +from typing import Dict from veryfi.client_base import Client @@ -7,7 +7,7 @@ class LineItems: def __init__(self, client: Client): self.client = client - def get_line_items(self, document_id): + def get_line_items(self, document_id: int): """ Retrieve all line items for a document. https://docs.veryfi.com/api/receipts-invoices/get-document-line-items/ @@ -17,7 +17,7 @@ def get_line_items(self, document_id): """ return self.client._request("GET", f"/documents/{document_id}/line-items/") - def get_line_item(self, document_id, line_item_id): + def get_line_item(self, document_id: int, line_item_id: int): """ Retrieve a line item for existing document by ID. https://docs.veryfi.com/api/receipts-invoices/get-a-line-item/ @@ -53,7 +53,7 @@ def update_line_item(self, document_id: int, line_item_id: int, payload: Dict) - "PUT", f"/documents/{document_id}/line-items/{line_item_id}", payload ) - def delete_line_items(self, document_id): + def delete_line_items(self, document_id: int): """ Delete all line items on an existing document. https://docs.veryfi.com/api/receipts-invoices/delete-all-document-line-items/ @@ -62,7 +62,7 @@ def delete_line_items(self, document_id): """ self.client._request("DELETE", f"/documents/{document_id}/line-items/") - def delete_line_item(self, document_id, line_item_id): + def delete_line_item(self, document_id: int, line_item_id: int): """ Delete an existing line item on an existing document. https://docs.veryfi.com/api/receipts-invoices/delete-a-line-item/ diff --git a/veryfi/_documents/pdf_split.py b/veryfi/_documents/pdf_split.py index 652b316..5d79ba9 100644 --- a/veryfi/_documents/pdf_split.py +++ b/veryfi/_documents/pdf_split.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, List, Optional from veryfi.client_base import Client diff --git a/veryfi/_documents/tags.py b/veryfi/_documents/tags.py index 0cdfb6e..2c912c2 100644 --- a/veryfi/_documents/tags.py +++ b/veryfi/_documents/tags.py @@ -1,4 +1,3 @@ -from typing import * from veryfi.client_base import Client diff --git a/veryfi/_w2s/w2_split.py b/veryfi/_w2s/w2_split.py index bd6155f..241136d 100644 --- a/veryfi/_w2s/w2_split.py +++ b/veryfi/_w2s/w2_split.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, List, Optional from veryfi.client_base import Client diff --git a/veryfi/a_docs.py b/veryfi/a_docs.py index f796938..52fefb6 100644 --- a/veryfi/a_docs.py +++ b/veryfi/a_docs.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, List, Optional from veryfi.client_base import Client diff --git a/veryfi/bank_statements.py b/veryfi/bank_statements.py index 277389d..e44f039 100644 --- a/veryfi/bank_statements.py +++ b/veryfi/bank_statements.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, Optional from veryfi.client_base import Client @@ -33,7 +33,7 @@ def process_bank_statement_document_url( def process_bank_statement_document( self, file_path: str, file_name: Optional[str] = None, **kwargs - ): + ) -> Dict: """ Process bank statement document from url and extract all the fields from it. https://docs.veryfi.com/api/bank-statements/process-a-bank-statement/ diff --git a/veryfi/bussines_cards.py b/veryfi/bussines_cards.py index f1291fa..61e5d03 100644 --- a/veryfi/bussines_cards.py +++ b/veryfi/bussines_cards.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, Optional from veryfi.client_base import Client @@ -33,7 +33,7 @@ def process_bussines_card_document_url( def process_bussines_card_document( self, file_path: str, file_name: Optional[str] = None, **kwargs - ): + ) -> Dict: """ Process bussiness card from url and extract all the fields from it. https://docs.veryfi.com/api/business-cards/process-a-business-card/ @@ -55,7 +55,7 @@ def process_bussines_card_document( request_arguments.update(kwargs) return self.client._request("POST", endpoint_name, request_arguments) - def get_business_cards(self, **kwargs: Dict): + def get_business_cards(self, **kwargs): """ Get list of business card documents. https://docs.veryfi.com/api/business-cards/get-business-cards/ @@ -66,7 +66,7 @@ def get_business_cards(self, **kwargs: Dict): endpoint_name = "/business-cards/" return self.client._request("GET", endpoint_name, {}, kwargs) - def get_business_card(self, document_id: int, **kwargs: Dict): + def get_business_card(self, document_id: int, **kwargs) -> Dict: """ Get a business card document. https://docs.veryfi.com/api/business-cards/get-a-business-card/ diff --git a/veryfi/checks.py b/veryfi/checks.py index ff2e16d..b901cba 100644 --- a/veryfi/checks.py +++ b/veryfi/checks.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, List, Optional from veryfi.client_base import Client @@ -16,7 +16,7 @@ def get_checks( created_date__lt: Optional[str] = None, created_date__lte: Optional[str] = None, **kwargs, - ) -> List[Dict]: + ): """ Get list of checks https://docs.veryfi.com/api/checks/get-checks/ @@ -59,7 +59,7 @@ def process_check( self, file_path: str, **kwargs, - ): + ) -> Dict: """ Process a check document and extract all the fields from it https://docs.veryfi.com/api/checks/process-a-check/ diff --git a/veryfi/client.py b/veryfi/client.py index def8e0b..d1d01b1 100644 --- a/veryfi/client.py +++ b/veryfi/client.py @@ -13,13 +13,13 @@ class Client(ClientBase, ADocs, BankStatements, BussinesCards, Checks, Documents, W2s, W8s, W9s): def __init__( self, - client_id, - client_secret, - username, - api_key, - base_url=ClientBase.BASE_URL, - api_version=ClientBase.API_VERSION, - timeout=ClientBase.API_TIMEOUT, + client_id: str, + client_secret: str, + username: str, + api_key: str, + base_url: str = ClientBase.BASE_URL, + api_version: str = ClientBase.API_VERSION, + timeout: int = ClientBase.API_TIMEOUT, ): super().__init__( client_id=client_id, diff --git a/veryfi/client_base.py b/veryfi/client_base.py index 2408cdb..c81a724 100644 --- a/veryfi/client_base.py +++ b/veryfi/client_base.py @@ -4,8 +4,7 @@ import hmac import json import time -from typing import * -from urllib.parse import urlencode +from typing import Dict, Optional from veryfi.errors import VeryfiClientError @@ -18,13 +17,13 @@ class Client: def __init__( self, - client_id, - client_secret, - username, - api_key, - base_url=BASE_URL, - api_version=API_VERSION, - timeout=API_TIMEOUT, + client_id: str, + client_secret: str, + username: str, + api_key: str, + base_url: str = BASE_URL, + api_version: str = API_VERSION, + timeout: int = API_TIMEOUT, ): self.client_id = client_id self.client_secret = client_secret @@ -53,7 +52,13 @@ def _get_headers(self) -> Dict: return final_headers - def _request(self, http_verb, endpoint_name, request_arguments=None, query_params=None): + def _request( + self, + http_verb: str, + endpoint_name: str, + request_arguments: Optional[Dict] = None, + query_params: Optional[Dict] = None, + ): """ Submit the HTTP request. :param http_verb: HTTP Method @@ -62,7 +67,7 @@ def _request(self, http_verb, endpoint_name, request_arguments=None, query_param :return: A JSON of the response data. """ headers = self._get_headers() - api_url = "{0}/partner{1}".format(self.versioned_url, endpoint_name) + api_url = f"{self.versioned_url}/partner{endpoint_name}" request_arguments = request_arguments or {} if self.client_secret: @@ -89,7 +94,7 @@ def _request(self, http_verb, endpoint_name, request_arguments=None, query_param return response.json() - def _generate_signature(self, payload_params, timestamp): + def _generate_signature(self, payload_params: Dict, timestamp: int) -> str: """ Generate unique signature for payload params. :param payload_params: JSON params to be sent to API request diff --git a/veryfi/documents.py b/veryfi/documents.py index e1e7085..2c76335 100644 --- a/veryfi/documents.py +++ b/veryfi/documents.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, List, Optional from veryfi._documents.line_items import LineItems from veryfi._documents.tags import Tags @@ -79,7 +79,7 @@ def get_documents( endpoint_name = "/documents/" return self.client._request("GET", endpoint_name, {}, query_params) - def get_document(self, document_id, **kwargs) -> Dict: + def get_document(self, document_id: int, **kwargs) -> Dict: """ Retrieve document by ID https://docs.veryfi.com/api/receipts-invoices/get-a-document/ diff --git a/veryfi/w2s.py b/veryfi/w2s.py index 89bdb52..27453e2 100644 --- a/veryfi/w2s.py +++ b/veryfi/w2s.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, Optional from veryfi.client_base import Client from veryfi._w2s.w2_split import W2Split @@ -32,7 +32,9 @@ def process_w2_document_url( request_arguments.update(kwargs) return self.client._request("POST", endpoint_name, request_arguments) - def process_w2_document(self, file_path: str, file_name: Optional[str] = None, **kwargs): + def process_w2_document( + self, file_path: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: """ Process W2 Document from url and extract all the fields from it. https://docs.veryfi.com/api/w2s/process-a-w-2/ diff --git a/veryfi/w8s.py b/veryfi/w8s.py index a0fbf04..7b99616 100644 --- a/veryfi/w8s.py +++ b/veryfi/w8s.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, Optional from veryfi.client_base import Client @@ -10,7 +10,7 @@ def __init__(self, client: Client): self.client = client def process_w8_document_url( - self, file_url: str, file_name: Optional[str] = None, **kwargs: Dict + self, file_url: str, file_name: Optional[str] = None, **kwargs ) -> Dict: """ Process W2 Document from url and extract all the fields from it. @@ -31,7 +31,9 @@ def process_w8_document_url( request_arguments.update(kwargs) return self.client._request("POST", endpoint_name, request_arguments) - def process_w8_document(self, file_path: str, file_name: Optional[str] = None, **kwargs): + def process_w8_document( + self, file_path: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: """ Process W8 Document from url and extract all the fields from it. https://docs.veryfi.com/api/w-8ben-e/process-a-w-8-ben-e/ @@ -71,7 +73,7 @@ def get_w8s( created_date_gte: Optional[str] = None, created_date_lt: Optional[str] = None, created_date_lte: Optional[str] = None, - **kwargs: Dict, + **kwargs, ): """ Get list of w8s documents diff --git a/veryfi/w9s.py b/veryfi/w9s.py index 9bfc1cc..d742222 100644 --- a/veryfi/w9s.py +++ b/veryfi/w9s.py @@ -1,6 +1,6 @@ import os import base64 -from typing import * +from typing import Dict, Optional from veryfi.client_base import Client @@ -31,7 +31,9 @@ def process_w9_document_url( request_arguments.update(kwargs) return self.client._request("POST", endpoint_name, request_arguments) - def process_w9_document(self, file_path: str, file_name: Optional[str] = None, **kwargs): + def process_w9_document( + self, file_path: str, file_name: Optional[str] = None, **kwargs + ) -> Dict: """ Process W9 Document from url and extract all the fields from it. https://docs.veryfi.com/api/w9s/process-a-w-9/ @@ -72,7 +74,7 @@ def get_w9s( created_date_gte: Optional[str] = None, created_date_lt: Optional[str] = None, created_date_lte: Optional[str] = None, - **kwargs: Dict, + **kwargs, ): """ Get list of w9s documents From 2065c3e9ca1d50794808274508065f9e2a9377dc Mon Sep 17 00:00:00 2001 From: Sebastian Date: Wed, 19 Mar 2025 13:51:35 -0500 Subject: [PATCH 7/8] new version --- veryfi/client_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veryfi/client_base.py b/veryfi/client_base.py index c81a724..79b7c24 100644 --- a/veryfi/client_base.py +++ b/veryfi/client_base.py @@ -42,7 +42,7 @@ def _get_headers(self) -> Dict: :return: Dictionary with headers """ final_headers = { - "User-Agent": "Python Veryfi-Python/3.4.1", + "User-Agent": "Python Veryfi-Python/4.0.0", "Accept": "application/json", "Content-Type": "application/json", "Client-Id": self.client_id, From fb146241713ec1014988fb7199d22f1984b896d1 Mon Sep 17 00:00:00 2001 From: Sebastian Date: Thu, 20 Mar 2025 10:10:34 -0500 Subject: [PATCH 8/8] update user agent to 5.0.0 --- veryfi/client_base.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/veryfi/client_base.py b/veryfi/client_base.py index 79b7c24..fb75dfd 100644 --- a/veryfi/client_base.py +++ b/veryfi/client_base.py @@ -42,7 +42,7 @@ def _get_headers(self) -> Dict: :return: Dictionary with headers """ final_headers = { - "User-Agent": "Python Veryfi-Python/4.0.0", + "User-Agent": "Python Veryfi-Python/5.0.0", "Accept": "application/json", "Content-Type": "application/json", "Client-Id": self.client_id,