├── .github ├── ISSUE_TEMPLATE │ ├── bug_report.md │ ├── feature_request.md │ └── something-else.md └── workflows │ ├── build.yml │ ├── mutation-test.yml │ └── release.yml ├── .gitignore ├── .mend ├── .pre-commit-config.yaml ├── .scripts └── update_vonage_versions.py ├── BUILD ├── CODE_OF_CONDUCT.md ├── LICENSE ├── Makefile ├── README.md ├── V3_TO_V4_SDK_MIGRATION_GUIDE.md ├── account ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_account │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── account.py │ │ ├── errors.py │ │ ├── requests.py │ │ └── responses.py └── tests │ ├── BUILD │ ├── data │ ├── create_secret_error_max_number.json │ ├── get_balance.json │ ├── get_country_pricing.json │ ├── get_multiple_countries_pricing.json │ ├── list_secrets.json │ ├── revoke_secret_error.json │ ├── secret.json │ ├── top_up.json │ └── update_default_sms_webhook.json │ └── test_account.py ├── application ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_application │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── application.py │ │ ├── common.py │ │ ├── enums.py │ │ ├── errors.py │ │ ├── requests.py │ │ └── responses.py └── tests │ ├── BUILD │ ├── data │ ├── create_application_basic.json │ ├── create_application_options.json │ ├── get_application.json │ ├── list_applications_basic.json │ ├── list_applications_multiple_pages.json │ └── update_application.json │ └── test_application.py ├── http_client ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_http_client │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── auth.py │ │ ├── errors.py │ │ └── http_client.py └── tests │ ├── BUILD │ ├── data │ ├── 400.json │ ├── 400.txt │ ├── 401.json │ ├── 403.json │ ├── 404.json │ ├── 429.json │ ├── 500.json │ ├── dummy_private_key.txt │ ├── dummy_public_key.txt │ ├── example_get.json │ ├── example_post.json │ └── file_stream.mp3 │ ├── test_auth.py │ └── test_http_client.py ├── jwt ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_jwt │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── jwt.py │ │ └── verify_jwt.py └── tests │ ├── BUILD │ ├── data │ ├── private_key.txt │ └── public_key.txt │ ├── test_jwt_generator.py │ └── test_verify_jwt.py ├── messages ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_messages │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── messages.py │ │ ├── models │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── base_message.py │ │ ├── enums.py │ │ ├── messenger.py │ │ ├── mms.py │ │ ├── rcs.py │ │ ├── sms.py │ │ ├── viber.py │ │ └── whatsapp.py │ │ └── responses.py └── tests │ ├── BUILD │ ├── data │ ├── invalid_error.json │ ├── low_balance_error.json │ ├── not_found.json │ └── send_message.json │ ├── test_messages.py │ ├── test_messenger_models.py │ ├── test_mms_models.py │ ├── test_rcs_models.py │ ├── test_sms_models.py │ ├── test_viber_models.py │ └── test_whatsapp_models.py ├── network_auth ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_network_auth │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── network_auth.py │ │ ├── requests.py │ │ └── responses.py └── tests │ ├── BUILD │ ├── data │ ├── oidc_request.json │ ├── oidc_request_permissions_error.json │ └── token_request.json │ └── test_network_auth.py ├── network_number_verification ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_network_number_verification │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── number_verification.py │ │ ├── requests.py │ │ └── responses.py └── tests │ ├── BUILD │ ├── data │ ├── token_request.json │ └── verify_number.json │ └── test_number_verification.py ├── network_sim_swap ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_network_sim_swap │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── requests.py │ │ ├── responses.py │ │ └── sim_swap.py └── tests │ ├── BUILD │ ├── data │ ├── check_sim_swap.json │ └── get_swap_date.json │ └── test_sim_swap.py ├── number_insight ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_number_insight │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── number_insight.py │ │ ├── requests.py │ │ └── responses.py └── tests │ ├── BUILD │ ├── data │ ├── advanced_async_insight.json │ ├── advanced_async_insight_error.json │ ├── advanced_async_insight_partial_error.json │ ├── advanced_sync_insight.json │ ├── basic_insight.json │ ├── basic_insight_error.json │ └── standard_insight.json │ └── test_number_insight.py ├── number_management ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_numbers │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── enums.py │ │ ├── errors.py │ │ ├── number_management.py │ │ ├── requests.py │ │ └── responses.py └── tests │ ├── BUILD │ ├── data │ ├── list_owned_numbers_basic.json │ ├── list_owned_numbers_filter.json │ ├── list_owned_numbers_subset.json │ ├── no_number.json │ ├── nothing.json │ ├── number.json │ ├── search_available_numbers_basic.json │ ├── search_available_numbers_end_of_list.json │ └── search_available_numbers_filter.json │ └── test_numbers.py ├── pants.ci.toml ├── pants.toml ├── pyproject.toml ├── requirements.txt ├── sms ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_sms │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── requests.py │ │ ├── responses.py │ │ └── sms.py └── tests │ ├── BUILD │ ├── data │ ├── conversion_not_enabled.html │ ├── null │ ├── send_long_sms.json │ ├── send_sms.json │ ├── send_sms_error.json │ └── send_sms_partial_error.json │ └── test_sms.py ├── subaccounts ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_subaccounts │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── requests.py │ │ ├── responses.py │ │ └── subaccounts.py └── tests │ ├── BUILD │ ├── data │ ├── create_subaccount.json │ ├── get_subaccount.json │ ├── list_balance_transfers.json │ ├── list_credit_transfers.json │ ├── list_subaccounts.json │ ├── modify_subaccount.json │ ├── transfer.json │ ├── transfer_number.json │ └── transfer_number_error_suspended_account.json │ └── test_subaccounts.py ├── testutils ├── BUILD ├── __init__.py ├── data │ └── fake_private_key.txt ├── mock_auth.py └── testutils.py ├── users ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_users │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── common.py │ │ ├── requests.py │ │ ├── responses.py │ │ └── users.py └── tests │ ├── BUILD │ ├── data │ ├── list_users.json │ ├── list_users_options.json │ ├── updated_user.json │ ├── user.json │ └── user_not_found.json │ └── test_users.py ├── verify ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_verify │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── enums.py │ │ ├── errors.py │ │ ├── requests.py │ │ ├── responses.py │ │ └── verify.py └── tests │ ├── BUILD │ ├── data │ ├── check_code.json │ ├── check_code_400.json │ ├── check_code_410.json │ ├── trigger_next_workflow_error.json │ ├── verify_request.json │ └── verify_request_error.json │ ├── test_models.py │ └── test_verify.py ├── verify_legacy ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_verify_legacy │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── language_codes.py │ │ ├── requests.py │ │ ├── responses.py │ │ └── verify_legacy.py └── tests │ ├── BUILD │ ├── data │ ├── cancel_verification.json │ ├── cancel_verification_error.json │ ├── check_code.json │ ├── check_code_error.json │ ├── network_unblock.json │ ├── network_unblock_error.json │ ├── search_request.json │ ├── search_request_error.json │ ├── search_request_list.json │ ├── trigger_next_event.json │ ├── trigger_next_event_error.json │ ├── verify_request.json │ ├── verify_request_error.json │ └── verify_request_error_with_network.json │ └── test_verify_legacy.py ├── video ├── BUILD ├── CHANGES.md ├── OPENTOK_TO_VONAGE_MIGRATION.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_video │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── models │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── archive.py │ │ ├── audio_connector.py │ │ ├── broadcast.py │ │ ├── captions.py │ │ ├── common.py │ │ ├── enums.py │ │ ├── experience_composer.py │ │ ├── session.py │ │ ├── signal.py │ │ ├── sip.py │ │ ├── stream.py │ │ └── token.py │ │ └── video.py └── tests │ ├── BUILD │ ├── data │ ├── archive.json │ ├── audio_connector.json │ ├── broadcast.json │ ├── captions_error_already_enabled.json │ ├── change_stream_layout.json │ ├── create_session.json │ ├── delete_archive_error.json │ ├── get_experience_composer.json │ ├── get_stream.json │ ├── initiate_sip_call.json │ ├── list_archives.json │ ├── list_broadcasts.json │ ├── list_broadcasts_next_page.json │ ├── list_experience_composers.json │ ├── list_streams.json │ ├── nothing.json │ ├── start_broadcast_error.json │ ├── start_captions.json │ ├── start_experience_composer.json │ ├── stop_archive.json │ ├── stop_archive_error.json │ ├── stop_broadcast.json │ └── stop_broadcast_timeout_error.json │ ├── test_archive.py │ ├── test_audio_connector.py │ ├── test_broadcast.py │ ├── test_captions.py │ ├── test_experience_composer.py │ ├── test_moderation.py │ ├── test_session.py │ ├── test_signal.py │ ├── test_sip.py │ ├── test_stream.py │ └── test_token.py ├── voice ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage_voice │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ ├── errors.py │ │ ├── models │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── common.py │ │ ├── connect_endpoints.py │ │ ├── enums.py │ │ ├── input_types.py │ │ ├── ncco.py │ │ ├── requests.py │ │ └── responses.py │ │ └── voice.py └── tests │ ├── BUILD │ ├── data │ ├── create_call.json │ ├── file_stream.mp3 │ ├── get_call.json │ ├── list_calls.json │ ├── list_calls_filter.json │ ├── play_audio_into_call.json │ ├── play_dtmf_into_call.json │ ├── play_tts_into_call.json │ ├── stop_audio_stream.json │ └── stop_tts.json │ ├── test_ncco_actions.py │ └── test_voice.py ├── vonage ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src │ └── vonage │ │ ├── BUILD │ │ ├── __init__.py │ │ ├── _version.py │ │ └── vonage.py └── tests │ ├── BUILD │ └── test_vonage.py └── vonage_utils ├── BUILD ├── CHANGES.md ├── README.md ├── pyproject.toml ├── src └── vonage_utils │ ├── BUILD │ ├── __init__.py │ ├── _version.py │ ├── errors.py │ ├── models.py │ ├── types.py │ └── utils.py └── tests ├── BUILD ├── test_format_phone_number.py └── test_remove_none_values.py /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | --- 11 | name: Bug report 12 | about: Create a report to help us improve 13 | title: '' 14 | labels: '' 15 | assignees: '' 16 | 17 | --- 18 | 19 | 20 | 21 | ## Expected Behavior 22 | 23 | 24 | 25 | ## Current Behavior 26 | 27 | 28 | 29 | ## Possible Solution 30 | 31 | 32 | 33 | ## Steps to Reproduce (for bugs) 34 | 35 | 36 | 1. 37 | 2. 38 | 3. 39 | 4. 40 | 41 | ## Context 42 | 43 | 44 | 45 | ## Your Environment 46 | 47 | * Version used: 48 | * Environment name and version (e.g. language and server version): 49 | * Operating System and version: 50 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/feature_request.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Feature request 3 | about: Suggest an idea for this project 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | **Is your feature request related to a problem? Please describe.** 11 | A clear and concise description of what the problem is. Ex. I'm always frustrated when [...] 12 | 13 | **Describe the solution you'd like** 14 | A clear and concise description of what you want to happen. 15 | 16 | **Describe alternatives you've considered** 17 | A clear and concise description of any alternative solutions or features you've considered. 18 | 19 | **Additional context** 20 | Add any other context or screenshots about the feature request here. 21 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/something-else.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Something else 3 | about: Custom template 4 | title: '' 5 | labels: '' 6 | assignees: '' 7 | 8 | --- 9 | 10 | Tell us what's up! 11 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | 4 | permissions: 5 | actions: write 6 | checks: write 7 | contents: read 8 | deployments: read 9 | issues: write 10 | discussions: write 11 | packages: read 12 | pages: write 13 | pull-requests: write 14 | security-events: write 15 | statuses: write 16 | 17 | env: 18 | PANTS_CONFIG_FILES: "pants.ci.toml" 19 | 20 | jobs: 21 | test: 22 | name: Test 23 | runs-on: ${{ matrix.os }} 24 | strategy: 25 | fail-fast: false 26 | matrix: 27 | python: ["3.9", "3.10", "3.11", "3.12"] 28 | os: ["ubuntu-latest"] 29 | steps: 30 | - name: Clone repo 31 | uses: actions/checkout@v4 32 | - name: Setup python 33 | uses: actions/setup-python@v5 34 | with: 35 | python-version: ${{ matrix.python }} 36 | - name: Initialize pants 37 | uses: pantsbuild/actions/init-pants@main 38 | with: 39 | gha-cache-key: cache0-py${{ matrix.python }} 40 | named-caches-hash: ${{ hashFiles('requirements.txt') }} 41 | - name: Check BUILD files 42 | run: | 43 | pants tailor --check update-build-files --check :: 44 | - name: Lint 45 | run: | 46 | pants lint :: 47 | - name: Run tests 48 | run: | 49 | pants test --use-coverage :: 50 | -------------------------------------------------------------------------------- /.github/workflows/mutation-test.yml: -------------------------------------------------------------------------------- 1 | name: Mutation Test 2 | on: workflow_dispatch 3 | 4 | permissions: 5 | actions: write 6 | checks: write 7 | contents: read 8 | deployments: read 9 | issues: write 10 | discussions: write 11 | packages: read 12 | pages: write 13 | pull-requests: write 14 | security-events: write 15 | statuses: write 16 | 17 | jobs: 18 | mutation: 19 | name: run mutation test 20 | runs-on: ubuntu-latest 21 | strategy: 22 | fail-fast: true 23 | matrix: 24 | python-version: ["3.10"] 25 | 26 | continue-on-error: true 27 | 28 | steps: 29 | - uses: actions/checkout@v4 30 | - name: Set up Python ${{ matrix.python-version }} 31 | uses: actions/setup-python@v5 32 | with: 33 | python-version: ${{ matrix.python-version }} 34 | - name: Install dependencies 35 | run: | 36 | python -m pip install --upgrade pip 37 | if [ -f requirements.txt ]; then pip install -r requirements.txt; fi 38 | python -m pip install mutmut 39 | - name: Run mutation test 40 | run: | 41 | mutmut run --no-progress --CI 42 | - name: Save HTML output 43 | run: | 44 | mutmut html 45 | - uses: actions/upload-artifact@v3 46 | with: 47 | name: mutation-test-report 48 | path: html/ 49 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Created by https://www.gitignore.io/api/python 2 | 3 | ### Python ### 4 | # Byte-compiled / optimized / DLL files 5 | __pycache__/ 6 | *.py[cod] 7 | *$py.class 8 | 9 | # C extensions 10 | *.so 11 | 12 | # Distribution / packaging 13 | .Python 14 | env/ 15 | build/ 16 | develop-eggs/ 17 | dist/ 18 | downloads/ 19 | eggs/ 20 | .eggs/ 21 | lib/ 22 | lib64/ 23 | parts/ 24 | sdist/ 25 | var/ 26 | wheels/ 27 | *.egg-info/ 28 | .installed.cfg 29 | *.egg 30 | 31 | # PyInstaller 32 | # Usually these files are written by a python script from a template 33 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 34 | *.manifest 35 | *.spec 36 | 37 | # Installer logs 38 | pip-log.txt 39 | pip-delete-this-directory.txt 40 | 41 | # Unit test / coverage reports 42 | htmlcov/ 43 | .tox/ 44 | .coverage 45 | .coverage.* 46 | .coveralls.* 47 | .cache 48 | nosetests.xml 49 | coverage.xml 50 | *,cover 51 | .hypothesis/ 52 | 53 | # Translations 54 | *.mo 55 | *.pot 56 | 57 | # Django stuff: 58 | *.log 59 | local_settings.py 60 | 61 | # Flask stuff: 62 | instance/ 63 | .webassets-cache 64 | 65 | # Scrapy stuff: 66 | .scrapy 67 | 68 | # Sphinx documentation 69 | docs/_build/ 70 | 71 | # PyBuilder 72 | target/ 73 | 74 | # Jupyter Notebook 75 | .ipynb_checkpoints 76 | 77 | # pyenv 78 | .python-version 79 | 80 | # celery beat schedule file 81 | celerybeat-schedule 82 | 83 | # SageMath parsed files 84 | *.sage.py 85 | 86 | # dotenv 87 | .env 88 | 89 | # virtualenv 90 | .venv* 91 | venv* 92 | ENV* 93 | 94 | # Spyder project settings 95 | .spyderproject 96 | .spyproject 97 | 98 | # Rope project settings 99 | .ropeproject 100 | 101 | # mkdocs documentation 102 | /site 103 | 104 | .requirements.txt 105 | *_quickstart* 106 | 107 | .DS_Store 108 | .vscode 109 | .idea 110 | .pypirc 111 | .pytest_cache 112 | html/ 113 | .mutmut-cache 114 | _test_scripts/ 115 | _dev_scripts/ 116 | 117 | # Pants workspace files 118 | /.pants.* 119 | /dist/ 120 | /.pids 121 | -------------------------------------------------------------------------------- /.mend: -------------------------------------------------------------------------------- 1 | { 2 | "settingsInheritedFrom": "Vonage/whitesource-config@main", 3 | "scanSettings": { 4 | "enableIaC": false 5 | } 6 | } -------------------------------------------------------------------------------- /.pre-commit-config.yaml: -------------------------------------------------------------------------------- 1 | repos: 2 | - repo: https://github.com/pre-commit/pre-commit-hooks 3 | rev: v4.4.0 4 | hooks: 5 | - id: check-yaml 6 | - id: trailing-whitespace -------------------------------------------------------------------------------- /BUILD: -------------------------------------------------------------------------------- 1 | python_requirements( 2 | name="reqs", 3 | ) 4 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | .PHONY: format test coverage coverage-report install 2 | 3 | format: 4 | pants lint :: 5 | pants fix :: 6 | 7 | test: 8 | pants test :: 9 | 10 | coverage: 11 | pants test --use-coverage :: 12 | 13 | coverage-report: 14 | pants test --use-coverage --open-coverage :: 15 | 16 | install: 17 | pip install -r requirements.txt -------------------------------------------------------------------------------- /account/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-account', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'account/src/vonage_account', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /account/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.1.1 2 | - Update dependency versions 3 | 4 | # 1.1.0 5 | - Add support for the [Vonage Pricing API](https://developer.vonage.com/en/api/pricing) 6 | - Update dependency versions 7 | 8 | # 1.0.2 9 | - Support for Python 3.13, drop support for 3.8 10 | 11 | # 1.0.1 12 | - Add docstrings to data models 13 | 14 | # 1.0.0 15 | - Initial upload 16 | -------------------------------------------------------------------------------- /account/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-account' 3 | dynamic = ["version"] 4 | description = 'Vonage Account API package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_account._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /account/src/vonage_account/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /account/src/vonage_account/__init__.py: -------------------------------------------------------------------------------- 1 | from .account import Account 2 | from .errors import InvalidSecretError 3 | from .requests import GetCountryPricingRequest, GetPrefixPricingRequest, ServiceType 4 | from .responses import ( 5 | Balance, 6 | GetMultiplePricingResponse, 7 | GetPricingResponse, 8 | NetworkPricing, 9 | SettingsResponse, 10 | TopUpResponse, 11 | VonageApiSecret, 12 | ) 13 | 14 | __all__ = [ 15 | 'Account', 16 | 'InvalidSecretError', 17 | 'GetCountryPricingRequest', 18 | 'GetPrefixPricingRequest', 19 | 'ServiceType', 20 | 'Balance', 21 | 'GetPricingResponse', 22 | 'GetMultiplePricingResponse', 23 | 'NetworkPricing', 24 | 'SettingsResponse', 25 | 'TopUpResponse', 26 | 'VonageApiSecret', 27 | ] 28 | -------------------------------------------------------------------------------- /account/src/vonage_account/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.1.1' 2 | -------------------------------------------------------------------------------- /account/src/vonage_account/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class InvalidSecretError(VonageError): 5 | """Indicates that the secret provided was invalid.""" 6 | -------------------------------------------------------------------------------- /account/src/vonage_account/requests.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class ServiceType(str, Enum): 7 | """The service you wish to retrieve outbound pricing data about. 8 | 9 | Values: 10 | ``` 11 | SMS: SMS 12 | SMS_TRANSIT: SMS transit 13 | VOICE: Voice 14 | ``` 15 | """ 16 | 17 | SMS = 'sms' 18 | SMS_TRANSIT = 'sms-transit' 19 | VOICE = 'voice' 20 | 21 | 22 | class GetCountryPricingRequest(BaseModel): 23 | """The options for getting the pricing for a specific country. 24 | 25 | Args: 26 | country_code (str): The two-letter country code for the country to retrieve 27 | pricing data about. 28 | type (ServiceType, Optional): The type of service to retrieve pricing data about. 29 | """ 30 | 31 | country_code: str 32 | type: ServiceType = ServiceType.SMS 33 | 34 | 35 | class GetPrefixPricingRequest(BaseModel): 36 | """The options for getting the pricing for a specific prefix. 37 | 38 | Args: 39 | prefix (str): The numerical dialing prefix to look up pricing for, e.g. "1", "44". 40 | type (ServiceType, Optional): The type of service to retrieve pricing data about. 41 | """ 42 | 43 | prefix: str 44 | type: ServiceType = ServiceType.SMS 45 | -------------------------------------------------------------------------------- /account/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['account', 'testutils']) 2 | -------------------------------------------------------------------------------- /account/tests/data/create_secret_error_max_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.nexmo.com/api-errors/account/secret-management#add-excess-secret", 3 | "title": "Secret Addition Forbidden", 4 | "detail": "Account reached maximum number [2] of allowed secrets", 5 | "instance": "48898273-7ae1-4ce4-8125-a71058ca6069" 6 | } -------------------------------------------------------------------------------- /account/tests/data/get_balance.json: -------------------------------------------------------------------------------- 1 | { 2 | "value": 29.18202293, 3 | "autoReload": false 4 | } -------------------------------------------------------------------------------- /account/tests/data/get_multiple_countries_pricing.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "countries": [ 4 | { 5 | "dialingPrefix": "39", 6 | "defaultPrice": "0.08270000", 7 | "currency": "EUR", 8 | "countryDisplayName": "Italy", 9 | "countryCode": "IT", 10 | "countryName": "Italy", 11 | "networks": [ 12 | { 13 | "type": "mobile", 14 | "price": "0.08270000", 15 | "currency": "EUR", 16 | "mcc": "222", 17 | "mnc": "07", 18 | "networkCode": "22207", 19 | "networkName": "Noverca Italia S.r.l." 20 | }, 21 | { 22 | "type": "mobile", 23 | "price": "0.08270000", 24 | "currency": "EUR", 25 | "mcc": "222", 26 | "mnc": "08", 27 | "networkCode": "22208", 28 | "networkName": "FastWeb S.p.A." 29 | }, 30 | { 31 | "type": "landline_premium", 32 | "price": "0.08270000", 33 | "currency": "EUR", 34 | "networkCode": "IT-PREMIUM", 35 | "networkName": "Italy Premium" 36 | } 37 | ] 38 | }, 39 | { 40 | "dialingPrefix": "39", 41 | "currency": "EUR", 42 | "countryDisplayName": "Vatican City", 43 | "countryCode": "VA", 44 | "countryName": "Vatican City" 45 | } 46 | ] 47 | } -------------------------------------------------------------------------------- /account/tests/data/list_secrets.json: -------------------------------------------------------------------------------- 1 | { 2 | "_links": { 3 | "self": { 4 | "href": "/accounts/test_api_key/secrets" 5 | } 6 | }, 7 | "_embedded": { 8 | "secrets": [ 9 | { 10 | "_links": { 11 | "self": { 12 | "href": "/accounts/test_api_key/secrets/1b1b1b1b-1b1b-1b-1b1b-1b1b1b1b1b1b" 13 | } 14 | }, 15 | "id": "1b1b1b1b-1b1b-1b-1b1b-1b1b1b1b1b1b", 16 | "created_at": "2022-03-28T14:16:56Z" 17 | } 18 | ] 19 | } 20 | } -------------------------------------------------------------------------------- /account/tests/data/revoke_secret_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.nexmo.com/api-errors/account/secret-management#delete-last-secret", 3 | "title": "Secret Deletion Forbidden", 4 | "detail": "Can not delete the last secret. The account must always have at least 1 secret active at any time", 5 | "instance": "a845d164-5623-4cc1-b7c6-0f95b94c6e53" 6 | } -------------------------------------------------------------------------------- /account/tests/data/secret.json: -------------------------------------------------------------------------------- 1 | { 2 | "_links": { 3 | "self": { 4 | "href": "/accounts/test_api_key/secrets" 5 | } 6 | }, 7 | "id": "ad6dc56f-07b5-46e1-a527-85530e625800", 8 | "created_at": "2017-03-02T16:34:49Z" 9 | } -------------------------------------------------------------------------------- /account/tests/data/top_up.json: -------------------------------------------------------------------------------- 1 | { 2 | "error-code": "200", 3 | "error-code-label": "success" 4 | } -------------------------------------------------------------------------------- /account/tests/data/update_default_sms_webhook.json: -------------------------------------------------------------------------------- 1 | { 2 | "mo-callback-url": "https://example.com/inbound_sms_webhook", 3 | "dr-callback-url": "https://example.com/delivery_receipt_webhook", 4 | "max-outbound-request": 30, 5 | "max-inbound-request": 30, 6 | "max-calls-per-second": 30 7 | } -------------------------------------------------------------------------------- /application/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-application', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'application/src/vonage_application', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /application/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 2.0.1 2 | - Updated dependency versions 3 | 4 | # 2.0.0 5 | - Rename `params` -> `config` in method arguments 6 | - Update dependency versions 7 | 8 | # 1.0.3 9 | - Support for Python 3.13, drop support for 3.8 10 | 11 | # 1.0.2 12 | - Add docstrings to data models 13 | 14 | # 1.0.1 15 | - Update project metadata 16 | 17 | # 1.0.0 18 | - Initial upload 19 | -------------------------------------------------------------------------------- /application/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-application' 3 | dynamic = ["version"] 4 | description = 'Vonage Application API package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_application._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /application/src/vonage_application/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /application/src/vonage_application/__init__.py: -------------------------------------------------------------------------------- 1 | from . import errors 2 | from .application import Application 3 | from .common import ( 4 | ApplicationUrl, 5 | Capabilities, 6 | Keys, 7 | Messages, 8 | MessagesWebhooks, 9 | Privacy, 10 | Rtc, 11 | RtcWebhooks, 12 | Vbc, 13 | Verify, 14 | VerifyWebhooks, 15 | Voice, 16 | VoiceUrl, 17 | VoiceWebhooks, 18 | ) 19 | from .enums import Region 20 | from .requests import ApplicationConfig, ListApplicationsFilter 21 | from .responses import ApplicationData, ListApplicationsResponse 22 | 23 | __all__ = [ 24 | 'Application', 25 | 'ApplicationConfig', 26 | 'ApplicationData', 27 | 'ApplicationUrl', 28 | 'Capabilities', 29 | 'Keys', 30 | 'ListApplicationsFilter', 31 | 'ListApplicationsResponse', 32 | 'Messages', 33 | 'MessagesWebhooks', 34 | 'Privacy', 35 | 'Region', 36 | 'Rtc', 37 | 'RtcWebhooks', 38 | 'Vbc', 39 | 'Verify', 40 | 'VerifyWebhooks', 41 | 'Voice', 42 | 'VoiceUrl', 43 | 'VoiceWebhooks', 44 | 'errors', 45 | ] 46 | -------------------------------------------------------------------------------- /application/src/vonage_application/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.0.1' 2 | -------------------------------------------------------------------------------- /application/src/vonage_application/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Region(str, Enum): 5 | """All inbound, programmable SIP and SIP connect voice calls will be sent to the 6 | selected region unless the call itself is sent to a regional endpoint. 7 | 8 | If the call is using a regional endpoint, this will override the application setting. 9 | """ 10 | 11 | NA_EAST = 'na-east' 12 | NA_WEST = 'na-west' 13 | EU_EAST = 'eu-east' 14 | EU_WEST = 'eu-west' 15 | APAC_SNG = 'apac-sng' 16 | APAC_AUSTRALIA = 'apac-australia' 17 | -------------------------------------------------------------------------------- /application/src/vonage_application/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class ApplicationError(VonageError): 5 | """Indicates an error with the Application package.""" 6 | -------------------------------------------------------------------------------- /application/src/vonage_application/requests.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | from .common import ApplicationBase 6 | 7 | 8 | class ListApplicationsFilter(BaseModel): 9 | """Request object for filtering applications. 10 | 11 | Args: 12 | page_size (int, Optional): The number of applications to return per page. 13 | page (int, Optional): The page number to return. 14 | """ 15 | 16 | page_size: Optional[int] = 100 17 | page: int = None 18 | 19 | 20 | class ApplicationConfig(ApplicationBase): 21 | """Application object used in requests when communicating with the Vonage Application 22 | API. 23 | 24 | Args: 25 | name (str): The name of the application. 26 | capabilities (Capabilities, Optional): The capabilities of the application. 27 | privacy (Privacy, Optional): The privacy settings for the application. 28 | keys (Keys, Optional): The application keys. 29 | """ 30 | -------------------------------------------------------------------------------- /application/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['application', 'testutils']) 2 | -------------------------------------------------------------------------------- /application/tests/data/create_application_basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "ba1a6aa3-8ac6-487d-ac5c-be469e77ddb7", 3 | "name": "My Application", 4 | "keys": { 5 | "private_key": "-----BEGIN PRIVATE KEY-----\nprivate_key_info_goes_here\n-----END PRIVATE KEY-----\n", 6 | "public_key": "-----BEGIN PUBLIC KEY-----\npublic_key_info_goes_here\n-----END PUBLIC KEY-----\n" 7 | }, 8 | "privacy": { 9 | "improve_ai": false 10 | }, 11 | "capabilities": {}, 12 | "_links": { 13 | "self": { 14 | "href": "/v2/applications/ba1a6aa3-8ac6-487d-ac5c-be469e77ddb7" 15 | } 16 | } 17 | } -------------------------------------------------------------------------------- /application/tests/data/get_application.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b", 3 | "name": "My Server Demo", 4 | "keys": { 5 | "public_key": "-----BEGIN PUBLIC KEY-----\npublic_key_info_goes_here\n-----END PUBLIC KEY-----\n" 6 | }, 7 | "privacy": { 8 | "improve_ai": false 9 | }, 10 | "capabilities": { 11 | "voice": { 12 | "webhooks": { 13 | "event_url": { 14 | "address": "http://example.ngrok.app/webhooks/events", 15 | "http_method": "POST", 16 | "socket_timeout": 10000, 17 | "connect_timeout": 1000 18 | }, 19 | "answer_url": { 20 | "address": "http://example.ngrok.app/webhooks/answer", 21 | "http_method": "GET", 22 | "socket_timeout": 5000, 23 | "connect_timeout": 1000 24 | } 25 | }, 26 | "signed_callbacks": true, 27 | "conversations_ttl": 48, 28 | "leg_persistence_time": 7 29 | } 30 | }, 31 | "_links": { 32 | "self": { 33 | "href": "/v2/applications/1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b" 34 | } 35 | } 36 | } -------------------------------------------------------------------------------- /application/tests/data/list_applications_basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "page_size": 100, 3 | "page": 1, 4 | "total_items": 1, 5 | "total_pages": 1, 6 | "_embedded": { 7 | "applications": [ 8 | { 9 | "id": "1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b", 10 | "name": "dev-application", 11 | "keys": { 12 | "public_key": "-----BEGIN PUBLIC KEY-----\npublic_key_info_goes_here\n-----END PUBLIC KEY-----\n" 13 | }, 14 | "privacy": { 15 | "improve_ai": true 16 | }, 17 | "capabilities": { 18 | "voice": { 19 | "webhooks": { 20 | "event_url": { 21 | "address": "http://example.com", 22 | "http_method": "POST" 23 | }, 24 | "answer_url": { 25 | "address": "http://example.com", 26 | "http_method": "GET" 27 | } 28 | }, 29 | "signed_callbacks": true, 30 | "conversations_ttl": 9000, 31 | "leg_persistence_time": 7 32 | } 33 | } 34 | } 35 | ] 36 | }, 37 | "_links": { 38 | "self": { 39 | "href": "/v2/applications?page_size=100&page=1" 40 | }, 41 | "first": { 42 | "href": "/v2/applications?page_size=100" 43 | }, 44 | "last": { 45 | "href": "/v2/applications?page_size=100&page=1" 46 | } 47 | } 48 | } -------------------------------------------------------------------------------- /application/tests/data/update_application.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b", 3 | "name": "My Updated Application", 4 | "keys": { 5 | "public_key": "-----BEGIN PUBLIC KEY-----\nupdated_public_key_info\n-----END PUBLIC KEY-----\n" 6 | }, 7 | "capabilities": {}, 8 | "_links": { 9 | "self": { 10 | "href": "/v2/applications/1b1b1b1b-1b1b-1b1b-1b1b-1b1b1b1b1b1b" 11 | } 12 | } 13 | } -------------------------------------------------------------------------------- /http_client/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-http-client', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'http_client/src/vonage_http_client:http_client', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /http_client/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.5.1 2 | - Remove unnecessary `Content-Type` check on error 3 | 4 | # 1.5.0 5 | - Add new `HttpClient.download_file_stream` method 6 | - Add new `FileStreamingError` exception type 7 | - Add backoff exponential timeout increase for HTTP request retries 8 | - Add retries for `RemoteDisconnected` exceptions 9 | 10 | # 1.4.3 11 | - Update JWT dependency version 12 | 13 | # 1.4.2 14 | - Support for Python 3.13, drop support for 3.8 15 | 16 | # 1.4.1 17 | - Add docstrings to data models 18 | 19 | # 1.4.0 20 | - Add new `oauth2` logic for calling APIs that require Oauth 21 | 22 | # 1.3.1 23 | - Update minimum dependency version 24 | 25 | # 1.3.0 26 | - Add new PUT method 27 | 28 | # 1.2.1 29 | - Expose classes and errors at the package level 30 | 31 | # 1.2.0 32 | - Add `last_request` and `last_response` properties 33 | - Add new `Forbidden` error 34 | 35 | # 1.1.1 36 | - Add new Patch method 37 | - New input fields for different ways to pass data in a request 38 | 39 | # 1.1.0 40 | - Add support for signature authentication 41 | 42 | # 1.0.0 43 | - Initial upload 44 | -------------------------------------------------------------------------------- /http_client/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vonage-http-client" 3 | dynamic = ["version"] 4 | description = "An HTTP client for making requests to Vonage APIs." 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-utils>=1.1.4", 10 | "vonage-jwt>=1.1.5", 11 | "requests>=2.27.0", 12 | "typing-extensions>=4.9.0", 13 | "pydantic>=2.9.2", 14 | ] 15 | classifiers = [ 16 | "Programming Language :: Python", 17 | "Programming Language :: Python :: 3", 18 | "Programming Language :: Python :: 3.9", 19 | "Programming Language :: Python :: 3.10", 20 | "Programming Language :: Python :: 3.11", 21 | "Programming Language :: Python :: 3.12", 22 | "Programming Language :: Python :: 3.13", 23 | "License :: OSI Approved :: Apache Software License", 24 | ] 25 | 26 | [project.urls] 27 | Homepage = "https://github.com/Vonage/vonage-python-sdk" 28 | 29 | [tool.setuptools.dynamic] 30 | version = { attr = "vonage_http_client._version.__version__" } 31 | 32 | [build-system] 33 | requires = ["setuptools>=61.0", "wheel"] 34 | build-backend = "setuptools.build_meta" 35 | -------------------------------------------------------------------------------- /http_client/src/vonage_http_client/BUILD: -------------------------------------------------------------------------------- 1 | python_sources(name='http_client') 2 | -------------------------------------------------------------------------------- /http_client/src/vonage_http_client/__init__.py: -------------------------------------------------------------------------------- 1 | from .auth import Auth 2 | from .errors import ( 3 | AuthenticationError, 4 | FileStreamingError, 5 | ForbiddenError, 6 | HttpRequestError, 7 | InvalidAuthError, 8 | InvalidHttpClientOptionsError, 9 | JWTGenerationError, 10 | NotFoundError, 11 | RateLimitedError, 12 | ServerError, 13 | ) 14 | from .http_client import HttpClient, HttpClientOptions 15 | 16 | __all__ = [ 17 | 'Auth', 18 | 'AuthenticationError', 19 | 'FileStreamingError', 20 | 'ForbiddenError', 21 | 'HttpRequestError', 22 | 'InvalidAuthError', 23 | 'InvalidHttpClientOptionsError', 24 | 'JWTGenerationError', 25 | 'NotFoundError', 26 | 'RateLimitedError', 27 | 'ServerError', 28 | 'HttpClient', 29 | 'HttpClientOptions', 30 | ] 31 | -------------------------------------------------------------------------------- /http_client/src/vonage_http_client/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.5.1' 2 | -------------------------------------------------------------------------------- /http_client/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['http_client']) 2 | -------------------------------------------------------------------------------- /http_client/tests/data/400.json: -------------------------------------------------------------------------------- 1 | {"Error": "Bad Request"} -------------------------------------------------------------------------------- /http_client/tests/data/400.txt: -------------------------------------------------------------------------------- 1 | Error: Bad Request -------------------------------------------------------------------------------- /http_client/tests/data/401.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.nexmo.com/api-errors#unauthorized", 3 | "title": "Unauthorized", 4 | "detail": "You did not provide correct credentials.", 5 | "instance": "a813c536-43f6-4568-acbf-f36ef2db955a" 6 | } -------------------------------------------------------------------------------- /http_client/tests/data/403.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.vonage.com/api-errors#forbidden", 3 | "title": "Forbidden", 4 | "detail": "Your account does not have permission to perform this action.", 5 | "instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf" 6 | } -------------------------------------------------------------------------------- /http_client/tests/data/404.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Not found.", 3 | "type": "https://developer.vonage.com/api/conversation#user:error:not-found", 4 | "detail": "User does not exist, or you do not have access.", 5 | "instance": "00a5916655d650e920ccf0daf40ef4ee" 6 | } -------------------------------------------------------------------------------- /http_client/tests/data/429.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Rate Limit Hit", 3 | "type": "https://developer.vonage.com/api-errors#rate-limit", 4 | "detail": "Please wait, then retry your request", 5 | "instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf" 6 | } -------------------------------------------------------------------------------- /http_client/tests/data/500.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.vonage.com/api-errors", 3 | "title": "Internal Server Error", 4 | "instance": "272c5fa3-c02a-4451-b33c-d01e8de74023" 5 | } -------------------------------------------------------------------------------- /http_client/tests/data/dummy_private_key.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQdAHqJHs/a+Ra 3 | 2ubvSd1vz/aWlJ9BqnMUtB7guTlyggdENAbleIkzep6mUHepDJdQh8Qv6zS3lpUe 4 | K0UkDfr1/FvsvxurGw/YYPagUEhP/HxMbs2rnQTiAdWOT+Ux9vPABoyNYvZB90xN 5 | IVhBDRWgkz1HPQBRNjFcm3NOol83h5Uwp5YroGTWx+rpmIiRhQj3mv6luk102d95 6 | 4ulpPpzcYWKIpJNdclJrEkBZaghDZTOpbv79qd+ds9AVp1j8i9cG/owBJpsJWxfw 7 | StMDpNeEZqopeQWmA121sSEsxpAbKJ5DA7F/lmckx74sulKHX1fDWT76cRhloaEQ 8 | VmETdj0VAgMBAAECggEAZ+SBtchz8vKbsBqtAbM/XcR5Iqi1TR2eWMHDJ/65HpSm 9 | +XuyujjerN0e6EZvtT4Uxmq8QaPJNP0kmhI31hXvsB0UVcUUDa4hshb1pIYO3Gq7 10 | Kr8I29EZB2mhndm9Ii9yYhEBiVA66zrNeR225kkWr97iqjhBibhoVr8Vc6oiqcIP 11 | nFy5zSFtQSkhucaPge6rW00JSOD3wg2GM+rgS6r22t8YmqTzAwvwfil5pQfUngal 12 | oywqLOf6CUYXPBleJc1KgaIIP/cSvqh6b/t25o2VXnI4rpRhtleORvYBbH6K6xLa 13 | OWgg6B58T+0/QEqtZIAn4miYtVCkYLB78Ormc7Q9ewKBgQDuSytuYqxdZh/L/RDU 14 | CErFcNO5I1e9fkLAs5dQEBvvdQC74+oA1MsDEVv0xehFa1JwPKSepmvB2UznZg9L 15 | CtR7QKMDZWvS5xx4j0E/b+PiNQ/tlcFZB2UZ0JwviSxdd7omOTscq9c3RIhFHar1 16 | Y38Fixkfm44Ij/K3JqIi2v2QMwKBgQDf8TYOOmAr9UuipUDxMsRSqTGVIY8B+aEJ 17 | W+2aLrqJVkLGTRfrbjzXWYo3+n7kNJjFgNkltDq6HYtufHMYRs/0PPtNR0w0cDPS 18 | Xr7m2LNHTDcBalC/AS4yKZJLNLm+kXA84vkw4qiTjc0LSFxJkouTQzkea0l8EWHt 19 | zRMv/qYVlwKBgBaJOWRJJK/4lo0+M7c5yYh+sSdTNlsPc9Sxp1/FBj9RO26JkXne 20 | pgx2OdIeXWcjTTqcIZ13c71zhZhkyJF6RroZVNFfaCEcBk9IjQ0o0c504jq/7Pc0 21 | gdU9K2g7etykFBDFXNfLUKFDc/fFZIOskzi8/PVGStp4cqXrm23cdBqNAoGBAKtf 22 | A2bP9ViuVjsZCyGJIAPBxlfBXpa8WSe4WZNrvwPqJx9pT6yyp4yE0OkVoJUyStaZ 23 | S5M24NocUd8zDUC+r9TP9d+leAOI+Z87MgumOUuOX2mN2kzQsnFgrrsulhXnZmSx 24 | rNBkI20HTqobrcP/iSAgiU1l/M4c3zwDe3N3A9HxAoGBAM2hYu0Ij6htSNgo/WWr 25 | IEYYXuwf8hPkiuwzlaiWhD3eocgd4S8SsBu/bTCY19hQ2QbBPaYyFlNem+ynQyXx 26 | IOacrgIHCrYnRCxjPfFF/MxgUHJb8ZoiexprP/FME5p0PoRQIEFYa+jVht3hT5wC 27 | 9aedWufq4JJb+akO6MVUjTvs 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /http_client/tests/data/dummy_public_key.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0HQB6iR7P2vkWtrm70nd 3 | b8/2lpSfQapzFLQe4Lk5coIHRDQG5XiJM3qeplB3qQyXUIfEL+s0t5aVHitFJA36 4 | 9fxb7L8bqxsP2GD2oFBIT/x8TG7Nq50E4gHVjk/lMfbzwAaMjWL2QfdMTSFYQQ0V 5 | oJM9Rz0AUTYxXJtzTqJfN4eVMKeWK6Bk1sfq6ZiIkYUI95r+pbpNdNnfeeLpaT6c 6 | 3GFiiKSTXXJSaxJAWWoIQ2UzqW7+/anfnbPQFadY/IvXBv6MASabCVsX8ErTA6TX 7 | hGaqKXkFpgNdtbEhLMaQGyieQwOxf5ZnJMe+LLpSh19Xw1k++nEYZaGhEFZhE3Y9 8 | FQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /http_client/tests/data/example_get.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world" 3 | } -------------------------------------------------------------------------------- /http_client/tests/data/example_post.json: -------------------------------------------------------------------------------- 1 | { 2 | "hello": "world!" 3 | } -------------------------------------------------------------------------------- /http_client/tests/data/file_stream.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vonage/vonage-python-sdk/82a02e48b9793a2aa0bce3fa593de8cf97caf839/http_client/tests/data/file_stream.mp3 -------------------------------------------------------------------------------- /jwt/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-jwt', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'jwt/src/vonage_jwt', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /jwt/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.1.5 2 | - Improve `verify_signature` docstring 3 | 4 | # 1.1.4 5 | - Fix a bug with generating non-default JWTs 6 | 7 | # 1.1.3 8 | - Support for Python 3.13, drop support for 3.8 9 | 10 | # 1.1.2 11 | - Dynamically specify package version 12 | 13 | # 1.1.1 14 | - Exceptions inherit from `VonageError` 15 | - Moving the package into the Vonage Python SDK monorepo 16 | 17 | # 1.1.0 18 | - Add new module with method to verify JWT signatures, `verify_jwt.verify_signature` 19 | 20 | # 1.0.0 21 | - First stable release -------------------------------------------------------------------------------- /jwt/README.md: -------------------------------------------------------------------------------- 1 | # Vonage JWT Generator for Python 2 | 3 | This package (`vonage-jwt`) provides functionality to generate a JWT in Python code. 4 | 5 | It is used by the [Vonage Python SDK](https://github.com/Vonage/vonage-python-sdk), specifically by the `vonage-http-client` package, to generate JWTs for authentication. Thus, it doesn't require manual installation or configuration unless you're using this package independently of an SDK. 6 | 7 | For full API documentation, refer to the [Vonage developer documentation](https://developer.vonage.com). 8 | 9 | - [Installation](#installation) 10 | - [Generating JWTs](#generating-jwts) 11 | - [Verifying a JWT signature](#verifying-a-jwt-signature) 12 | 13 | ## Installation 14 | 15 | Install from the Python Package Index with pip: 16 | 17 | ```bash 18 | pip install vonage-jwt 19 | ``` 20 | 21 | ## Generating JWTs 22 | 23 | This JWT Generator can be used implicitly, just by using the [Vonage Python SDK](https://github.com/Vonage/vonage-python-sdk) to make JWT-authenticated API calls. 24 | 25 | It can also be used as a standalone JWT generator for use with Vonage APIs, like so: 26 | 27 | ### Import the `JwtClient` object 28 | 29 | ```python 30 | from vonage_jwt import JwtClient 31 | ``` 32 | 33 | ### Create a `JwtClient` object 34 | 35 | ```python 36 | jwt_client = JwtClient(application_id, private_key) 37 | ``` 38 | 39 | ### Generate a JWT using the provided application id and private key 40 | 41 | ```python 42 | jwt_client.generate_application_jwt() 43 | ``` 44 | 45 | Optional JWT claims can be provided in a python dictionary: 46 | 47 | ```python 48 | claims = {'jti': 'asdfzxcv1234', 'nbf': now + 100} 49 | jwt_client.generate_application_jwt(claims) 50 | ``` 51 | 52 | ## Verifying a JWT signature 53 | 54 | You can use the `verify_jwt.verify_signature` method to verify a JWT signature is valid. 55 | 56 | ```python 57 | from vonage_jwt import verify_signature 58 | 59 | verify_signature(TOKEN, SIGNATURE_SECRET) # Returns a boolean 60 | ``` 61 | -------------------------------------------------------------------------------- /jwt/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vonage-jwt" 3 | dynamic = ["version"] 4 | description = "Tooling for working with JWTs for Vonage APIs in Python." 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = ["vonage-utils>=1.1.4", "pyjwt[crypto]>=1.6.4"] 9 | classifiers = [ 10 | "Programming Language :: Python", 11 | "Programming Language :: Python :: 3", 12 | "Programming Language :: Python :: 3.9", 13 | "Programming Language :: Python :: 3.10", 14 | "Programming Language :: Python :: 3.11", 15 | "Programming Language :: Python :: 3.12", 16 | "Programming Language :: Python :: 3.13", 17 | "License :: OSI Approved :: Apache Software License", 18 | ] 19 | 20 | [project.urls] 21 | Homepage = "https://github.com/Vonage/vonage-python-sdk" 22 | 23 | [tool.setuptools.dynamic] 24 | version = { attr = "vonage_jwt._version.__version__" } 25 | 26 | [build-system] 27 | requires = ["setuptools>=61.0", "wheel"] 28 | build-backend = "setuptools.build_meta" 29 | -------------------------------------------------------------------------------- /jwt/src/vonage_jwt/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /jwt/src/vonage_jwt/__init__.py: -------------------------------------------------------------------------------- 1 | from .errors import VonageJwtError, VonageVerifyJwtError 2 | from .jwt import JwtClient 3 | from .verify_jwt import verify_signature 4 | 5 | __all__ = ['JwtClient', 'VonageJwtError', 'VonageVerifyJwtError', 'verify_signature'] 6 | -------------------------------------------------------------------------------- /jwt/src/vonage_jwt/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.1.5' 2 | -------------------------------------------------------------------------------- /jwt/src/vonage_jwt/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils import VonageError 2 | 3 | 4 | class VonageJwtError(VonageError): 5 | """An error relating to the Vonage JWT Generator.""" 6 | 7 | 8 | class VonageVerifyJwtError(VonageError): 9 | """The signature could not be verified.""" 10 | -------------------------------------------------------------------------------- /jwt/src/vonage_jwt/verify_jwt.py: -------------------------------------------------------------------------------- 1 | from jwt import InvalidSignatureError, decode 2 | 3 | from .errors import VonageVerifyJwtError 4 | 5 | 6 | def verify_signature(token: str, signature_secret: str = None) -> bool: 7 | """Method to verify that an incoming JWT was sent by Vonage. 8 | 9 | Args: 10 | token (str): The token to verify. 11 | signature_secret (str, optional): The signature to verify the token against. 12 | 13 | Returns: 14 | bool: True if the token is verified, False otherwise. 15 | 16 | Raises: 17 | VonageVerifyJwtError: The signature could not be verified. 18 | """ 19 | 20 | try: 21 | decode(token, signature_secret, algorithms='HS256') 22 | return True 23 | except InvalidSignatureError: 24 | return False 25 | except Exception as e: 26 | raise VonageVerifyJwtError(repr(e)) 27 | -------------------------------------------------------------------------------- /jwt/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['jwt', 'testutils']) 2 | -------------------------------------------------------------------------------- /jwt/tests/data/private_key.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQdAHqJHs/a+Ra 3 | 2ubvSd1vz/aWlJ9BqnMUtB7guTlyggdENAbleIkzep6mUHepDJdQh8Qv6zS3lpUe 4 | K0UkDfr1/FvsvxurGw/YYPagUEhP/HxMbs2rnQTiAdWOT+Ux9vPABoyNYvZB90xN 5 | IVhBDRWgkz1HPQBRNjFcm3NOol83h5Uwp5YroGTWx+rpmIiRhQj3mv6luk102d95 6 | 4ulpPpzcYWKIpJNdclJrEkBZaghDZTOpbv79qd+ds9AVp1j8i9cG/owBJpsJWxfw 7 | StMDpNeEZqopeQWmA121sSEsxpAbKJ5DA7F/lmckx74sulKHX1fDWT76cRhloaEQ 8 | VmETdj0VAgMBAAECggEAZ+SBtchz8vKbsBqtAbM/XcR5Iqi1TR2eWMHDJ/65HpSm 9 | +XuyujjerN0e6EZvtT4Uxmq8QaPJNP0kmhI31hXvsB0UVcUUDa4hshb1pIYO3Gq7 10 | Kr8I29EZB2mhndm9Ii9yYhEBiVA66zrNeR225kkWr97iqjhBibhoVr8Vc6oiqcIP 11 | nFy5zSFtQSkhucaPge6rW00JSOD3wg2GM+rgS6r22t8YmqTzAwvwfil5pQfUngal 12 | oywqLOf6CUYXPBleJc1KgaIIP/cSvqh6b/t25o2VXnI4rpRhtleORvYBbH6K6xLa 13 | OWgg6B58T+0/QEqtZIAn4miYtVCkYLB78Ormc7Q9ewKBgQDuSytuYqxdZh/L/RDU 14 | CErFcNO5I1e9fkLAs5dQEBvvdQC74+oA1MsDEVv0xehFa1JwPKSepmvB2UznZg9L 15 | CtR7QKMDZWvS5xx4j0E/b+PiNQ/tlcFZB2UZ0JwviSxdd7omOTscq9c3RIhFHar1 16 | Y38Fixkfm44Ij/K3JqIi2v2QMwKBgQDf8TYOOmAr9UuipUDxMsRSqTGVIY8B+aEJ 17 | W+2aLrqJVkLGTRfrbjzXWYo3+n7kNJjFgNkltDq6HYtufHMYRs/0PPtNR0w0cDPS 18 | Xr7m2LNHTDcBalC/AS4yKZJLNLm+kXA84vkw4qiTjc0LSFxJkouTQzkea0l8EWHt 19 | zRMv/qYVlwKBgBaJOWRJJK/4lo0+M7c5yYh+sSdTNlsPc9Sxp1/FBj9RO26JkXne 20 | pgx2OdIeXWcjTTqcIZ13c71zhZhkyJF6RroZVNFfaCEcBk9IjQ0o0c504jq/7Pc0 21 | gdU9K2g7etykFBDFXNfLUKFDc/fFZIOskzi8/PVGStp4cqXrm23cdBqNAoGBAKtf 22 | A2bP9ViuVjsZCyGJIAPBxlfBXpa8WSe4WZNrvwPqJx9pT6yyp4yE0OkVoJUyStaZ 23 | S5M24NocUd8zDUC+r9TP9d+leAOI+Z87MgumOUuOX2mN2kzQsnFgrrsulhXnZmSx 24 | rNBkI20HTqobrcP/iSAgiU1l/M4c3zwDe3N3A9HxAoGBAM2hYu0Ij6htSNgo/WWr 25 | IEYYXuwf8hPkiuwzlaiWhD3eocgd4S8SsBu/bTCY19hQ2QbBPaYyFlNem+ynQyXx 26 | IOacrgIHCrYnRCxjPfFF/MxgUHJb8ZoiexprP/FME5p0PoRQIEFYa+jVht3hT5wC 27 | 9aedWufq4JJb+akO6MVUjTvs 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /jwt/tests/data/public_key.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PUBLIC KEY----- 2 | MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA0HQB6iR7P2vkWtrm70nd 3 | b8/2lpSfQapzFLQe4Lk5coIHRDQG5XiJM3qeplB3qQyXUIfEL+s0t5aVHitFJA36 4 | 9fxb7L8bqxsP2GD2oFBIT/x8TG7Nq50E4gHVjk/lMfbzwAaMjWL2QfdMTSFYQQ0V 5 | oJM9Rz0AUTYxXJtzTqJfN4eVMKeWK6Bk1sfq6ZiIkYUI95r+pbpNdNnfeeLpaT6c 6 | 3GFiiKSTXXJSaxJAWWoIQ2UzqW7+/anfnbPQFadY/IvXBv6MASabCVsX8ErTA6TX 7 | hGaqKXkFpgNdtbEhLMaQGyieQwOxf5ZnJMe+LLpSh19Xw1k++nEYZaGhEFZhE3Y9 8 | FQIDAQAB 9 | -----END PUBLIC KEY----- 10 | -------------------------------------------------------------------------------- /jwt/tests/test_verify_jwt.py: -------------------------------------------------------------------------------- 1 | import pytest 2 | from vonage_jwt.errors import VonageVerifyJwtError 3 | from vonage_jwt.verify_jwt import verify_signature 4 | 5 | token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJpc3MiOiJPbmxpbmUgSldUIEJ1aWxkZXIiLCJpYXQiOjE2OTc2MzQ2ODAsImV4cCI6MzMyNTQ1NDA4MjgsImF1ZCI6IiIsInN1YiI6IiJ9.88vJc3I2HhuqEDixHXVhc9R30tA6U_HQHZTC29y6CGM' 6 | valid_signature = "qwertyuiopasdfghjklzxcvbnm123456" 7 | invalid_signature = 'asdf' 8 | 9 | 10 | def test_verify_signature_valid(): 11 | assert verify_signature(token, valid_signature) is True 12 | 13 | 14 | def test_verify_signature_invalid(): 15 | assert verify_signature(token, invalid_signature) is False 16 | 17 | 18 | def test_verify_signature_error(): 19 | with pytest.raises(VonageVerifyJwtError) as e: 20 | verify_signature('asdf', valid_signature) 21 | assert 'DecodeError' in str(e.value) 22 | -------------------------------------------------------------------------------- /messages/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-messages', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'messages/src/vonage_messages', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /messages/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.4.0 2 | - Make all models originally accessed by `vonage_messages.models.***` available at the top level of the package, i.e. `vonage_messages.***` 3 | 4 | # 1.3.0 5 | - Add support for API key/secret header authentication 6 | - Updated dependency versions 7 | 8 | # 1.2.3 9 | - Update dependency versions 10 | 11 | # 1.2.2 12 | - Support for Python 3.13, drop support for 3.8 13 | 14 | # 1.2.1 15 | - Add docstrings to data models 16 | 17 | # 1.2.0 18 | - Add RCS channel support 19 | - Add methods to revoke an RCS message and mark a WhatsApp message as read 20 | 21 | # 1.1.1 22 | - Update minimum dependency version 23 | 24 | # 1.1.0 25 | - Add `http_client` property 26 | 27 | # 1.0.0 28 | - Initial upload 29 | -------------------------------------------------------------------------------- /messages/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-messages' 3 | dynamic = ["version"] 4 | description = 'Vonage messages package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.8", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Programming Language :: Python :: 3.12", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_messages._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /messages/src/vonage_messages/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /messages/src/vonage_messages/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models # Import models to access the module directly 2 | from .messages import Messages 3 | from .models import * # Need this to directly expose data models 4 | from .responses import SendMessageResponse 5 | 6 | __all__ = ['models', 'Messages', 'SendMessageResponse'] 7 | -------------------------------------------------------------------------------- /messages/src/vonage_messages/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.4.0' 2 | -------------------------------------------------------------------------------- /messages/src/vonage_messages/models/BUILD: -------------------------------------------------------------------------------- 1 | python_sources(name='models') 2 | -------------------------------------------------------------------------------- /messages/src/vonage_messages/models/base_message.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | from vonage_utils.types import PhoneNumber 5 | 6 | from .enums import WebhookVersion 7 | 8 | 9 | class BaseMessage(BaseModel): 10 | """Model with base properties for a message. 11 | 12 | Args: 13 | to (PhoneNumber): The recipient's phone number in E.164 format. Don't use a leading plus sign. 14 | client_ref (str, Optional): An optional client reference. 15 | webhook_url (str, Optional): The URL to which Status Webhook messages will be sent for this particular message. 16 | webhook_version (WebhookVersion, Optional): Which version of the Messages API will be used to send Status Webhook messages for this particular message. 17 | """ 18 | 19 | to: PhoneNumber 20 | client_ref: Optional[str] = Field(None, max_length=100) 21 | webhook_url: Optional[str] = None 22 | webhook_version: Optional[WebhookVersion] = None 23 | -------------------------------------------------------------------------------- /messages/src/vonage_messages/models/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class MessageType(str, Enum): 5 | """The type of message.""" 6 | 7 | TEXT = 'text' 8 | IMAGE = 'image' 9 | AUDIO = 'audio' 10 | VIDEO = 'video' 11 | FILE = 'file' 12 | TEMPLATE = 'template' 13 | STICKER = 'sticker' 14 | CUSTOM = 'custom' 15 | VCARD = 'vcard' 16 | 17 | 18 | class ChannelType(str, Enum): 19 | """The channel used to send a message.""" 20 | 21 | SMS = 'sms' 22 | MMS = 'mms' 23 | RCS = 'rcs' 24 | WHATSAPP = 'whatsapp' 25 | MESSENGER = 'messenger' 26 | VIBER = 'viber_service' 27 | 28 | 29 | class WebhookVersion(str, Enum): 30 | """Which version of the Messages API will be used to send Status Webhook messages.""" 31 | 32 | V0_1 = 'v0.1' 33 | V1 = 'v1' 34 | 35 | 36 | class EncodingType(str, Enum): 37 | TEXT = 'text' 38 | UNICODE = 'unicode' 39 | AUTO = 'auto' 40 | -------------------------------------------------------------------------------- /messages/src/vonage_messages/responses.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel 2 | 3 | 4 | class SendMessageResponse(BaseModel): 5 | """Response from Vonage's Messages API. 6 | 7 | Attributes: 8 | message_uuid (str): The UUID of the sent message. 9 | """ 10 | 11 | message_uuid: str 12 | -------------------------------------------------------------------------------- /messages/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['messages', 'testutils']) 2 | -------------------------------------------------------------------------------- /messages/tests/data/invalid_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.vonage.com/api-errors/messages#1150", 3 | "title": "Invalid params", 4 | "detail": "The value of one or more parameters is invalid.", 5 | "instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf", 6 | "invalid_parameters": [ 7 | { 8 | "name": "messenger.tag", 9 | "reason": "invalid value" 10 | } 11 | ] 12 | } -------------------------------------------------------------------------------- /messages/tests/data/low_balance_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.nexmo.com/api-errors/#low-balance", 3 | "title": "Low balance", 4 | "detail": "This request could not be performed due to your account balance being low.", 5 | "instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf" 6 | } -------------------------------------------------------------------------------- /messages/tests/data/not_found.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.vonage.com/api-errors#not-found", 3 | "title": "Not Found", 4 | "detail": "Message with ID asdf not found", 5 | "instance": "617431f2-06b7-4798-af36-1b8151df8359" 6 | } -------------------------------------------------------------------------------- /messages/tests/data/send_message.json: -------------------------------------------------------------------------------- 1 | { 2 | "message_uuid": "d8f86df1-dec6-442f-870a-2241be27d721" 3 | } -------------------------------------------------------------------------------- /messages/tests/test_sms_models.py: -------------------------------------------------------------------------------- 1 | from vonage_messages.models import Sms, SmsOptions 2 | from vonage_messages.models.enums import EncodingType, WebhookVersion 3 | 4 | 5 | def test_create_sms(): 6 | sms_model = Sms( 7 | to='1234567890', 8 | from_='1234567890', 9 | text='Hello, World!', 10 | ) 11 | sms_dict = { 12 | 'to': '1234567890', 13 | 'from': '1234567890', 14 | 'text': 'Hello, World!', 15 | 'channel': 'sms', 16 | 'message_type': 'text', 17 | } 18 | 19 | assert sms_model.model_dump(by_alias=True, exclude_none=True) == sms_dict 20 | 21 | 22 | def test_create_sms_all_fields(): 23 | sms_model = Sms( 24 | to='1234567890', 25 | from_='1234567890', 26 | text='Hello, World!', 27 | sms=SmsOptions( 28 | encoding_type=EncodingType.TEXT, 29 | content_id='content-id', 30 | entity_id='entity-id', 31 | ), 32 | client_ref='client-ref', 33 | webhook_url='https://example.com', 34 | webhook_version=WebhookVersion.V1, 35 | ttl=600, 36 | ) 37 | sms_dict = { 38 | 'to': '1234567890', 39 | 'from': '1234567890', 40 | 'text': 'Hello, World!', 41 | 'sms': { 42 | 'encoding_type': 'text', 43 | 'content_id': 'content-id', 44 | 'entity_id': 'entity-id', 45 | }, 46 | 'client_ref': 'client-ref', 47 | 'webhook_url': 'https://example.com', 48 | 'webhook_version': 'v1', 49 | 'ttl': 600, 50 | 'channel': 'sms', 51 | 'message_type': 'text', 52 | } 53 | 54 | assert sms_model.model_dump(by_alias=True) == sms_dict 55 | -------------------------------------------------------------------------------- /network_auth/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-network-auth', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'network_auth/src/vonage_network_auth', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /network_auth/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.0.2 2 | - Updated dependency versions 3 | 4 | # 1.0.1 5 | - Update dependency versions 6 | 7 | # 1.0.0 8 | - Add methods to work with the Vonage Number Verification API 9 | - Internal refactoring 10 | 11 | # 0.1.1b0 12 | - Add docstrings to data models 13 | 14 | # 0.1.0b0 15 | - Initial upload -------------------------------------------------------------------------------- /network_auth/README.md: -------------------------------------------------------------------------------- 1 | # Vonage Network API Authentication Client 2 | 3 | This package (`vonage-network-auth`) provides a client for authenticating Network APIs that require Oauth2 authentication. Using it, it is possible to generate authenticated JWTs for use with Vonage Network APIs, e.g. Sim Swap, Number Verification. 4 | 5 | This package is intended to be used as part of the `vonage` SDK package, accessing required methods through the SDK instead of directly. Thus, it doesn't require manual installation or configuration unless you're using this package independently of an SDK. 6 | 7 | For full API documentation, refer to the [Vonage developer documentation](https://developer.vonage.com). 8 | 9 | ## Installation 10 | 11 | Install from the Python Package Index with pip: 12 | 13 | ```bash 14 | pip install vonage-network-auth 15 | ``` 16 | 17 | ## Usage 18 | 19 | ### Create a `NetworkAuth` Object 20 | 21 | ```python 22 | from vonage_network_auth import NetworkAuth 23 | from vonage_http_client import HttpClient, Auth 24 | 25 | network_auth = NetworkAuth(HttpClient(Auth(application_id='application-id', private_key='private-key'))) 26 | ``` 27 | 28 | -------------------------------------------------------------------------------- /network_auth/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vonage-network-auth" 3 | dynamic = ["version"] 4 | description = "Package for working with Network APIs that require Oauth2 in Python." 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | Homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_network_auth._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /network_auth/src/vonage_network_auth/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /network_auth/src/vonage_network_auth/__init__.py: -------------------------------------------------------------------------------- 1 | from .network_auth import NetworkAuth 2 | from .requests import CreateOidcUrl 3 | from .responses import OidcResponse, TokenResponse 4 | 5 | __all__ = [ 6 | 'NetworkAuth', 7 | 'CreateOidcUrl', 8 | 'OidcResponse', 9 | 'TokenResponse', 10 | ] 11 | -------------------------------------------------------------------------------- /network_auth/src/vonage_network_auth/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.2' 2 | -------------------------------------------------------------------------------- /network_auth/src/vonage_network_auth/requests.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class CreateOidcUrl(BaseModel): 7 | """Model to craft a URL for OIDC authentication. 8 | 9 | Args: 10 | redirect_uri (str): The URI to redirect to after authentication. 11 | state (str): A unique identifier for the request. Can be any string. 12 | login_hint (str): The phone number to use for the request. 13 | """ 14 | 15 | redirect_uri: str 16 | state: str 17 | login_hint: str 18 | scope: Optional[str] = ( 19 | 'openid dpv:FraudPreventionAndDetection#number-verification-verify-read' 20 | ) 21 | -------------------------------------------------------------------------------- /network_auth/src/vonage_network_auth/responses.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class OidcResponse(BaseModel): 7 | """Model for an OpenID Connect response. 8 | 9 | Args: 10 | auth_req_id (str): The authentication request ID. 11 | expires_in (int): The time in seconds until the authentication code expires. 12 | interval (int, Optional): The time in seconds until the next request can be made. 13 | """ 14 | 15 | auth_req_id: str 16 | expires_in: int 17 | interval: Optional[int] = None 18 | 19 | 20 | class TokenResponse(BaseModel): 21 | """Model for a token response. 22 | 23 | Args: 24 | access_token (str): The access token. 25 | token_type (str, Optional): The token type. 26 | refresh_token (str, Optional): The refresh token. 27 | expires_in (int, Optional): The time until the token expires. 28 | """ 29 | 30 | access_token: str 31 | token_type: Optional[str] = None 32 | refresh_token: Optional[str] = None 33 | expires_in: Optional[int] = None 34 | -------------------------------------------------------------------------------- /network_auth/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['network_auth', 'testutils']) 2 | -------------------------------------------------------------------------------- /network_auth/tests/data/oidc_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "auth_req_id": "arid/8b0d35f3-4627-487c-a776-aegtdsf4rsd2", 3 | "expires_in": 300, 4 | "interval": 0 5 | } -------------------------------------------------------------------------------- /network_auth/tests/data/oidc_request_permissions_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.vonage.com/api-errors#invalid-param", 3 | "title": "Bad Request", 4 | "detail": "No Network Application associated with Vonage Application: 29f760f8-7ce1-46c9-ade3-f2dedee4ed5f", 5 | "instance": "b45ae630-7621-42b0-8ff0-6c1ad98e6e32" 6 | } -------------------------------------------------------------------------------- /network_auth/tests/data/token_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "access_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vYW51YmlzLWNlcnRzLWMxLWV1dzEucHJvZC52MS52b25hZ2VuZXR3b3Jrcy5uZXQvandrcyIsImtpZCI6IkNOPVZvbmFnZSAxdmFwaWd3IEludGVybmFsIENBOjoxOTUxODQ2ODA3NDg1NTYwNjYzODY3MTM0NjE2MjU2MTU5MjU2NDkiLCJ0eXAiOiJKV1QiLCJ4NXUiOiJodHRwczovL2FudWJpcy1jZXJ0cy1jMS1ldXcxLnByb2QudjEudm9uYWdlbmV0d29ya3MubmV0L3YxL2NlcnRzLzA4NjliNDMyZTEzZmIyMzcwZTk2ZGI4YmUxMDc4MjJkIn0.eyJwcmluY2lwYWwiOnsiYXBpS2V5IjoiNGI1MmMwMGUiLCJhcHBsaWNhdGlvbklkIjoiMmJlZTViZWQtNmZlZS00ZjM2LTkxNmQtNWUzYjRjZDI1MjQzIiwibWFzdGVyQWNjb3VudElkIjoiNGI1MmMwMGUiLCJjYXBhYmlsaXRpZXMiOlsibmV0d29yay1hcGktZmVhdHVyZXMiXSwiZXh0cmFDb25maWciOnsiY2FtYXJhU3RhdGUiOiJmb0ZyQndnOFNmeGMydnd2S1o5Y3UrMlgrT0s1K2FvOWhJTTVGUGZMQ1dOeUlMTHR3WmY1dFRKbDdUc1p4QnY4QWx3aHM2bFNWcGVvVkhoWngvM3hUenFRWVkwcHpIZE5XL085ZEdRN1RKOE9sU1lDdTFYYXFEcnNFbEF4WEJVcUpGdnZTTkp5a1A5ZDBYWVN4ajZFd0F6UUFsNGluQjE1c3VMRFNsKy82U1FDa29Udnpld0tvcFRZb0F5MVg2dDJVWXdEVWFDNjZuOS9kVWxIemN3V0NGK3QwOGNReGxZVUxKZyt3T0hwV2xvWGx1MGc3REx0SCtHd0pvRGJoYnMyT2hVY3BobGZqajBpeHQ1OTRsSG5sQ1NYNkZrMmhvWEhKUW01S3JtOVBKSmttK0xTRjVsRTd3NUxtWTRvYTFXSGpkY0dwV1VsQlNQY000YnprOGU0bVE9PSJ9fSwiZmVkZXJhdGVkQXNzZXJ0aW9ucyI6e30sImF1ZCI6ImFwaS1ldS52b25hZ2UuY29tIiwiZXhwIjoxNzE3MDkyODY4LCJqdGkiOiJmNDZhYTViOC1hODA2LTRjMzctODQyMS02OGYwMzJjNDlhMWYiLCJpYXQiOjE3MTcwOTE5NzAsImlzcyI6IlZJQU0tSUFQIiwibmJmIjoxNzE3MDkxOTU1fQ.iLUbyDPR1HGLKh29fy6fqK65Q1O7mjWOletAEPJD4eu7gb0E85EL4M9R7ckJq5lIvgedQt3vBheTaON9_u-VYjMqo8ulPoEoGUDHbOzNbs4MmCW0_CRdDPGyxnUhvcbuJhPgnEHxmfHjJBljncUnk-Z7XCgyNajBNXeQQnHkRF_6NMngxJ-qjjhqbYL0VsF_JS7-TXxixNL0KAFl0SeN2DjkfwRBCclP-69CTExDjyOvouAcchqi-6ZYj_tXPCrTADuzUrQrW8C5nHp2-XjWJSFKzyvi48n8V1U6KseV-eYzBzvy7bJf0tRMX7G6gctTYq3DxdC_eXvXlnp1zx16mg", 3 | "token_type": "bearer", 4 | "expires_in": 29 5 | } -------------------------------------------------------------------------------- /network_number_verification/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-network-number-verification', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'network_number_verification/src/vonage_network_number_verification', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /network_number_verification/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.0.2 2 | - Updated dependency versions 3 | 4 | # 1.0.1 5 | - Update dependency versions 6 | 7 | # 1.0.0 8 | - Initial upload -------------------------------------------------------------------------------- /network_number_verification/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vonage-network-number-verification" 3 | dynamic = ["version"] 4 | description = "Package for working with the Vonage Number Verification Network API." 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-network-auth>=1.0.2", 11 | "vonage-utils>=1.1.4", 12 | "pydantic>=2.9.2", 13 | ] 14 | classifiers = [ 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Programming Language :: Python :: 3.12", 21 | "Programming Language :: Python :: 3.13", 22 | "License :: OSI Approved :: Apache Software License", 23 | ] 24 | 25 | [project.urls] 26 | Homepage = "https://github.com/Vonage/vonage-python-sdk" 27 | 28 | [tool.setuptools.dynamic] 29 | version = { attr = "vonage_network_number_verification._version.__version__" } 30 | 31 | [build-system] 32 | requires = ["setuptools>=61.0", "wheel"] 33 | build-backend = "setuptools.build_meta" 34 | -------------------------------------------------------------------------------- /network_number_verification/src/vonage_network_number_verification/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /network_number_verification/src/vonage_network_number_verification/__init__.py: -------------------------------------------------------------------------------- 1 | from .errors import NetworkNumberVerificationError 2 | from .number_verification import CreateOidcUrl, NetworkNumberVerification 3 | from .requests import NumberVerificationRequest 4 | from .responses import NumberVerificationResponse 5 | 6 | __all__ = [ 7 | 'NetworkNumberVerification', 8 | 'CreateOidcUrl', 9 | 'NumberVerificationRequest', 10 | 'NumberVerificationResponse', 11 | 'NetworkNumberVerificationError', 12 | ] 13 | -------------------------------------------------------------------------------- /network_number_verification/src/vonage_network_number_verification/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.2' 2 | -------------------------------------------------------------------------------- /network_number_verification/src/vonage_network_number_verification/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils import VonageError 2 | 3 | 4 | class NetworkNumberVerificationError(VonageError): 5 | """Base class for Vonage Network Number Verification errors.""" 6 | -------------------------------------------------------------------------------- /network_number_verification/src/vonage_network_number_verification/requests.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field, model_validator 2 | from vonage_network_number_verification.errors import NetworkNumberVerificationError 3 | 4 | 5 | class NumberVerificationRequest(BaseModel): 6 | """Model for the request to verify a phone number. 7 | 8 | Args: 9 | code (str): The code returned from the OIDC redirect. 10 | redirect_uri (str): The URI to redirect to after authentication. 11 | phone_number (str): The phone number to verify. Use the E.164 format with 12 | or without a leading +. 13 | hashed_phone_number (str): The hashed phone number to verify. 14 | """ 15 | 16 | code: str 17 | redirect_uri: str 18 | phone_number: str = Field(None, serialization_alias='phoneNumber') 19 | hashed_phone_number: str = Field(None, serialization_alias='hashedPhoneNumber') 20 | 21 | @model_validator(mode='after') 22 | def check_only_one_phone_number(self): 23 | """Check that only one of `phone_number` and `hashed_phone_number` is set.""" 24 | 25 | if self.phone_number is not None and self.hashed_phone_number is not None: 26 | raise NetworkNumberVerificationError( 27 | 'Only one of `phone_number` and `hashed_phone_number` can be set.' 28 | ) 29 | 30 | if self.phone_number is None and self.hashed_phone_number is None: 31 | raise NetworkNumberVerificationError( 32 | 'One of `phone_number` and `hashed_phone_number` must be set.' 33 | ) 34 | 35 | return self 36 | -------------------------------------------------------------------------------- /network_number_verification/src/vonage_network_number_verification/responses.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | 4 | class NumberVerificationResponse(BaseModel): 5 | """Model for the response from the Number Verification API. 6 | 7 | Args: 8 | device_phone_number_verified (bool): Whether the phone number has been 9 | successfully verified. 10 | """ 11 | 12 | device_phone_number_verified: bool = Field( 13 | ..., validation_alias='devicePhoneNumberVerified' 14 | ) 15 | -------------------------------------------------------------------------------- /network_number_verification/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['network_number_verification', 'testutils']) 2 | -------------------------------------------------------------------------------- /network_number_verification/tests/data/token_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "access_token": "eyJhbGciOiJSUzI1NiIsImprdSI6Imh0dHBzOi8vYW51YmlzLWNlcnRzLWMxLWV1dzEucHJvZC52MS52b25hZ2VuZXR3b3Jrcy5uZXQvandrcyIsImtpZCI6IkNOPVZvbmFnZSAxdmFwaWd3IEludGVybmFsIENBOjoxOTUxODQ2ODA3NDg1NTYwNjYzODY3MTM0NjE2MjU2MTU5MjU2NDkiLCJ0eXAiOiJKV1QiLCJ4NXUiOiJodHRwczovL2FudWJpcy1jZXJ0cy1jMS1ldXcxLnByb2QudjEudm9uYWdlbmV0d29ya3MubmV0L3YxL2NlcnRzLzA4NjliNDMyZTEzZmIyMzcwZTk2ZGI4YmUxMDc4MjJkIn0.eyJwcmluY2lwYWwiOnsiYXBpS2V5IjoiNGI1MmMwMGUiLCJhcHBsaWNhdGlvbklkIjoiMmJlZTViZWQtNmZlZS00ZjM2LTkxNmQtNWUzYjRjZDI1MjQzIiwibWFzdGVyQWNjb3VudElkIjoiNGI1MmMwMGUiLCJjYXBhYmlsaXRpZXMiOlsibmV0d29yay1hcGktZmVhdHVyZXMiXSwiZXh0cmFDb25maWciOnsiY2FtYXJhU3RhdGUiOiJmb0ZyQndnOFNmeGMydnd2S1o5Y3UrMlgrT0s1K2FvOWhJTTVGUGZMQ1dOeUlMTHR3WmY1dFRKbDdUc1p4QnY4QWx3aHM2bFNWcGVvVkhoWngvM3hUenFRWVkwcHpIZE5XL085ZEdRN1RKOE9sU1lDdTFYYXFEcnNFbEF4WEJVcUpGdnZTTkp5a1A5ZDBYWVN4ajZFd0F6UUFsNGluQjE1c3VMRFNsKy82U1FDa29Udnpld0tvcFRZb0F5MVg2dDJVWXdEVWFDNjZuOS9kVWxIemN3V0NGK3QwOGNReGxZVUxKZyt3T0hwV2xvWGx1MGc3REx0SCtHd0pvRGJoYnMyT2hVY3BobGZqajBpeHQ1OTRsSG5sQ1NYNkZrMmhvWEhKUW01S3JtOVBKSmttK0xTRjVsRTd3NUxtWTRvYTFXSGpkY0dwV1VsQlNQY000YnprOGU0bVE9PSJ9fSwiZmVkZXJhdGVkQXNzZXJ0aW9ucyI6e30sImF1ZCI6ImFwaS1ldS52b25hZ2UuY29tIiwiZXhwIjoxNzE3MDkyODY4LCJqdGkiOiJmNDZhYTViOC1hODA2LTRjMzctODQyMS02OGYwMzJjNDlhMWYiLCJpYXQiOjE3MTcwOTE5NzAsImlzcyI6IlZJQU0tSUFQIiwibmJmIjoxNzE3MDkxOTU1fQ.iLUbyDPR1HGLKh29fy6fqK65Q1O7mjWOletAEPJD4eu7gb0E85EL4M9R7ckJq5lIvgedQt3vBheTaON9_u-VYjMqo8ulPoEoGUDHbOzNbs4MmCW0_CRdDPGyxnUhvcbuJhPgnEHxmfHjJBljncUnk-Z7XCgyNajBNXeQQnHkRF_6NMngxJ-qjjhqbYL0VsF_JS7-TXxixNL0KAFl0SeN2DjkfwRBCclP-69CTExDjyOvouAcchqi-6ZYj_tXPCrTADuzUrQrW8C5nHp2-XjWJSFKzyvi48n8V1U6KseV-eYzBzvy7bJf0tRMX7G6gctTYq3DxdC_eXvXlnp1zx16mg", 3 | "token_type": "bearer", 4 | "expires_in": 29 5 | } -------------------------------------------------------------------------------- /network_number_verification/tests/data/verify_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "devicePhoneNumberVerified": true 3 | } -------------------------------------------------------------------------------- /network_sim_swap/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-network-sim-swap', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'network_sim_swap/src/vonage_network_sim_swap', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /network_sim_swap/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.1.2 2 | - Updated dependency versions 3 | 4 | # 1.1.1 5 | - Update dependency versions 6 | 7 | # 1.1.0 8 | - Add new model `SimSwapCheckRequest` to replace arguments in the `SimSwap.check` method 9 | 10 | # 1.0.0 11 | - Support for Python 3.13, drop support for 3.8 12 | 13 | # 0.1.1b0 14 | - Add docstrings to data models 15 | 16 | # 0.1.0b0 17 | - Initial upload -------------------------------------------------------------------------------- /network_sim_swap/README.md: -------------------------------------------------------------------------------- 1 | # Vonage Sim Swap Network API Client 2 | 3 | This package (`vonage-network-sim-swap`) allows you to check whether a SIM card has been swapped, and the last swap date. 4 | 5 | This package is not intended to be used directly, instead being accessed from an enclosing SDK package. Thus, it doesn't require manual installation or configuration unless you're using this package independently of an SDK. 6 | 7 | For full API documentation, refer to the [Vonage developer documentation](https://developer.vonage.com). 8 | 9 | ## Registering to Use the Sim Swap API 10 | 11 | To use this API, you must first create and register your business profile with the Vonage Network Registry. [This documentation page](https://developer.vonage.com/en/getting-started-network/registration) explains how this can be done. You need to obtain approval for each network and region you want to use the APIs in. 12 | 13 | ## Installation 14 | 15 | Install from the Python Package Index with pip: 16 | 17 | ```bash 18 | pip install vonage-network-sim-swap 19 | ``` 20 | 21 | ## Usage 22 | 23 | It is recommended to use this as part of the `vonage` package. The examples below assume you've created an instance of the `vonage.Vonage` class called `vonage_client`. 24 | 25 | ### Check if a SIM Has Been Swapped 26 | 27 | ```python 28 | from vonage_network_sim_swap import SwapStatus 29 | swap_status: SwapStatus = vonage_client.sim_swap.check(phone_number='MY_NUMBER') 30 | print(swap_status.swapped) 31 | ``` 32 | 33 | ### Get the Date of the Last SIM Swap 34 | 35 | ```python 36 | from vonage_network_sim_swap import LastSwapDate 37 | swap_date: LastSwapDate = vonage_client.sim_swap.get_last_swap_date 38 | print(swap_date.last_swap_date) 39 | ``` -------------------------------------------------------------------------------- /network_sim_swap/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vonage-network-sim-swap" 3 | dynamic = ["version"] 4 | description = "Package for working with the Vonage Sim Swap Network API." 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-network-auth>=1.0.2", 11 | "vonage-utils>=1.1.4", 12 | "pydantic>=2.9.2", 13 | ] 14 | classifiers = [ 15 | "Programming Language :: Python", 16 | "Programming Language :: Python :: 3", 17 | "Programming Language :: Python :: 3.9", 18 | "Programming Language :: Python :: 3.10", 19 | "Programming Language :: Python :: 3.11", 20 | "Programming Language :: Python :: 3.12", 21 | "Programming Language :: Python :: 3.13", 22 | "License :: OSI Approved :: Apache Software License", 23 | ] 24 | 25 | [project.urls] 26 | Homepage = "https://github.com/Vonage/vonage-python-sdk" 27 | 28 | [tool.setuptools.dynamic] 29 | version = { attr = "vonage_network_sim_swap._version.__version__" } 30 | 31 | [build-system] 32 | requires = ["setuptools>=61.0", "wheel"] 33 | build-backend = "setuptools.build_meta" 34 | -------------------------------------------------------------------------------- /network_sim_swap/src/vonage_network_sim_swap/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /network_sim_swap/src/vonage_network_sim_swap/__init__.py: -------------------------------------------------------------------------------- 1 | from .requests import SimSwapCheckRequest 2 | from .responses import LastSwapDate, SwapStatus 3 | from .sim_swap import NetworkSimSwap 4 | 5 | __all__ = ['NetworkSimSwap', 'LastSwapDate', 'SimSwapCheckRequest', 'SwapStatus'] 6 | -------------------------------------------------------------------------------- /network_sim_swap/src/vonage_network_sim_swap/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.1.2' 2 | -------------------------------------------------------------------------------- /network_sim_swap/src/vonage_network_sim_swap/requests.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class SimSwapCheckRequest(BaseModel): 7 | """Request model to check if a SIM has been swapped using the Vonage Sim Swap Network 8 | API. 9 | 10 | Args: 11 | phone_number (str): The phone number to check. Use the E.164 format with 12 | or without a leading +. 13 | max_age (int, optional): Period in hours to be checked for SIM swap. 14 | """ 15 | 16 | phone_number: str = Field(..., serialization_alias='phoneNumber') 17 | max_age: Optional[int] = Field(None, serialization_alias='maxAge') 18 | -------------------------------------------------------------------------------- /network_sim_swap/src/vonage_network_sim_swap/responses.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | 4 | class SwapStatus(BaseModel): 5 | """Model for the status of a SIM swap. 6 | 7 | Args: 8 | swapped (str): Indicates whether the SIM card has been swapped during the period 9 | within the `max_age` provided in the request. 10 | """ 11 | 12 | swapped: str 13 | 14 | 15 | class LastSwapDate(BaseModel): 16 | """Model for the last SIM swap date information. 17 | 18 | Args: 19 | last_swap_date (str): The timestamp of the latest SIM swap performed. 20 | """ 21 | 22 | last_swap_date: str = Field(..., validation_alias='latestSimChange') 23 | -------------------------------------------------------------------------------- /network_sim_swap/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['network_sim_swap', 'testutils']) 2 | -------------------------------------------------------------------------------- /network_sim_swap/tests/data/check_sim_swap.json: -------------------------------------------------------------------------------- 1 | { 2 | "swapped": true 3 | } -------------------------------------------------------------------------------- /network_sim_swap/tests/data/get_swap_date.json: -------------------------------------------------------------------------------- 1 | { 2 | "latestSimChange": "2023-12-22T04:00:44.000Z" 3 | } -------------------------------------------------------------------------------- /network_sim_swap/tests/test_sim_swap.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath 2 | from unittest.mock import MagicMock, patch 3 | 4 | import responses 5 | from vonage_http_client.http_client import HttpClient 6 | from vonage_network_sim_swap import NetworkSimSwap 7 | from vonage_network_sim_swap.requests import SimSwapCheckRequest 8 | 9 | from testutils import build_response, get_mock_jwt_auth 10 | 11 | path = abspath(__file__) 12 | 13 | sim_swap = NetworkSimSwap(HttpClient(get_mock_jwt_auth())) 14 | 15 | 16 | def test_http_client_property(): 17 | http_client = sim_swap.http_client 18 | assert isinstance(http_client, HttpClient) 19 | 20 | 21 | @patch('vonage_network_auth.NetworkAuth.get_sim_swap_camara_token') 22 | @responses.activate 23 | def test_check_sim_swap(mock_get_oauth2_user_token: MagicMock): 24 | build_response( 25 | path, 26 | 'POST', 27 | 'https://api-eu.vonage.com/camara/sim-swap/v040/check', 28 | 'check_sim_swap.json', 29 | ) 30 | mock_get_oauth2_user_token.return_value = 'token' 31 | 32 | response = sim_swap.check( 33 | SimSwapCheckRequest(phone_number='447700900000', max_age=24) 34 | ) 35 | 36 | assert response['swapped'] == True 37 | 38 | 39 | @patch('vonage_network_auth.NetworkAuth.get_sim_swap_camara_token') 40 | @responses.activate 41 | def test_get_last_swap_date(mock_get_oauth2_user_token: MagicMock): 42 | build_response( 43 | path, 44 | 'POST', 45 | 'https://api-eu.vonage.com/camara/sim-swap/v040/retrieve-date', 46 | 'get_swap_date.json', 47 | ) 48 | mock_get_oauth2_user_token.return_value = 'token' 49 | 50 | response = sim_swap.get_last_swap_date('447700900000') 51 | 52 | assert response['latestSimChange'] == '2023-12-22T04:00:44.000Z' 53 | -------------------------------------------------------------------------------- /number_insight/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-number-insight', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'number_insight/src/vonage_number_insight', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /number_insight/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.0.7 2 | - Use basic header auth instead of request body auth 3 | 4 | # 1.0.6 5 | - Updated dependency versions 6 | 7 | # 1.0.5 8 | - Fix missed method renaming 9 | - Docstring update 10 | 11 | # 1.0.4 12 | - Update dependency versions 13 | 14 | # 1.0.3 15 | - Rename `basic_number_insight` -> `get_basic_info`, `standard_number_insight` -> `get_standard_info`, `advanced_async_number_insight` -> `get_advanced_info_async`, `advanced_sync_number_insight` -> `get_advanced_info_sync` 16 | 17 | # 1.0.2 18 | - Support for Python 3.13, drop support for 3.8 19 | 20 | # 1.0.1 21 | - Add docstrings to data models 22 | 23 | # 1.0.0 24 | - Initial upload 25 | -------------------------------------------------------------------------------- /number_insight/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-number-insight' 3 | dynamic = ["version"] 4 | description = 'Vonage Number Insight package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_number_insight._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /number_insight/src/vonage_number_insight/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /number_insight/src/vonage_number_insight/__init__.py: -------------------------------------------------------------------------------- 1 | from . import errors 2 | from .number_insight import NumberInsight 3 | from .requests import ( 4 | AdvancedAsyncInsightRequest, 5 | AdvancedSyncInsightRequest, 6 | BasicInsightRequest, 7 | StandardInsightRequest, 8 | ) 9 | from .responses import ( 10 | AdvancedAsyncInsightResponse, 11 | AdvancedSyncInsightResponse, 12 | BasicInsightResponse, 13 | CallerIdentity, 14 | Carrier, 15 | RoamingStatus, 16 | StandardInsightResponse, 17 | ) 18 | 19 | __all__ = [ 20 | 'NumberInsight', 21 | 'BasicInsightRequest', 22 | 'StandardInsightRequest', 23 | 'AdvancedAsyncInsightRequest', 24 | 'AdvancedSyncInsightRequest', 25 | 'BasicInsightResponse', 26 | 'CallerIdentity', 27 | 'Carrier', 28 | 'RoamingStatus', 29 | 'StandardInsightResponse', 30 | 'AdvancedSyncInsightResponse', 31 | 'AdvancedAsyncInsightResponse', 32 | 'errors', 33 | ] 34 | -------------------------------------------------------------------------------- /number_insight/src/vonage_number_insight/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.7' 2 | -------------------------------------------------------------------------------- /number_insight/src/vonage_number_insight/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class NumberInsightError(VonageError): 5 | """Indicates an error when using the Vonage Number Insight API.""" 6 | -------------------------------------------------------------------------------- /number_insight/src/vonage_number_insight/requests.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | from vonage_utils.types import PhoneNumber 5 | 6 | 7 | class BasicInsightRequest(BaseModel): 8 | """Model for a basic number insight request. 9 | 10 | Args: 11 | number (PhoneNumber): The phone number to get insight information for. 12 | country (str, Optional): The country code for the phone number. 13 | """ 14 | 15 | number: PhoneNumber 16 | country: Optional[str] = None 17 | 18 | 19 | class StandardInsightRequest(BasicInsightRequest): 20 | """Model for a standard number insight request. 21 | 22 | Args: 23 | number (PhoneNumber): The phone number to get insight information for. 24 | country (str, Optional): The country code for the phone number. 25 | cnam (bool, Optional): Whether to include the Caller ID Name (CNAM) with the response. 26 | """ 27 | 28 | cnam: Optional[bool] = None 29 | 30 | 31 | class AdvancedAsyncInsightRequest(StandardInsightRequest): 32 | """Model for an advanced asynchronous number insight request. 33 | 34 | Args: 35 | number (PhoneNumber): The phone number to get insight information for. 36 | callback (str): The URL to send the asynchronous response to. 37 | country (str, Optional): The country code for the phone number. 38 | cnam (bool, Optional): Whether to include the Caller ID Name (CNAM) with the response. 39 | """ 40 | 41 | callback: str 42 | 43 | 44 | class AdvancedSyncInsightRequest(StandardInsightRequest): 45 | """Model for an advanced synchronous number insight request. 46 | 47 | Args: 48 | number (PhoneNumber): The phone number to get insight information for. 49 | country (str, Optional): The country code for the phone number. 50 | cnam (bool, Optional): Whether to include the Caller ID Name (CNAM) with the response. 51 | """ 52 | -------------------------------------------------------------------------------- /number_insight/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['number_insight', 'testutils']) 2 | -------------------------------------------------------------------------------- /number_insight/tests/data/advanced_async_insight.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": "447700900000", 3 | "remaining_balance": "32.92665294", 4 | "request_id": "434205b5-90ec-4ee2-a337-7b40d9683420", 5 | "request_price": "0.04000000", 6 | "status": 0 7 | } -------------------------------------------------------------------------------- /number_insight/tests/data/advanced_async_insight_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "error_text": "Invalid credentials", 3 | "status": 4 4 | } -------------------------------------------------------------------------------- /number_insight/tests/data/advanced_async_insight_partial_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "error_text": "Live mobile lookup not returned", 3 | "status": 43, 4 | "number": "447700900000", 5 | "remaining_balance": "32.92665294", 6 | "request_id": "434205b5-90ec-4ee2-a337-7b40d9683420", 7 | "request_price": "0.04000000" 8 | } -------------------------------------------------------------------------------- /number_insight/tests/data/advanced_sync_insight.json: -------------------------------------------------------------------------------- 1 | { 2 | "caller_identity": { 3 | "caller_name": "John Smith", 4 | "caller_type": "consumer", 5 | "first_name": "John", 6 | "last_name": "Smith", 7 | "subscription_type": "postpaid" 8 | }, 9 | "caller_name": "John Smith", 10 | "caller_type": "consumer", 11 | "country_code": "US", 12 | "country_code_iso3": "USA", 13 | "country_name": "United States of America", 14 | "country_prefix": "1", 15 | "current_carrier": { 16 | "country": "US", 17 | "name": "AT&T Mobility", 18 | "network_code": "310090", 19 | "network_type": "mobile" 20 | }, 21 | "first_name": "John", 22 | "international_format_number": "12345678900", 23 | "ip_warnings": "unknown", 24 | "last_name": "Smith", 25 | "lookup_outcome": 1, 26 | "lookup_outcome_message": "Partial success - some fields populated", 27 | "national_format_number": "(234) 567-8900", 28 | "original_carrier": { 29 | "country": "US", 30 | "name": "AT&T Mobility", 31 | "network_code": "310090", 32 | "network_type": "mobile" 33 | }, 34 | "ported": "not_ported", 35 | "reachable": "unknown", 36 | "refund_price": "0.01025000", 37 | "remaining_balance": "32.68590294", 38 | "request_id": "97e973e7-2e27-4fd3-9e1a-972ea14dd992", 39 | "request_price": "0.05025000", 40 | "roaming": "unknown", 41 | "status": 44, 42 | "status_message": "Lookup Handler unable to handle request", 43 | "valid_number": "valid" 44 | } -------------------------------------------------------------------------------- /number_insight/tests/data/basic_insight.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 0, 3 | "status_message": "Success", 4 | "request_id": "7f4a8a16-aa89-4078-b0ae-7743da34aca5", 5 | "international_format_number": "12345678900", 6 | "national_format_number": "(234) 567-8900", 7 | "country_code": "US", 8 | "country_code_iso3": "USA", 9 | "country_name": "United States of America", 10 | "country_prefix": "1" 11 | } -------------------------------------------------------------------------------- /number_insight/tests/data/basic_insight_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 3, 3 | "status_message": "Invalid request :: Not valid number format detected [ 145645562 ]" 4 | } -------------------------------------------------------------------------------- /number_insight/tests/data/standard_insight.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": 0, 3 | "status_message": "Success", 4 | "request_id": "1d56406b-9d52-497a-a023-b3f40b62f9b3", 5 | "international_format_number": "447700900000", 6 | "national_format_number": "07700 900000", 7 | "country_code": "GB", 8 | "country_code_iso3": "GBR", 9 | "country_name": "United Kingdom", 10 | "country_prefix": "44", 11 | "request_price": "0.00500000", 12 | "remaining_balance": "32.98665294", 13 | "current_carrier": { 14 | "network_code": "23415", 15 | "name": "Vodafone Limited", 16 | "country": "GB", 17 | "network_type": "mobile" 18 | }, 19 | "original_carrier": { 20 | "network_code": "23420", 21 | "name": "Hutchison 3G Ltd", 22 | "country": "GB", 23 | "network_type": "mobile" 24 | }, 25 | "ported": "ported", 26 | "caller_identity": { 27 | "caller_type": "consumer", 28 | "caller_name": "John Smith", 29 | "first_name": "John", 30 | "last_name": "Smith" 31 | }, 32 | "caller_name": "John Smith", 33 | "last_name": "Smith", 34 | "first_name": "John", 35 | "caller_type": "consumer" 36 | } -------------------------------------------------------------------------------- /number_management/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-numbers', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'number_management/src/vonage_numbers', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /number_management/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.0.4 2 | - Updated dependency versions 3 | 4 | # 1.0.3 5 | - Update dependency versions 6 | 7 | # 1.0.2 8 | - Support for Python 3.13, drop support for 3.8 9 | 10 | # 1.0.1 11 | - Add docstrings for data models 12 | 13 | # 1.0.0 14 | - Initial upload 15 | -------------------------------------------------------------------------------- /number_management/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-numbers' 3 | dynamic = ["version"] 4 | description = 'Vonage Numbers package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_numbers._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /number_management/src/vonage_numbers/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /number_management/src/vonage_numbers/__init__.py: -------------------------------------------------------------------------------- 1 | from .enums import NumberFeatures, NumberType, VoiceCallbackType 2 | from .errors import NumbersError 3 | from .number_management import Numbers 4 | from .requests import ( 5 | ListOwnedNumbersFilter, 6 | NumberParams, 7 | SearchAvailableNumbersFilter, 8 | UpdateNumberParams, 9 | ) 10 | from .responses import AvailableNumber, NumbersStatus, OwnedNumber 11 | 12 | __all__ = [ 13 | 'NumberFeatures', 14 | 'NumberType', 15 | 'VoiceCallbackType', 16 | 'NumbersError', 17 | 'Numbers', 18 | 'ListOwnedNumbersFilter', 19 | 'NumberParams', 20 | 'SearchAvailableNumbersFilter', 21 | 'UpdateNumberParams', 22 | 'AvailableNumber', 23 | 'NumbersStatus', 24 | 'OwnedNumber', 25 | ] 26 | -------------------------------------------------------------------------------- /number_management/src/vonage_numbers/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.4' 2 | -------------------------------------------------------------------------------- /number_management/src/vonage_numbers/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class NumberType(str, Enum): 5 | LANDLINE = 'landline' 6 | MOBILE_LVN = 'mobile-lvn' 7 | LANDLINE_TOLL_FREE = 'landline-toll-free' 8 | 9 | 10 | class NumberFeatures(str, Enum): 11 | SMS = 'SMS' 12 | VOICE = 'VOICE' 13 | MMS = 'MMS' 14 | SMS_VOICE = 'SMS,VOICE' 15 | SMS_MMS = 'SMS,MMS' 16 | VOICE_MMS = 'VOICE,MMS' 17 | SMS_VOICE_MMS = 'SMS,VOICE,MMS' 18 | 19 | 20 | class VoiceCallbackType(str, Enum): 21 | SIP = 'sip' 22 | TEL = 'tel' 23 | -------------------------------------------------------------------------------- /number_management/src/vonage_numbers/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class NumbersError(VonageError): 5 | """Indicates an error with the Numbers API package.""" 6 | -------------------------------------------------------------------------------- /number_management/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['number_management', 'testutils']) 2 | -------------------------------------------------------------------------------- /number_management/tests/data/list_owned_numbers_basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "numbers": [ 4 | { 5 | "country": "ES", 6 | "msisdn": "3400000000", 7 | "type": "mobile-lvn", 8 | "features": [ 9 | "SMS" 10 | ] 11 | }, 12 | { 13 | "country": "GB", 14 | "msisdn": "447007000000", 15 | "type": "mobile-lvn", 16 | "features": [ 17 | "VOICE", 18 | "SMS" 19 | ], 20 | "voiceCallbackType": "app", 21 | "voiceCallbackValue": "29f769u7-7ce1-46c9-ade3-f2dedee4fr4t", 22 | "app_id": "29f769u7-7ce1-46c9-ade3-f2dedee4fr4t" 23 | } 24 | ] 25 | } -------------------------------------------------------------------------------- /number_management/tests/data/list_owned_numbers_filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "numbers": [ 4 | { 5 | "country": "GB", 6 | "msisdn": "447007000000", 7 | "type": "mobile-lvn", 8 | "features": [ 9 | "VOICE", 10 | "SMS" 11 | ], 12 | "voiceCallbackType": "app", 13 | "voiceCallbackValue": "29f769u7-7ce1-46c9-ade3-f2dedee4fr4t", 14 | "app_id": "29f769u7-7ce1-46c9-ade3-f2dedee4fr4t" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /number_management/tests/data/list_owned_numbers_subset.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "numbers": [ 4 | { 5 | "country": "ES", 6 | "msisdn": "3400000000", 7 | "type": "mobile-lvn", 8 | "features": [ 9 | "SMS" 10 | ] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /number_management/tests/data/no_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "error-code": "420", 3 | "error-code-label": "method failed" 4 | } -------------------------------------------------------------------------------- /number_management/tests/data/nothing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /number_management/tests/data/number.json: -------------------------------------------------------------------------------- 1 | { 2 | "error-code": "200", 3 | "error-code-label": "success" 4 | } -------------------------------------------------------------------------------- /number_management/tests/data/search_available_numbers_basic.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 8353, 3 | "numbers": [ 4 | { 5 | "country": "GB", 6 | "msisdn": "442039050911", 7 | "cost": "1.00", 8 | "type": "landline", 9 | "features": [ 10 | "VOICE" 11 | ] 12 | }, 13 | { 14 | "country": "GB", 15 | "msisdn": "442039051911", 16 | "cost": "1.00", 17 | "type": "landline", 18 | "features": [ 19 | "VOICE" 20 | ] 21 | }, 22 | { 23 | "country": "GB", 24 | "msisdn": "442039052911", 25 | "cost": "1.00", 26 | "type": "landline", 27 | "features": [ 28 | "VOICE" 29 | ] 30 | } 31 | ] 32 | } -------------------------------------------------------------------------------- /number_management/tests/data/search_available_numbers_end_of_list.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "numbers": [ 4 | { 5 | "country": "GB", 6 | "msisdn": "442039055555", 7 | "cost": "0.80", 8 | "type": "mobile-lvn", 9 | "features": [ 10 | "VOICE", 11 | "SMS" 12 | ] 13 | } 14 | ] 15 | } -------------------------------------------------------------------------------- /number_management/tests/data/search_available_numbers_filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "numbers": [ 4 | { 5 | "country": "GB", 6 | "msisdn": "442039055555", 7 | "cost": "1.00", 8 | "type": "landline", 9 | "features": [ 10 | "VOICE" 11 | ] 12 | } 13 | ] 14 | } -------------------------------------------------------------------------------- /pants.ci.toml: -------------------------------------------------------------------------------- 1 | [GLOBAL] 2 | colors = true 3 | 4 | [python] 5 | interpreter_constraints = ['>=3.9'] 6 | -------------------------------------------------------------------------------- /pants.toml: -------------------------------------------------------------------------------- 1 | [GLOBAL] 2 | pants_version = '2.26.0' 3 | 4 | backend_packages = [ 5 | 'pants.backend.python', 6 | 'pants.backend.python.lint.autoflake', 7 | 'pants.backend.build_files.fmt.black', 8 | 'pants.backend.python.lint.isort', 9 | 'pants.backend.python.lint.black', 10 | 'pants.backend.python.lint.docformatter', 11 | 'pants.backend.tools.taplo', 12 | "pants.backend.experimental.python", 13 | ] 14 | 15 | pants_ignore.add = ['!_test_scripts/', '!_dev_scripts/'] 16 | 17 | [anonymous-telemetry] 18 | enabled = false 19 | 20 | [source] 21 | root_patterns = ['/', 'src/', 'tests/'] 22 | 23 | [python] 24 | interpreter_constraints = ['==3.13.*'] 25 | 26 | [pytest] 27 | args = ['-vv', '--no-header'] 28 | 29 | [coverage-py] 30 | interpreter_constraints = ['>=3.9'] 31 | report = ['html', 'console'] 32 | 33 | [black] 34 | args = ['--line-length=90', '--skip-string-normalization'] 35 | interpreter_constraints = ['>=3.9'] 36 | 37 | [isort] 38 | args = ['--profile=black', '--line-length=90'] 39 | interpreter_constraints = ['>=3.9'] 40 | 41 | [docformatter] 42 | args = ['--wrap-summaries=90', '--wrap-descriptions=90'] 43 | interpreter_constraints = ['>=3.9'] 44 | 45 | [autoflake] 46 | interpreter_constraints = ['>=3.9'] 47 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [tool.coverage.run] 2 | omit = ['**/tests/*', '**/src/**/_version.py'] 3 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | pytest>=8.0.0 2 | requests>=2.31.0 3 | responses>=0.24.1 4 | pydantic>=2.9.2 5 | typing-extensions>=4.9.0 6 | pyjwt[crypto]>=1.6.4 7 | toml>=0.10.2 8 | urllib3 9 | 10 | -e jwt 11 | -e http_client 12 | -e account 13 | -e application 14 | -e messages 15 | -e network_auth 16 | -e network_number_verification 17 | -e network_sim_swap 18 | -e number_insight 19 | -e number_management 20 | -e sms 21 | -e subaccounts 22 | -e users 23 | -e verify 24 | -e verify_legacy 25 | -e video 26 | -e voice 27 | -e vonage_utils 28 | -e vonage 29 | -------------------------------------------------------------------------------- /sms/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-sms', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'sms/src/vonage_sms', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /sms/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.1.6 2 | - Make returned response fields optional 3 | 4 | # 1.1.5 5 | - Updated dependency versions 6 | 7 | # 1.1.4 8 | - Update dependency versions 9 | 10 | # 1.1.3 11 | - Support for Python 3.13, drop support for 3.8 12 | 13 | # 1.1.2 14 | - Add docstrings to data models 15 | 16 | # 1.1.1 17 | - Update minimum dependency version 18 | 19 | # 1.1.0 20 | - Add `http_client` property 21 | 22 | # 1.0.2 23 | - Internal refactoring 24 | 25 | # 1.0.1 26 | - Internal refactoring 27 | 28 | # 1.0.0 29 | - Initial upload 30 | -------------------------------------------------------------------------------- /sms/README.md: -------------------------------------------------------------------------------- 1 | # Vonage SMS Package 2 | 3 | This package contains the code to use Vonage's SMS API in Python. 4 | 5 | It includes a method for sending SMS messages and returns an `SmsResponse` class to handle the response. 6 | 7 | ## Usage 8 | 9 | It is recommended to use this as part of the main `vonage` package. The examples below assume you've created an instance of the `vonage.Vonage` class called `vonage_client`. 10 | 11 | ### Send an SMS 12 | 13 | Create an `SmsMessage` object, then pass into the `Sms.send` method. 14 | 15 | ```python 16 | from vonage_sms import SmsMessage, SmsResponse 17 | 18 | message = SmsMessage(to='1234567890', from_='Acme Inc.', text='Hello, World!') 19 | response: SmsResponse = vonage_client.sms.send(message) 20 | 21 | print(response.model_dump(exclude_unset=True)) 22 | ``` 23 | 24 | 25 | -------------------------------------------------------------------------------- /sms/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-sms' 3 | dynamic = ["version"] 4 | description = 'Vonage SMS package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_sms._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /sms/src/vonage_sms/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /sms/src/vonage_sms/__init__.py: -------------------------------------------------------------------------------- 1 | from .errors import PartialFailureError, SmsError 2 | from .requests import SmsMessage 3 | from .responses import MessageResponse, SmsResponse 4 | from .sms import Sms 5 | 6 | __all__ = [ 7 | 'Sms', 8 | 'SmsMessage', 9 | 'SmsResponse', 10 | 'MessageResponse', 11 | 'SmsError', 12 | 'PartialFailureError', 13 | ] 14 | -------------------------------------------------------------------------------- /sms/src/vonage_sms/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.1.6' 2 | -------------------------------------------------------------------------------- /sms/src/vonage_sms/errors.py: -------------------------------------------------------------------------------- 1 | from requests import Response 2 | from vonage_utils.errors import VonageError 3 | 4 | 5 | class SmsError(VonageError): 6 | """Indicates an error with the Vonage SMS Package.""" 7 | 8 | 9 | class PartialFailureError(SmsError): 10 | """Indicates that a request was partially successful.""" 11 | 12 | def __init__(self, response: Response): 13 | self.message = ( 14 | 'Sms.send_message method partially failed. Not all of the message(s) sent successfully.', 15 | ) 16 | super().__init__(self.message) 17 | self.response = response 18 | -------------------------------------------------------------------------------- /sms/src/vonage_sms/responses.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class MessageResponse(BaseModel): 7 | """Individual message response model. 8 | 9 | Args: 10 | to (str): The recipient's phone number in E.164 format. 11 | message_id (str): The message ID. 12 | status (str): The status of the message. 13 | remaining_balance (str): The estimated remaining balance. 14 | message_price (str): The estimated message cost. 15 | network (str): The estimated ID of the network of the recipient 16 | client_ref (str, Optional): If a `client_ref` was included when sending the SMS, 17 | this field will be included and hold the value that was sent. 18 | account_ref (str, Optional): An optional string used to identify separate 19 | accounts using the SMS endpoint for billing purposes. To use this feature, 20 | please email support. 21 | """ 22 | 23 | to: Optional[str] = None 24 | message_id: Optional[str] = Field(None, validation_alias='message-id') 25 | status: Optional[str] = None 26 | remaining_balance: Optional[str] = Field(None, validation_alias='remaining-balance') 27 | message_price: Optional[str] = Field(None, validation_alias='message-price') 28 | network: Optional[str] = None 29 | client_ref: Optional[str] = Field(None, validation_alias='client-ref') 30 | account_ref: Optional[str] = Field(None, validation_alias='account-ref') 31 | 32 | 33 | class SmsResponse(BaseModel): 34 | """Response recieved after sending an SMS. 35 | 36 | Args: 37 | message_count (str): The number of messages sent. 38 | messages (list[MessageResponse]): A list of individual message responses. See 39 | `MessageResponse` for more information. 40 | """ 41 | 42 | message_count: str = Field(..., validation_alias='message-count') 43 | messages: list[MessageResponse] 44 | -------------------------------------------------------------------------------- /sms/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['sms', 'testutils']) 2 | -------------------------------------------------------------------------------- /sms/tests/data/conversion_not_enabled.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | Error 402 5 | 6 | 7 |

HTTP ERROR: 402

8 |

Problem accessing /conversions/sms. Reason: 9 |

    Bad Account Credentials

10 |
Powered by Jetty:// 11 | 12 | -------------------------------------------------------------------------------- /sms/tests/data/null: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vonage/vonage-python-sdk/82a02e48b9793a2aa0bce3fa593de8cf97caf839/sms/tests/data/null -------------------------------------------------------------------------------- /sms/tests/data/send_long_sms.json: -------------------------------------------------------------------------------- 1 | { 2 | "message-count": "2", 3 | "messages": [ 4 | { 5 | "to": "1234567890", 6 | "message-id": "62dfdf68-6c7c-479a-a190-5c52f798a787", 7 | "status": "0", 8 | "remaining-balance": "37.43563628", 9 | "message-price": "0.04120000", 10 | "network": "23420", 11 | "client-ref": "ref123" 12 | }, 13 | { 14 | "to": "1234567890", 15 | "message-id": "72ff9536-62d6-455a-9f0b-65f3c265b423", 16 | "status": "0", 17 | "remaining-balance": "37.43563628", 18 | "message-price": "0.04120000", 19 | "network": "23420", 20 | "client-ref": "ref123" 21 | } 22 | ] 23 | } -------------------------------------------------------------------------------- /sms/tests/data/send_sms.json: -------------------------------------------------------------------------------- 1 | { 2 | "message-count": "1", 3 | "messages": [ 4 | { 5 | "to": "1234567890", 6 | "message-id": "3295d748-4e14-4681-af78-166dca3c5aab", 7 | "status": "0", 8 | "remaining-balance": "38.07243628", 9 | "message-price": "0.04120000", 10 | "network": "23420" 11 | } 12 | ] 13 | } 14 | -------------------------------------------------------------------------------- /sms/tests/data/send_sms_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "message-count": "1", 3 | "messages": [ 4 | { 5 | "status": "7", 6 | "error-text": "Number barred." 7 | } 8 | ] 9 | } -------------------------------------------------------------------------------- /sms/tests/data/send_sms_partial_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "message-count": "2", 3 | "messages": [ 4 | { 5 | "to": "1234567890", 6 | "message-id": "3295d748-4e14-4681-af78-166dca3c5aab", 7 | "status": "0", 8 | "remaining-balance": "38.07243628", 9 | "message-price": "0.04120000", 10 | "network": "23420" 11 | }, 12 | { 13 | "status": "1", 14 | "error-text": "Throttled" 15 | } 16 | ] 17 | } -------------------------------------------------------------------------------- /subaccounts/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-subaccounts', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'subaccounts/src/vonage_subaccounts', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /subaccounts/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.0.4 2 | - Updated dependency versions 3 | 4 | # 1.0.3 5 | - Update dependency versions 6 | 7 | # 1.0.3 8 | - Support for Python 3.13, drop support for 3.8 9 | 10 | # 1.0.2 11 | - Add docstrings to data models 12 | 13 | # 1.0.1 14 | - Updated `vonage_subaccounts.ListSubaccountsResponse` for compatibility with Python 3.8 15 | 16 | # 1.0.0 17 | - Initial upload 18 | -------------------------------------------------------------------------------- /subaccounts/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-subaccounts' 3 | dynamic = ["version"] 4 | description = 'Vonage Subaccounts API package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_subaccounts._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /subaccounts/src/vonage_subaccounts/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /subaccounts/src/vonage_subaccounts/__init__.py: -------------------------------------------------------------------------------- 1 | from .errors import InvalidSecretError 2 | from .requests import ( 3 | ListTransfersFilter, 4 | ModifySubaccountOptions, 5 | SubaccountOptions, 6 | TransferNumberRequest, 7 | TransferRequest, 8 | ) 9 | from .responses import ( 10 | ListSubaccountsResponse, 11 | NewSubaccount, 12 | PrimaryAccount, 13 | Subaccount, 14 | Transfer, 15 | TransferNumberResponse, 16 | VonageAccount, 17 | ) 18 | from .subaccounts import Subaccounts 19 | 20 | __all__ = [ 21 | 'Subaccounts', 22 | 'InvalidSecretError', 23 | 'ListTransfersFilter', 24 | 'SubaccountOptions', 25 | 'ModifySubaccountOptions', 26 | 'TransferNumberRequest', 27 | 'TransferRequest', 28 | 'VonageAccount', 29 | 'PrimaryAccount', 30 | 'Subaccount', 31 | 'ListSubaccountsResponse', 32 | 'NewSubaccount', 33 | 'Transfer', 34 | 'TransferNumberResponse', 35 | ] 36 | -------------------------------------------------------------------------------- /subaccounts/src/vonage_subaccounts/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.4' 2 | -------------------------------------------------------------------------------- /subaccounts/src/vonage_subaccounts/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class InvalidSecretError(VonageError): 5 | """Indicates that the secret provided was invalid.""" 6 | -------------------------------------------------------------------------------- /subaccounts/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['subaccounts', 'testutils']) 2 | -------------------------------------------------------------------------------- /subaccounts/tests/data/create_subaccount.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "1234qwer", 3 | "secret": "SuperSecr3t", 4 | "primary_account_api_key": "test_api_key", 5 | "use_primary_account_balance": false, 6 | "name": "test_subaccount", 7 | "balance": 0.0000, 8 | "credit_limit": 0.0000, 9 | "suspended": false, 10 | "created_at": "2024-08-28T14:11:32.239Z" 11 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/get_subaccount.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "1234qwer", 3 | "primary_account_api_key": "test_api_key", 4 | "use_primary_account_balance": false, 5 | "name": "test_subaccount", 6 | "balance": 0.0000, 7 | "credit_limit": 0.0000, 8 | "suspended": false, 9 | "created_at": "2024-08-28T14:11:32.000Z" 10 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/list_balance_transfers.json: -------------------------------------------------------------------------------- 1 | { 2 | "_links": { 3 | "self": { 4 | "href": "/accounts/test_api_key/balance-transfers" 5 | } 6 | }, 7 | "_embedded": { 8 | "balance_transfers": [ 9 | { 10 | "from": "test_api_key", 11 | "to": "asdfqwer", 12 | "amount": 0.01, 13 | "reference": "", 14 | "id": "6917b0ae-aed3-453c-a918-e37f6ef7b21a", 15 | "created_at": "2023-12-22T19:41:19.000Z" 16 | }, 17 | { 18 | "from": "test_api_key", 19 | "to": "asdfqwer", 20 | "amount": 0.5, 21 | "reference": "", 22 | "id": "049adc07-5da1-4d13-bacd-0ee6a99ef948", 23 | "created_at": "2023-12-22T19:40:36.000Z" 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/list_credit_transfers.json: -------------------------------------------------------------------------------- 1 | { 2 | "_links": { 3 | "self": { 4 | "href": "/accounts/test_api_key/balance-transfers" 5 | } 6 | }, 7 | "_embedded": { 8 | "credit_transfers": [ 9 | { 10 | "from": "test_api_key", 11 | "to": "asdfqwer", 12 | "amount": 0.01, 13 | "reference": "", 14 | "id": "6917b0ae-aed3-453c-a918-e37f6ef7b21a", 15 | "created_at": "2023-12-22T19:41:19.000Z" 16 | }, 17 | { 18 | "from": "test_api_key", 19 | "to": "asdfqwer", 20 | "amount": 0.5, 21 | "reference": "", 22 | "id": "049adc07-5da1-4d13-bacd-0ee6a99ef948", 23 | "created_at": "2023-12-22T19:40:36.000Z" 24 | } 25 | ] 26 | } 27 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/list_subaccounts.json: -------------------------------------------------------------------------------- 1 | { 2 | "_links": { 3 | "self": { 4 | "href": "/accounts/test_api_key/subaccounts" 5 | } 6 | }, 7 | "total_balance": 29.6672, 8 | "total_credit_limit": 0.0000, 9 | "_embedded": { 10 | "primary_account": { 11 | "api_key": "test_api_key", 12 | "name": "SMPP Account", 13 | "balance": 27.4572, 14 | "credit_limit": 0.0000, 15 | "suspended": false, 16 | "created_at": "2024-08-28T02:02:14.626Z" 17 | }, 18 | "subaccounts": [ 19 | { 20 | "api_key": "qwer1234", 21 | "primary_account_api_key": "test_api_key", 22 | "use_primary_account_balance": false, 23 | "name": "second own balance subacct", 24 | "balance": 0.5, 25 | "credit_limit": 0.0000, 26 | "suspended": false, 27 | "created_at": "2023-06-07T10:50:44.000Z" 28 | }, 29 | { 30 | "api_key": "1234qwer", 31 | "primary_account_api_key": "test_api_key", 32 | "use_primary_account_balance": false, 33 | "name": "own balance subaccount", 34 | "balance": 1.71, 35 | "credit_limit": 0.0000, 36 | "suspended": false, 37 | "created_at": "2023-06-09T13:52:43.000Z" 38 | } 39 | ] 40 | } 41 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/modify_subaccount.json: -------------------------------------------------------------------------------- 1 | { 2 | "api_key": "1234qwer", 3 | "primary_account_api_key": "asdf1234", 4 | "use_primary_account_balance": false, 5 | "name": "modified_test_subaccount", 6 | "balance": 0.0000, 7 | "credit_limit": 0.0000, 8 | "suspended": true, 9 | "created_at": "2024-08-28T14:11:32.000Z" 10 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/transfer.json: -------------------------------------------------------------------------------- 1 | { 2 | "masterAccountId": "test_api_key", 3 | "_links": { 4 | "self": { 5 | "href": "/accounts/test_api_key/balance-transfers/a1a90387-fcf2-41dc-9beb-cfd82b6b994d" 6 | } 7 | }, 8 | "from": "test_api_key", 9 | "to": "asdfqwer", 10 | "amount": 0.02, 11 | "reference": "A reference", 12 | "id": "a1a90387-fcf2-41dc-9beb-cfd82b6b994d", 13 | "created_at": "2024-08-29T13:29:51.000Z" 14 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/transfer_number.json: -------------------------------------------------------------------------------- 1 | { 2 | "number": "447700900000", 3 | "country": "GB", 4 | "from": "test_api_key", 5 | "to": "asdfqwer" 6 | } -------------------------------------------------------------------------------- /subaccounts/tests/data/transfer_number_error_suspended_account.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.nexmo.com/api-errors/subaccounts#invalid-number-transfer", 3 | "title": "Invalid Number Transfer", 4 | "detail": "One of the accounts involved in the transfer is banned", 5 | "instance": "ba2abf2a-e64d-4281-aca5-30f13947cbcd" 6 | } -------------------------------------------------------------------------------- /testutils/BUILD: -------------------------------------------------------------------------------- 1 | file(name='fake_private_key', source='data/fake_private_key.txt') 2 | 3 | python_sources(dependencies=[':fake_private_key']) 4 | -------------------------------------------------------------------------------- /testutils/__init__.py: -------------------------------------------------------------------------------- 1 | from .mock_auth import get_mock_api_key_auth, get_mock_jwt_auth 2 | from .testutils import build_response 3 | 4 | __all__ = ['build_response', 'get_mock_api_key_auth', 'get_mock_jwt_auth'] 5 | -------------------------------------------------------------------------------- /testutils/data/fake_private_key.txt: -------------------------------------------------------------------------------- 1 | -----BEGIN PRIVATE KEY----- 2 | MIIEvgIBADANBgkqhkiG9w0BAQEFAASCBKgwggSkAgEAAoIBAQDQdAHqJHs/a+Ra 3 | 2ubvSd1vz/aWlJ9BqnMUtB7guTlyggdENAbleIkzep6mUHepDJdQh8Qv6zS3lpUe 4 | K0UkDfr1/FvsvxurGw/YYPagUEhP/HxMbs2rnQTiAdWOT+Ux9vPABoyNYvZB90xN 5 | IVhBDRWgkz1HPQBRNjFcm3NOol83h5Uwp5YroGTWx+rpmIiRhQj3mv6luk102d95 6 | 4ulpPpzcYWKIpJNdclJrEkBZaghDZTOpbv79qd+ds9AVp1j8i9cG/owBJpsJWxfw 7 | StMDpNeEZqopeQWmA121sSEsxpAbKJ5DA7F/lmckx74sulKHX1fDWT76cRhloaEQ 8 | VmETdj0VAgMBAAECggEAZ+SBtchz8vKbsBqtAbM/XcR5Iqi1TR2eWMHDJ/65HpSm 9 | +XuyujjerN0e6EZvtT4Uxmq8QaPJNP0kmhI31hXvsB0UVcUUDa4hshb1pIYO3Gq7 10 | Kr8I29EZB2mhndm9Ii9yYhEBiVA66zrNeR225kkWr97iqjhBibhoVr8Vc6oiqcIP 11 | nFy5zSFtQSkhucaPge6rW00JSOD3wg2GM+rgS6r22t8YmqTzAwvwfil5pQfUngal 12 | oywqLOf6CUYXPBleJc1KgaIIP/cSvqh6b/t25o2VXnI4rpRhtleORvYBbH6K6xLa 13 | OWgg6B58T+0/QEqtZIAn4miYtVCkYLB78Ormc7Q9ewKBgQDuSytuYqxdZh/L/RDU 14 | CErFcNO5I1e9fkLAs5dQEBvvdQC74+oA1MsDEVv0xehFa1JwPKSepmvB2UznZg9L 15 | CtR7QKMDZWvS5xx4j0E/b+PiNQ/tlcFZB2UZ0JwviSxdd7omOTscq9c3RIhFHar1 16 | Y38Fixkfm44Ij/K3JqIi2v2QMwKBgQDf8TYOOmAr9UuipUDxMsRSqTGVIY8B+aEJ 17 | W+2aLrqJVkLGTRfrbjzXWYo3+n7kNJjFgNkltDq6HYtufHMYRs/0PPtNR0w0cDPS 18 | Xr7m2LNHTDcBalC/AS4yKZJLNLm+kXA84vkw4qiTjc0LSFxJkouTQzkea0l8EWHt 19 | zRMv/qYVlwKBgBaJOWRJJK/4lo0+M7c5yYh+sSdTNlsPc9Sxp1/FBj9RO26JkXne 20 | pgx2OdIeXWcjTTqcIZ13c71zhZhkyJF6RroZVNFfaCEcBk9IjQ0o0c504jq/7Pc0 21 | gdU9K2g7etykFBDFXNfLUKFDc/fFZIOskzi8/PVGStp4cqXrm23cdBqNAoGBAKtf 22 | A2bP9ViuVjsZCyGJIAPBxlfBXpa8WSe4WZNrvwPqJx9pT6yyp4yE0OkVoJUyStaZ 23 | S5M24NocUd8zDUC+r9TP9d+leAOI+Z87MgumOUuOX2mN2kzQsnFgrrsulhXnZmSx 24 | rNBkI20HTqobrcP/iSAgiU1l/M4c3zwDe3N3A9HxAoGBAM2hYu0Ij6htSNgo/WWr 25 | IEYYXuwf8hPkiuwzlaiWhD3eocgd4S8SsBu/bTCY19hQ2QbBPaYyFlNem+ynQyXx 26 | IOacrgIHCrYnRCxjPfFF/MxgUHJb8ZoiexprP/FME5p0PoRQIEFYa+jVht3hT5wC 27 | 9aedWufq4JJb+akO6MVUjTvs 28 | -----END PRIVATE KEY----- 29 | -------------------------------------------------------------------------------- /testutils/mock_auth.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, join 2 | 3 | from vonage_http_client.auth import Auth 4 | 5 | 6 | def read_file(path): 7 | """Read a file from the testutils/data directory.""" 8 | 9 | with open(join(dirname(__file__), path)) as input_file: 10 | return input_file.read() 11 | 12 | 13 | def get_mock_api_key_auth(): 14 | """Return an Auth object with an API key and secret.""" 15 | 16 | return Auth(api_key='test_api_key', api_secret='test_api_secret') 17 | 18 | 19 | def get_mock_jwt_auth(): 20 | """Return an Auth object with a JWT.""" 21 | 22 | return Auth( 23 | application_id='test_application_id', 24 | private_key=read_file('data/fake_private_key.txt'), 25 | ) 26 | -------------------------------------------------------------------------------- /testutils/testutils.py: -------------------------------------------------------------------------------- 1 | from os.path import dirname, join 2 | from typing import Literal 3 | 4 | import responses 5 | from pydantic import validate_call 6 | 7 | 8 | def _load_mock_data(caller_file_path: str, mock_path: str): 9 | """Load mock data from a file.""" 10 | 11 | try: 12 | with open(join(dirname(caller_file_path), 'data', mock_path)) as file: 13 | return file.read() 14 | except UnicodeDecodeError: 15 | with open(join(dirname(caller_file_path), 'data', mock_path), 'rb') as file: 16 | return file.read() 17 | 18 | 19 | def _filter_none_values(data: dict) -> dict: 20 | """Filter out None values from a dictionary.""" 21 | 22 | return {k: v for (k, v) in data.items() if v is not None} 23 | 24 | 25 | @validate_call 26 | def build_response( 27 | file_path: str, 28 | method: Literal['GET', 'POST', 'PATCH', 'PUT', 'DELETE'], 29 | url: str, 30 | mock_path: str = None, 31 | status_code: int = 200, 32 | content_type: str = 'application/json', 33 | match: list = None, 34 | ): 35 | """Build a response for a mock request. 36 | 37 | Args: 38 | file_path (str): The path to the file calling this function. 39 | method (Literal['GET', 'POST', 'PATCH', 'PUT', 'DELETE']): The HTTP method. 40 | url (str): The URL to match. 41 | mock_path (str, optional): The path to the mock data file. 42 | status_code (int, optional): The status code to return. 43 | content_type (str, optional): The content type to return. 44 | match (list, optional): The match parameters. 45 | """ 46 | 47 | body = _load_mock_data(file_path, mock_path) if mock_path else None 48 | responses.add( 49 | **_filter_none_values( 50 | { 51 | 'method': method, 52 | 'url': url, 53 | 'body': body, 54 | 'status': status_code, 55 | 'content_type': content_type, 56 | 'match': match, 57 | } 58 | ) 59 | ) 60 | -------------------------------------------------------------------------------- /users/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-users', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'users/src/vonage_users', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /users/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.2.1 2 | - Updated dependency versions 3 | 4 | # 1.2.0 5 | - Expose more properties in the top-level `vonage_users` scope 6 | - Update dependency versions 7 | 8 | # 1.1.4 9 | - Support for Python 3.13, drop support for 3.8 10 | 11 | # 1.1.3 12 | - Add docstrings to data models 13 | 14 | # 1.1.2 15 | - Internal refactoring 16 | 17 | # 1.1.1 18 | - Update minimum dependency version 19 | 20 | # 1.1.0 21 | - Add `http_client` property 22 | - Rename `ListUsersRequest` -> `ListUsersFilter` 23 | - Internal refactoring 24 | 25 | # 1.0.1 26 | - Internal refactoring 27 | 28 | # 1.0.0 29 | - Initial upload 30 | -------------------------------------------------------------------------------- /users/README.md: -------------------------------------------------------------------------------- 1 | # Vonage Users Package 2 | 3 | This package contains the code to use Vonage's Users API in Python. 4 | 5 | It includes methods for managing users. 6 | 7 | ## Usage 8 | 9 | It is recommended to use this as part of the main `vonage` package. The examples below assume you've created an instance of the `vonage.Vonage` class called `vonage_client`. 10 | 11 | ### List Users 12 | 13 | With no custom options specified, this method will get the last 100 users. It returns a tuple consisting of a list of `UserSummary` objects and a string describing the cursor to the next page of results. 14 | 15 | ```python 16 | from vonage_users import ListUsersRequest 17 | 18 | users, _ = vonage_client.users.list_users() 19 | 20 | # With options 21 | params = ListUsersRequest( 22 | page_size=10, 23 | cursor=my_cursor, 24 | order='desc', 25 | ) 26 | users, next_cursor = vonage_client.users.list_users(params) 27 | ``` 28 | 29 | ### Create a New User 30 | 31 | ```python 32 | from vonage_users import User, Channels, SmsChannel 33 | user_options = User( 34 | name='my_user_name', 35 | display_name='My User Name', 36 | properties={'custom_key': 'custom_value'}, 37 | channels=Channels(sms=[SmsChannel(number='1234567890')]), 38 | ) 39 | user = vonage_client.users.create_user(user_options) 40 | ``` 41 | 42 | ### Get a User 43 | 44 | ```python 45 | user = client.users.get_user('USR-87e3e6b0-cd7b-45ef-a0a7-bcd5566a672b') 46 | user_as_dict = user.model_dump(exclude_none=True) 47 | ``` 48 | 49 | ### Update a User 50 | ```python 51 | from vonage_users import User, Channels, SmsChannel, WhatsappChannel 52 | user_options = User( 53 | name='my_user_name', 54 | display_name='My User Name', 55 | properties={'custom_key': 'custom_value'}, 56 | channels=Channels(sms=[SmsChannel(number='1234567890')], whatsapp=[WhatsappChannel(number='9876543210')]), 57 | ) 58 | user = vonage_client.users.update_user(id, user_options) 59 | ``` 60 | 61 | ### Delete a User 62 | 63 | ```python 64 | vonage_client.users.delete_user(id) 65 | ``` -------------------------------------------------------------------------------- /users/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-users' 3 | dynamic = ["version"] 4 | description = 'Vonage Users package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_users._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /users/src/vonage_users/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /users/src/vonage_users/__init__.py: -------------------------------------------------------------------------------- 1 | from .common import ( 2 | Channels, 3 | MessengerChannel, 4 | MmsChannel, 5 | Properties, 6 | PstnChannel, 7 | SipChannel, 8 | SmsChannel, 9 | User, 10 | VbcChannel, 11 | ViberChannel, 12 | WebsocketChannel, 13 | WhatsappChannel, 14 | ) 15 | from .requests import ListUsersFilter 16 | from .responses import UserSummary 17 | from .users import Users 18 | 19 | __all__ = [ 20 | 'User', 21 | 'PstnChannel', 22 | 'SipChannel', 23 | 'WebsocketChannel', 24 | 'VbcChannel', 25 | 'SmsChannel', 26 | 'MmsChannel', 27 | 'WhatsappChannel', 28 | 'ViberChannel', 29 | 'MessengerChannel', 30 | 'Channels', 31 | 'Properties', 32 | 'ListUsersFilter', 33 | 'UserSummary', 34 | 'Users', 35 | ] 36 | -------------------------------------------------------------------------------- /users/src/vonage_users/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.1' 2 | -------------------------------------------------------------------------------- /users/src/vonage_users/requests.py: -------------------------------------------------------------------------------- 1 | from typing import Literal, Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class ListUsersFilter(BaseModel): 7 | """Request object for listing users. 8 | 9 | Args: 10 | page_size (int, Optional): The number of users to return per response. 11 | order (str, Optional): Return the records in ascending or descending order. 12 | cursor (str, Optional): The cursor to start returning results from. You must 13 | follow the url provided in the response tuple which contains a cursor value. 14 | name (str, Optional): The name of the user to filter by. 15 | """ 16 | 17 | page_size: Optional[int] = Field(100, ge=1, le=100) 18 | order: Optional[Literal['asc', 'desc', 'ASC', 'DESC']] = None 19 | cursor: Optional[str] = Field( 20 | None, 21 | description="The cursor to start returning results from. You are not expected to provide this manually, but to follow the url provided in _links.next.href or _links.prev.href in the response which contains a cursor value.", 22 | ) 23 | name: Optional[str] = None 24 | -------------------------------------------------------------------------------- /users/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['users', 'testutils']) 2 | -------------------------------------------------------------------------------- /users/tests/data/list_users_options.json: -------------------------------------------------------------------------------- 1 | { 2 | "page_size": 2, 3 | "_embedded": { 4 | "users": [ 5 | { 6 | "id": "USR-37a8299f-eaad-417c-a0b3-431b6555c4be", 7 | "name": "my_other_user_name", 8 | "display_name": "My Other User Name", 9 | "_links": { 10 | "self": { 11 | "href": "https://api-us-3.vonage.com/v1/users/USR-37a8299f-eaad-417c-a0b3-431b6555c4be" 12 | } 13 | } 14 | }, 15 | { 16 | "id": "USR-5ab17d58-b8b3-427d-ac42-c31dab7ef422", 17 | "name": "my_user_name", 18 | "display_name": "My User Name", 19 | "_links": { 20 | "self": { 21 | "href": "https://api-us-3.vonage.com/v1/users/USR-5ab17d58-b8b3-427d-ac42-c31dab7ef422" 22 | } 23 | } 24 | } 25 | ] 26 | }, 27 | "_links": { 28 | "first": { 29 | "href": "https://api-us-3.vonage.com/v1/users?page_size=2" 30 | }, 31 | "self": { 32 | "href": "https://api-us-3.vonage.com/v1/users?page_size=2&cursor=ItiNOQpJ7IOaL%2FvgHcixE8j8yw8VV0viPWw9nZEeO4%2Fp2DrDr4Qa7CtLAi5ST94XVpiwIJvkUBJ1U%2BL4S%2BK3cSLht3QkP3hmL1pKgkNGW6IdOzHGkpr7v0WsMOY%3D" 33 | }, 34 | "next": { 35 | "href": "https://api-us-3.vonage.com/v1/users?page_size=2&cursor=Rv1d7qE3lDuOuwSFjRGHJ2JpKG28CdI1iNjSKNwy0NIr7uicrn7SGpIyaDtvkEEBfyH5xyjSonpeoYNLdw19SQ%3D%3D" 36 | }, 37 | "prev": { 38 | "href": "https://api-us-3.vonage.com/v1/users?page_size=2&cursor=6nFju6mYCT5FYbsnxvlJ4XFD1ekcwh6DP0%2BT5BVLvRdZTsqB0EA9j%2B0Bwfpr63xTF%2BZVe7R9QHqv2wH6nQhf7hFz%2B0Ux3g%3D%3D" 39 | } 40 | } 41 | } -------------------------------------------------------------------------------- /users/tests/data/updated_user.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "USR-87e3e6b0-cd7b-45ef-a0a7-bcd5566a672b", 3 | "name": "new name!", 4 | "display_name": "My New Renamed User Name", 5 | "properties": {}, 6 | "_links": { 7 | "self": { 8 | "href": "https://api-us-3.vonage.com/v1/users/USR-87e3e6b0-cd7b-45ef-a0a7-bcd5566a672b" 9 | } 10 | }, 11 | "channels": { 12 | "sms": [ 13 | { 14 | "number": "1234567890" 15 | } 16 | ], 17 | "pstn": [ 18 | { 19 | "number": 123456 20 | } 21 | ] 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /users/tests/data/user.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "USR-87e3e6b0-cd7b-45ef-a0a7-bcd5566a672b", 3 | "name": "my_user_name", 4 | "display_name": "My User Name", 5 | "properties": { 6 | "custom_data": {} 7 | }, 8 | "_links": { 9 | "self": { 10 | "href": "https://api-us-3.vonage.com/v1/users/USR-87e3e6b0-cd7b-45ef-a0a7-bcd5566a672b" 11 | } 12 | }, 13 | "channels": { 14 | "sms": [ 15 | { 16 | "number": "1234567890" 17 | } 18 | ] 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /users/tests/data/user_not_found.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Not found.", 3 | "type": "https://developer.vonage.com/api/conversation#user:error:not-found", 4 | "detail": "User does not exist, or you do not have access.", 5 | "instance": "00a5916655d650e920ccf0daf40ef4ee" 6 | } -------------------------------------------------------------------------------- /verify/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-verify', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'verify/src/vonage_verify', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /verify/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 2.1.0 2 | - Add support for API key/secret header authentication 3 | - Updated dependency versions 4 | 5 | # 2.0.0 6 | - Rename `vonage-verify-v2` package -> `vonage-verify`, `VerifyV2` -> `Verify`, etc. This package now contains code for the Verify v2 API 7 | - Update dependency versions 8 | 9 | # 1.1.4 10 | - Support for Python 3.13, drop support for 3.8 11 | 12 | # 1.1.3 13 | - Add docstrings for data models 14 | 15 | # 1.1.2 16 | - Allow minimum `channel_timeout` value to be 15 seconds 17 | 18 | # 1.1.1 19 | - Update minimum dependency version 20 | 21 | # 1.1.0 22 | - Add `http_client` property 23 | 24 | # 1.0.0 25 | - Initial upload 26 | -------------------------------------------------------------------------------- /verify/README.md: -------------------------------------------------------------------------------- 1 | # Vonage Verify Package 2 | 3 | This package contains the code to use [Vonage's Verify API](https://developer.vonage.com/en/verify/overview) in Python. This package includes methods for working with 2-factor authentication (2FA) messages sent via SMS, Voice, WhatsApp and Email. You can also make Silent Authentication requests with Verify to give your end user a more seamless experience. 4 | 5 | ## Usage 6 | 7 | It is recommended to use this as part of the main `vonage` package. The examples below assume you've created an instance of the `vonage.Vonage` class called `vonage_client`. 8 | 9 | ### Make a Verify Request 10 | 11 | ```python 12 | from vonage_verify import VerifyRequest, SmsChannel 13 | # All channels have associated models 14 | sms_channel = SmsChannel(to='1234567890') 15 | params = { 16 | 'brand': 'Vonage', 17 | 'workflow': [sms_channel], 18 | } 19 | verify_request = VerifyRequest(**params) 20 | 21 | response = vonage_client.verify.start_verification(verify_request) 22 | ``` 23 | 24 | If using silent authentication, the response will include a `check_url` field with a url that should be accessed on the user's device to proceed with silent authentication. If used, silent auth must be the first element in the `workflow` list. 25 | 26 | ```python 27 | silent_auth_channel = SilentAuthChannel(channel=ChannelType.SILENT_AUTH, to='1234567890') 28 | sms_channel = SmsChannel(to='1234567890') 29 | params = { 30 | 'brand': 'Vonage', 31 | 'workflow': [silent_auth_channel, sms_channel], 32 | } 33 | verify_request = VerifyRequest(**params) 34 | 35 | response = vonage_client.verify.start_verification(verify_request) 36 | ``` 37 | 38 | ### Check a Verification Code 39 | 40 | ```python 41 | vonage_client.verify.check_code(request_id='my_request_id', code='1234') 42 | ``` 43 | 44 | ### Cancel a Verification 45 | 46 | ```python 47 | vonage_client.verify.cancel_verification('my_request_id') 48 | ``` 49 | 50 | ### Trigger the Next Workflow Event 51 | 52 | ```python 53 | vonage_client.verify.trigger_next_workflow('my_request_id') 54 | ``` -------------------------------------------------------------------------------- /verify/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-verify' 3 | dynamic = ["version"] 4 | description = 'Vonage verify package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_verify._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /verify/src/vonage_verify/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /verify/src/vonage_verify/__init__.py: -------------------------------------------------------------------------------- 1 | from .enums import ChannelType, Locale 2 | from .errors import VerifyError 3 | from .requests import ( 4 | EmailChannel, 5 | SilentAuthChannel, 6 | SmsChannel, 7 | VerifyRequest, 8 | VoiceChannel, 9 | WhatsappChannel, 10 | ) 11 | from .responses import CheckCodeResponse, StartVerificationResponse 12 | from .verify import Verify 13 | 14 | __all__ = [ 15 | 'Verify', 16 | 'VerifyError', 17 | 'ChannelType', 18 | 'CheckCodeResponse', 19 | 'Locale', 20 | 'VerifyRequest', 21 | 'SilentAuthChannel', 22 | 'SmsChannel', 23 | 'WhatsappChannel', 24 | 'VoiceChannel', 25 | 'EmailChannel', 26 | 'StartVerificationResponse', 27 | ] 28 | -------------------------------------------------------------------------------- /verify/src/vonage_verify/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '2.1.0' 2 | -------------------------------------------------------------------------------- /verify/src/vonage_verify/enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class ChannelType(str, Enum): 5 | SILENT_AUTH = 'silent_auth' 6 | SMS = 'sms' 7 | WHATSAPP = 'whatsapp' 8 | VOICE = 'voice' 9 | EMAIL = 'email' 10 | 11 | 12 | class Locale(str, Enum): 13 | EN_US = 'en-us' 14 | EN_GB = 'en-gb' 15 | ES_ES = 'es-es' 16 | ES_MX = 'es-mx' 17 | ES_US = 'es-us' 18 | IT_IT = 'it-it' 19 | FR_FR = 'fr-fr' 20 | DE_DE = 'de-de' 21 | RU_RU = 'ru-ru' 22 | HI_IN = 'hi-in' 23 | PT_BR = 'pt-br' 24 | PT_PT = 'pt-pt' 25 | ID_ID = 'id-id' 26 | -------------------------------------------------------------------------------- /verify/src/vonage_verify/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class VerifyError(VonageError): 5 | """Indicates an error when using the Vonage Verify API.""" 6 | -------------------------------------------------------------------------------- /verify/src/vonage_verify/responses.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class StartVerificationResponse(BaseModel): 7 | """Model for the response of a start verification request. 8 | 9 | Args: 10 | request_id (str): The request ID. 11 | check_url (str, Optional): URL for Silent Authentication Verify workflow 12 | completion (only shows if using Silent Auth). 13 | """ 14 | 15 | request_id: str 16 | check_url: Optional[str] = None 17 | 18 | 19 | class CheckCodeResponse(BaseModel): 20 | """Model for the response of a check code request. 21 | 22 | Args: 23 | request_id (str): The request ID. 24 | status (str): The status of the verification request. 25 | """ 26 | 27 | request_id: str 28 | status: str 29 | -------------------------------------------------------------------------------- /verify/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['verify', 'testutils']) 2 | -------------------------------------------------------------------------------- /verify/tests/data/check_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "36e7060d-2b23-4257-bad0-773ab47f85ef", 3 | "status": "completed" 4 | } -------------------------------------------------------------------------------- /verify/tests/data/check_code_400.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.nexmo.com/api-errors#bad-request", 3 | "title": "Invalid Code", 4 | "detail": "The code you provided does not match the expected value.", 5 | "instance": "475343c0-9239-4715-aed1-72b4a18379d1" 6 | } -------------------------------------------------------------------------------- /verify/tests/data/check_code_410.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Invalid Code", 3 | "detail": "An incorrect code has been provided too many times. Workflow terminated.", 4 | "instance": "f79d7a15-30b7-498a-bc99-4e879b836b18", 5 | "type": "https://developer.nexmo.com/api-errors#gone" 6 | } -------------------------------------------------------------------------------- /verify/tests/data/trigger_next_workflow_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Conflict", 3 | "detail": "There are no more events left to trigger.", 4 | "instance": "4d731cb7-25d3-487a-9ea0-f6b5811b534f", 5 | "type": "https://developer.nexmo.com/api-errors#conflict" 6 | } -------------------------------------------------------------------------------- /verify/tests/data/verify_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "2c59e3f4-a047-499f-a14f-819cd1989d2e", 3 | "check_url": "https://api-eu-3.vonage.com/v2/verify/cfbc9a3b-27a2-40d4-a4e0-0c59b3b41901/silent-auth/redirect" 4 | } -------------------------------------------------------------------------------- /verify/tests/data/verify_request_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "title": "Conflict", 3 | "detail": "Concurrent verifications to the same number are not allowed", 4 | "instance": "229ececf-382e-4ab6-b380-d8e0e830fd44", 5 | "request_id": "f8386e0f-6873-4617-aa99-19016217b2aa" 6 | } -------------------------------------------------------------------------------- /verify_legacy/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-verify', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'verify_legacy/src/vonage_verify_legacy', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /verify_legacy/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.0.1 2 | - Updated dependency versions 3 | 4 | # 1.0.0 5 | - Initial upload as `legacy` package 6 | -------------------------------------------------------------------------------- /verify_legacy/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-verify-legacy' 3 | dynamic = ["version"] 4 | description = 'Vonage legacy verify package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_verify_legacy._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /verify_legacy/src/vonage_verify_legacy/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /verify_legacy/src/vonage_verify_legacy/__init__.py: -------------------------------------------------------------------------------- 1 | from .errors import VerifyError 2 | from .language_codes import LanguageCode, Psd2LanguageCode 3 | from .requests import Psd2Request, VerifyRequest 4 | from .responses import ( 5 | CheckCodeResponse, 6 | NetworkUnblockStatus, 7 | StartVerificationResponse, 8 | VerifyControlStatus, 9 | VerifyStatus, 10 | ) 11 | from .verify_legacy import VerifyLegacy 12 | 13 | __all__ = [ 14 | 'VerifyError', 15 | 'VerifyLegacy', 16 | 'LanguageCode', 17 | 'Psd2LanguageCode', 18 | 'Psd2Request', 19 | 'VerifyRequest', 20 | 'CheckCodeResponse', 21 | 'NetworkUnblockStatus', 22 | 'StartVerificationResponse', 23 | 'VerifyControlStatus', 24 | 'VerifyStatus', 25 | ] 26 | -------------------------------------------------------------------------------- /verify_legacy/src/vonage_verify_legacy/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.0.1' 2 | -------------------------------------------------------------------------------- /verify_legacy/src/vonage_verify_legacy/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class VerifyError(VonageError): 5 | """Indicates an error when using the legacy Vonage Verify API.""" 6 | -------------------------------------------------------------------------------- /verify_legacy/src/vonage_verify_legacy/language_codes.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class LanguageCode(str, Enum): 5 | """Language code used in a specific Verify request.""" 6 | 7 | ar_xa = 'ar-xa' 8 | cs_cz = 'cs-cz' 9 | cy_cy = 'cy-cy' 10 | cy_gb = 'cy-gb' 11 | da_dk = 'da-dk' 12 | de_de = 'de-de' 13 | el_gr = 'el-gr' 14 | en_au = 'en-au' 15 | en_gb = 'en-gb' 16 | en_in = 'en-in' 17 | en_us = 'en-us' 18 | es_es = 'es-es' 19 | es_mx = 'es-mx' 20 | es_us = 'es-us' 21 | fi_fi = 'fi-fi' 22 | fil_ph = 'fil-ph' 23 | fr_ca = 'fr-ca' 24 | fr_fr = 'fr-fr' 25 | hi_in = 'hi-in' 26 | hu_hu = 'hu-hu' 27 | id_id = 'id-id' 28 | is_is = 'is-is' 29 | it_it = 'it-it' 30 | ja_jp = 'ja-jp' 31 | ko_kr = 'ko-kr' 32 | nb_no = 'nb-no' 33 | nl_nl = 'nl-nl' 34 | pl_pl = 'pl-pl' 35 | pt_br = 'pt-br' 36 | pt_pt = 'pt-pt' 37 | ro_ro = 'ro-ro' 38 | ru_ru = 'ru-ru' 39 | sv_se = 'sv-se' 40 | th_th = 'th-th' 41 | tr_tr = 'tr-tr' 42 | vi_vn = 'vi-vn' 43 | yue_cn = 'yue-cn' 44 | zh_cn = 'zh-cn' 45 | zh_tw = 'zh-tw' 46 | 47 | 48 | class Psd2LanguageCode(str, Enum): 49 | """Language code used in a specific Verify PSD2 request.""" 50 | 51 | en_gb = 'en-gb' 52 | bg_bg = 'bg-bg' 53 | cs_cz = 'cs-cz' 54 | da_dk = 'da-dk' 55 | de_de = 'de-de' 56 | ee_et = 'ee-et' 57 | el_gr = 'el-gr' 58 | es_es = 'es-es' 59 | fi_fi = 'fi-fi' 60 | fr_fr = 'fr-fr' 61 | ga_ie = 'ga-ie' 62 | hu_hu = 'hu-hu' 63 | it_it = 'it-it' 64 | lv_lv = 'lv-lv' 65 | lt_lt = 'lt-lt' 66 | mt_mt = 'mt-mt' 67 | nl_nl = 'nl-nl' 68 | pl_pl = 'pl-pl' 69 | sk_sk = 'sk-sk' 70 | sl_si = 'sl-si' 71 | sv_se = 'sv-se' 72 | -------------------------------------------------------------------------------- /verify_legacy/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['verify_legacy', 'testutils']) 2 | -------------------------------------------------------------------------------- /verify_legacy/tests/data/cancel_verification.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "0", 3 | "command": "cancel" 4 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/cancel_verification_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "6", 3 | "error_text": "The requestId 'cc121958d8fb4368aa3bb762bb9a0f75' does not exist or its no longer active." 4 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/check_code.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "c5037cb8b47449158ed6611afde58990", 3 | "status": "0", 4 | "event_id": "390f7296-aeff-45ba-8931-84a13f3f76d7", 5 | "price": "0.05000000", 6 | "currency": "EUR", 7 | "estimated_price_messages_sent": "0.04675" 8 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/check_code_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "cc121958d8fb4368aa3bb762bb9a0f74", 3 | "status": "16", 4 | "error_text": "The code provided does not match the expected value" 5 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/network_unblock.json: -------------------------------------------------------------------------------- 1 | { 2 | "network": "23410", 3 | "unblocked_until": "2024-04-22T08:34:58Z" 4 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/network_unblock_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "type": "https://developer.vonage.com/api-errors#bad-request", 3 | "title": "Not Found", 4 | "detail": "The network you provided does not have an active block.", 5 | "instance": "bf0ca0bf927b3b52e3cb03217e1a1ddf" 6 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/search_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "cc121958d8fb4368aa3bb762bb9a0f74", 3 | "account_id": "abcdef01", 4 | "status": "EXPIRED", 5 | "number": "1234567890", 6 | "price": "0", 7 | "currency": "EUR", 8 | "sender_id": "Acme Inc.", 9 | "date_submitted": "2024-04-03 02:22:37", 10 | "date_finalized": "2024-04-03 02:27:38", 11 | "first_event_date": "2024-04-03 02:22:37", 12 | "last_event_date": "2024-04-03 02:24:38", 13 | "checks": [ 14 | { 15 | "date_received": "2024-04-03 02:23:04", 16 | "code": "1234", 17 | "status": "INVALID", 18 | "ip_address": "" 19 | } 20 | ], 21 | "events": [ 22 | { 23 | "type": "sms", 24 | "id": "23f3a13d-6d03-4262-8f4d-67f12a56e1c8" 25 | }, 26 | { 27 | "type": "sms", 28 | "id": "09ef3984-3f62-453d-8f9c-1a161b373dba" 29 | } 30 | ], 31 | "estimated_price_messages_sent": "0.09350" 32 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/search_request_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "101", 3 | "error_text": "No response found" 4 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/trigger_next_event.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "0", 3 | "command": "trigger_next_event" 4 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/trigger_next_event_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "status": "19", 3 | "error_text": "No more events are left to execute for the request ['2c021d25cf2e47a9b277a996f4325b81']" 4 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/verify_request.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "abcdef0123456789abcdef0123456789", 3 | "status": "0" 4 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/verify_request_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "b6fc2b91d23c43f9b8ea05f9be64415c", 3 | "status": "10", 4 | "error_text": "Concurrent verifications to the same number are not allowed" 5 | } -------------------------------------------------------------------------------- /verify_legacy/tests/data/verify_request_error_with_network.json: -------------------------------------------------------------------------------- 1 | { 2 | "request_id": "b6fc2b91d23c43f9b8ea05f9be64415c", 3 | "status": "10", 4 | "error_text": "Concurrent verifications to the same number are not allowed", 5 | "network": "244523" 6 | } -------------------------------------------------------------------------------- /video/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-video', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'video/src/vonage_video', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /video/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.2.0 2 | - Make all models originally accessed by `vonage_video.models.***` available at the top level of the package, i.e. `vonage_video.***` 3 | 4 | # 1.1.0 5 | - Add new `max_bitrate` field for archives 6 | 7 | # 1.0.4 8 | - Updated dependency versions 9 | 10 | # 1.0.3 11 | - Make the filter optional in `Video.list_archives` and `Video.list_broadcasts` 12 | 13 | # 1.0.2 14 | - Update dependency versions 15 | 16 | # 1.0.1 17 | - Support for Python 3.13, drop support for 3.8 18 | 19 | # 1.0.0 20 | - Initial upload 21 | -------------------------------------------------------------------------------- /video/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-video' 3 | dynamic = ["version"] 4 | description = 'Vonage video package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_video._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /video/src/vonage_video/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /video/src/vonage_video/__init__.py: -------------------------------------------------------------------------------- 1 | from . import errors, models # Import models to access the module directly 2 | from .models import * # Need this to directly expose data models 3 | from .video import Video 4 | 5 | __all__ = ['Video', 'errors', 'models'] 6 | -------------------------------------------------------------------------------- /video/src/vonage_video/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.2.0' 2 | -------------------------------------------------------------------------------- /video/src/vonage_video/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class VideoError(VonageError): 5 | """Indicates an error when using the Vonage Voice API.""" 6 | 7 | 8 | class InvalidRoleError(VideoError): 9 | """The specified role was invalid.""" 10 | 11 | 12 | class TokenExpiryError(VideoError): 13 | """The specified token expiry time was invalid.""" 14 | 15 | 16 | class SipError(VideoError): 17 | """Error related to usage of SIP calls.""" 18 | 19 | 20 | class NoAudioOrVideoError(VideoError): 21 | """Either an audio or video stream must be included.""" 22 | 23 | 24 | class IndividualArchivePropertyError(VideoError): 25 | """The property cannot be set for `archive_mode: 'individual'`.""" 26 | 27 | 28 | class LayoutStylesheetError(VideoError): 29 | """Error with the `stylesheet` property when setting a layout.""" 30 | 31 | 32 | class LayoutScreenshareTypeError(VideoError): 33 | """Error with the `screenshare_type` property when setting a layout.""" 34 | 35 | 36 | class InvalidArchiveStateError(VideoError): 37 | """The archive state was invalid for the specified operation.""" 38 | 39 | 40 | class InvalidHlsOptionsError(VideoError): 41 | """The HLS options were invalid.""" 42 | 43 | 44 | class InvalidOutputOptionsError(VideoError): 45 | """The output options were invalid.""" 46 | 47 | 48 | class InvalidBroadcastStateError(VideoError): 49 | """The broadcast state was invalid for the specified operation.""" 50 | 51 | 52 | class RoutedSessionRequiredError(VideoError): 53 | """The operation requires a session with `media_mode=routed`.""" 54 | -------------------------------------------------------------------------------- /video/src/vonage_video/models/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /video/src/vonage_video/models/audio_connector.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | from vonage_video.models.enums import AudioSampleRate 5 | 6 | 7 | class AudioConnectorWebSocket(BaseModel): 8 | """The audio connector websocket options. 9 | 10 | Args: 11 | uri (str): The URI. 12 | streams (list[str]): Stream IDs to include. If not provided, all streams are included. 13 | headers (dict): The headers to send to your WebSocket server. 14 | audio_rate (AudioSampleRate): The audio sample rate in Hertz. 15 | """ 16 | 17 | uri: str 18 | streams: Optional[list[str]] = None 19 | headers: Optional[dict] = None 20 | audio_rate: Optional[AudioSampleRate] = Field(None, serialization_alias='audioRate') 21 | 22 | 23 | class AudioConnectorOptions(BaseModel): 24 | """Options for the audio connector. 25 | 26 | Args: 27 | session_id (str): The session ID. 28 | token (str): The token. 29 | websocket (AudioConnectorWebSocket): The audio connector websocket. 30 | """ 31 | 32 | session_id: str = Field(..., serialization_alias='sessionId') 33 | token: str 34 | websocket: AudioConnectorWebSocket 35 | 36 | 37 | class AudioConnectorData(BaseModel): 38 | """Class containing Audio Connector WebSocket ID and connection ID. 39 | 40 | Args: 41 | id (str, Optional): The WebSocket ID. 42 | connection_id (str, Optional): The connection ID. 43 | """ 44 | 45 | id: Optional[str] = None 46 | connection_id: Optional[str] = Field(None, validation_alias='connectionId') 47 | -------------------------------------------------------------------------------- /video/src/vonage_video/models/captions.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | from .enums import LanguageCode 6 | 7 | 8 | class CaptionsOptions(BaseModel): 9 | """The Options to send captions. 10 | 11 | Args: 12 | session_id (str): The session ID. 13 | token (str): A valid token with moderation privileges. 14 | language_code (LanguageCode, Optional): The language code. 15 | max_duration (int, Optional): The maximum duration. 16 | partial_captions (bool, Optional): The partial captions. 17 | status_callback_url (str, Optional): The status callback URL. 18 | """ 19 | 20 | session_id: str = Field(..., serialization_alias='sessionId') 21 | token: str 22 | language_code: Optional[LanguageCode] = Field( 23 | None, serialization_alias='languageCode' 24 | ) 25 | max_duration: Optional[int] = Field( 26 | None, ge=300, le=14400, serialization_alias='maxDuration' 27 | ) 28 | partial_captions: Optional[bool] = Field(None, serialization_alias='partialCaptions') 29 | status_callback_url: Optional[str] = Field( 30 | None, min_length=15, max_length=2048, serialization_alias='statusCallbackUrl' 31 | ) 32 | 33 | 34 | class CaptionsData(BaseModel): 35 | """Class containing captions ID. 36 | 37 | Args: 38 | captions_id (str): The captions ID. 39 | """ 40 | 41 | captions_id: str = Field(..., serialization_alias='captionsId') 42 | -------------------------------------------------------------------------------- /video/src/vonage_video/models/signal.py: -------------------------------------------------------------------------------- 1 | from pydantic import BaseModel, Field 2 | 3 | 4 | class SignalData(BaseModel): 5 | """The data to send in a signal. 6 | 7 | Args: 8 | type (str): The type of data being sent to the client. 9 | data (str): Payload to send to the client. 10 | """ 11 | 12 | type: str = Field(..., max_length=128) 13 | data: str = Field(..., max_length=8192) 14 | -------------------------------------------------------------------------------- /video/src/vonage_video/models/stream.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel, Field 4 | 5 | 6 | class StreamInfo(BaseModel): 7 | """The stream information. 8 | 9 | Args: 10 | id (str): The stream ID. 11 | video_type (str): Set to "camera", "screen", or "custom". A "screen" video uses 12 | screen sharing on the publisher as the video source; a "custom" video is 13 | published by a web client using an HTML VideoTrack element as the video 14 | source. 15 | name (str): An array of the layout classes for the stream. 16 | layout_class_list (list[str]): An array of the layout classes for the stream. 17 | """ 18 | 19 | id: Optional[str] = Field(None, validation_alias='id') 20 | video_type: Optional[str] = Field(None, validation_alias='videoType') 21 | name: Optional[str] = Field(None, validation_alias='name') 22 | layout_class_list: Optional[list[str]] = Field( 23 | None, validation_alias='layoutClassList' 24 | ) 25 | 26 | 27 | class StreamLayout(BaseModel): 28 | """The stream layout. 29 | 30 | Args: 31 | id (str): The stream ID. 32 | layout_class_list (list[str]): An array of the layout classes for the stream. 33 | """ 34 | 35 | id: str 36 | layout_class_list: list[str] = Field(..., serialization_alias='layoutClassList') 37 | 38 | 39 | class StreamLayoutOptions(BaseModel): 40 | """The options for the stream layout. 41 | 42 | Args: 43 | items (list[[StreamLayout]]): An array of the stream layout items. Each item is a StreamLayout 44 | object. See StreamLayout. 45 | """ 46 | 47 | items: list[StreamLayout] 48 | -------------------------------------------------------------------------------- /video/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['video', 'testutils']) 2 | -------------------------------------------------------------------------------- /video/tests/data/archive.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "5b1521e6-115f-4efd-bed9-e527b87f0699", 3 | "status": "started", 4 | "name": "first archive test", 5 | "reason": "", 6 | "sessionId": "test_session_id", 7 | "applicationId": "test_application_id", 8 | "createdAt": 1727870434974, 9 | "size": 0, 10 | "duration": 0, 11 | "outputMode": "composed", 12 | "streamMode": "manual", 13 | "hasAudio": true, 14 | "hasVideo": true, 15 | "hasTranscription": false, 16 | "sha256sum": "", 17 | "password": "", 18 | "updatedAt": 1727870434977, 19 | "multiArchiveTag": "my-multi-archive", 20 | "event": "archive", 21 | "resolution": "1280x720", 22 | "url": null, 23 | "maxBitrate": 2000000 24 | } -------------------------------------------------------------------------------- /video/tests/data/audio_connector.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "b3cd31f4-020e-4ba3-9a2a-12d98b8a184f", 3 | "connectionId": "1bf530df-97f4-4437-b6c9-2a66200200c8" 4 | } -------------------------------------------------------------------------------- /video/tests/data/broadcast.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f03fad17-4591-4422-8bd3-00a4df1e616a", 3 | "sessionId": "test_session_id", 4 | "applicationId": "test_application_id", 5 | "createdAt": 1728039361014, 6 | "broadcastUrls": { 7 | "rtmp": [ 8 | { 9 | "status": "connecting", 10 | "id": "test", 11 | "serverUrl": "rtmp://a.rtmp.youtube.com/live2", 12 | "streamName": "stream-key" 13 | } 14 | ], 15 | "hls": "https://broadcast2-euw1-cdn.media.prod.tokbox.com/broadcast-57d6569497-dj9b2.10293/broadcast-57d6569497-dj9b2.10293_f03fad17-4591-4422-8bd3-00a4df1e616a_29f760f8-7ce1-46c9-ade3-f2dedee4ed5f.2_MX4yOWY3NjBmOC03Y2UxLTQ2YzktYWRlMy1mMmRlZGVlNGVkNWZ-fjE3MjgwMzY0MTUzMDd-V2swbzlzeUppaGZIVTFzYUQwamdYM0Ryfn5-.smil/playlist.m3u8?DVR" 16 | }, 17 | "updatedAt": 1728039361511, 18 | "status": "started", 19 | "streamMode": "auto", 20 | "hasAudio": true, 21 | "hasVideo": true, 22 | "maxDuration": 3600, 23 | "multiBroadcastTag": "test-broadcast-5", 24 | "maxBitrate": 1000000, 25 | "settings": { 26 | "hls": { 27 | "lowLatency": false, 28 | "dvr": true 29 | } 30 | }, 31 | "event": "broadcast", 32 | "resolution": "1280x720" 33 | } -------------------------------------------------------------------------------- /video/tests/data/captions_error_already_enabled.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 60003, 3 | "message": "Audio captioning is already enabled", 4 | "description": "Audio captioning is already enabled" 5 | } -------------------------------------------------------------------------------- /video/tests/data/change_stream_layout.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "items": [ 4 | { 5 | "id": "e08ff3f4-d04b-4363-bd6c-31bd29648ec8", 6 | "videoType": "camera", 7 | "name": "", 8 | "layoutClassList": [ 9 | "full" 10 | ] 11 | } 12 | ] 13 | } -------------------------------------------------------------------------------- /video/tests/data/create_session.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "session_id": "1_MX4yOWY3NjBmOC03Y2UxLTQ2YzktYWRlMy1mMmRlZGVlNGVkNWZ-fjE3MjY0NjI1ODg2NDd-MTF4TGExYmJoelBlR1FHbVhzbWd4STBrfn5-", 4 | "project_id": "29f760f8-7ce1-46c9-ade3-f2dedee4ed5f", 5 | "partner_id": "29f760f8-7ce1-46c9-ade3-f2dedee4ed5f", 6 | "create_dt": "Sun Sep 15 21:56:28 PDT 2024", 7 | "session_status": null, 8 | "status_invalid": null, 9 | "media_server_hostname": null, 10 | "messaging_server_url": null, 11 | "messaging_url": null, 12 | "symphony_address": null, 13 | "properties": null, 14 | "ice_server": null, 15 | "session_segment_id": "35308566-4012-4c1e-90f7-cc15b5a390fe", 16 | "ice_servers": null, 17 | "ice_credential_expiration": 86100 18 | } 19 | ] -------------------------------------------------------------------------------- /video/tests/data/delete_archive_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 15004, 3 | "message": "You can only delete an archive that has one of the following statuses: available OR uploaded OR deleted", 4 | "description": "You can only delete an archive that has one of the following statuses: available OR uploaded OR deleted" 5 | } -------------------------------------------------------------------------------- /video/tests/data/get_experience_composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "be7712a4-3a63-4ed7-a2c6-7ffaebefd4a6", 3 | "sessionId": "test_session_id", 4 | "createdAt": 1727784741000, 5 | "updatedAt": 1727788344000, 6 | "url": "https://developer.vonage.com", 7 | "status": "stopped", 8 | "streamId": "C1B0E149-8169-4AFD-9397-882516EE9430", 9 | "reason": "Max duration exceeded", 10 | "event": "render", 11 | "applicationId": "test_application_id", 12 | "resolution": "1280x720" 13 | } -------------------------------------------------------------------------------- /video/tests/data/get_stream.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "e08ff3f4-d04b-4363-bd6c-31bd29648ec8", 3 | "videoType": "camera", 4 | "name": "", 5 | "layoutClassList": [] 6 | } -------------------------------------------------------------------------------- /video/tests/data/initiate_sip_call.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "0022f6ba-c3a7-44db-843e-dd5ffa9d0493", 3 | "projectId": "29f760f8-7ce1-46c9-ade3-f2dedee4ed5f", 4 | "sessionId": "test_session_id", 5 | "connectionId": "4baf5788-fa5d-4b8d-b344-7315194ebc7d", 6 | "streamId": "de7d4fde-1773-4c7f-a0f8-3e1e2956d739", 7 | "createdAt": 1728383115393, 8 | "updatedAt": 1728383115393 9 | } -------------------------------------------------------------------------------- /video/tests/data/list_archives.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "items": [ 4 | { 5 | "id": "5b1521e6-115f-4efd-bed9-e527b87f0699", 6 | "status": "paused", 7 | "name": "first archive test", 8 | "reason": "", 9 | "sessionId": "test_session_id", 10 | "applicationId": "test_application_id", 11 | "createdAt": 1727871263000, 12 | "size": 0, 13 | "duration": 0, 14 | "outputMode": "composed", 15 | "streamMode": "manual", 16 | "hasAudio": true, 17 | "hasVideo": true, 18 | "hasTranscription": false, 19 | "sha256sum": "", 20 | "password": "", 21 | "updatedAt": 1727871264000, 22 | "multiArchiveTag": "my-multi-archive", 23 | "event": "archive", 24 | "resolution": "1280x720", 25 | "url": null 26 | }, 27 | { 28 | "id": "a9cdeb69-f6cf-408b-9197-6f99e6eac5aa", 29 | "status": "available", 30 | "name": "first archive test", 31 | "reason": "session ended", 32 | "sessionId": "test_session_id", 33 | "applicationId": "test_application_id", 34 | "createdAt": 1727870435000, 35 | "size": 0, 36 | "duration": 134, 37 | "outputMode": "composed", 38 | "streamMode": "manual", 39 | "hasAudio": true, 40 | "hasVideo": true, 41 | "hasTranscription": false, 42 | "sha256sum": "test_sha256_sum", 43 | "password": "", 44 | "updatedAt": 1727870572000, 45 | "multiArchiveTag": "my-multi-archive", 46 | "event": "archive", 47 | "resolution": "1280x720", 48 | "url": "https://example.com/archive.mp4", 49 | "maxBitrate": 2000000 50 | } 51 | ] 52 | } -------------------------------------------------------------------------------- /video/tests/data/list_broadcasts_next_page.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 2, 3 | "items": [ 4 | { 5 | "id": "32cd16ee-715b-4025-bbc6-f314c1459e2f", 6 | "sessionId": "test_session_id", 7 | "applicationId": "test_application_id", 8 | "createdAt": 1728038157850, 9 | "broadcastUrls": { 10 | "rtmp": [ 11 | { 12 | "status": "offline", 13 | "id": "test", 14 | "serverUrl": "rtmp://a.rtmp.youtube.com/live2", 15 | "streamName": "stream-key" 16 | } 17 | ], 18 | "hlsStatus": "ready", 19 | "hls": "https://example.com/hls.m3u8" 20 | }, 21 | "updatedAt": 1728038163321, 22 | "status": "started", 23 | "streamMode": "auto", 24 | "hasAudio": true, 25 | "hasVideo": true, 26 | "maxDuration": 3600, 27 | "multiBroadcastTag": "test-broadcast-1", 28 | "maxBitrate": 1000000, 29 | "settings": { 30 | "hls": { 31 | "lowLatency": false, 32 | "dvr": true 33 | } 34 | }, 35 | "event": "broadcast", 36 | "resolution": "1280x720" 37 | } 38 | ] 39 | } -------------------------------------------------------------------------------- /video/tests/data/list_experience_composers.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 3, 3 | "items": [ 4 | { 5 | "id": "be7712a4-3a63-4ed7-a2c6-7ffaebefd4a6", 6 | "sessionId": "test_session_id", 7 | "createdAt": 1727784741000, 8 | "updatedAt": 1727784744000, 9 | "url": "https://developer.vonage.com", 10 | "status": "started", 11 | "streamId": "C1B0E149-8169-4AFD-9397-882516EE9430", 12 | "event": "render", 13 | "applicationId": "test_application_id", 14 | "resolution": "1280x720" 15 | }, 16 | { 17 | "id": "89559e73-0d49-4388-b373-ddef191e4373", 18 | "sessionId": "test_session_id", 19 | "createdAt": 1727784421000, 20 | "updatedAt": 1727784424000, 21 | "url": "https://example.com", 22 | "status": "started", 23 | "streamId": "F9C3BCD5-850F-4DB7-B6C1-97F615CA9E79", 24 | "event": "render", 25 | "applicationId": "test_application_id", 26 | "resolution": "1280x720" 27 | }, 28 | { 29 | "id": "80c3d2d8-0848-41b2-be14-1a5b8936c87d", 30 | "sessionId": "test_session_id", 31 | "createdAt": 1727781191000, 32 | "updatedAt": 1727784793000, 33 | "url": "https://example.com", 34 | "status": "stopped", 35 | "streamId": "95F83A10-D767-4F21-9270-DC6E88067FAC", 36 | "reason": "Max duration exceeded", 37 | "event": "render", 38 | "applicationId": "test_application_id", 39 | "resolution": "1280x720" 40 | } 41 | ] 42 | } -------------------------------------------------------------------------------- /video/tests/data/list_streams.json: -------------------------------------------------------------------------------- 1 | { 2 | "count": 1, 3 | "items": [ 4 | { 5 | "id": "e08ff3f4-d04b-4363-bd6c-31bd29648ec8", 6 | "videoType": "camera", 7 | "name": "", 8 | "layoutClassList": [] 9 | } 10 | ] 11 | } -------------------------------------------------------------------------------- /video/tests/data/nothing.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /video/tests/data/start_broadcast_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Session is already composed for given tag with code 409" 3 | } -------------------------------------------------------------------------------- /video/tests/data/start_captions.json: -------------------------------------------------------------------------------- 1 | { 2 | "captionsId": "bc01a6b7-0e8e-4aa0-bb4e-2390f7cb18a1" 3 | } -------------------------------------------------------------------------------- /video/tests/data/start_experience_composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "80c3d2d8-0848-41b2-be14-1a5b8936c87d", 3 | "sessionId": "test_session_id", 4 | "createdAt": 1727781191064, 5 | "updatedAt": 1727781191064, 6 | "url": "https://example.com", 7 | "status": "starting", 8 | "name": "test_experience_composer", 9 | "event": "render", 10 | "applicationId": "test_application_id", 11 | "resolution": "1280x720" 12 | } -------------------------------------------------------------------------------- /video/tests/data/stop_archive.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "e05d6f8f-2280-4025-b1d2-defc4f5c8dfa", 3 | "status": "stopped", 4 | "name": "archive test", 5 | "reason": "user initiated", 6 | "sessionId": "2_MX4yOWY3NjBmOC03Y2UxLTQ2YzktYWRlMy1mMmRlZGVlNGVkNWZ-fjE3Mjc4NzcyMTYwNzJ-OUJ1WHN0V05vN0NoU044OGthaURwNmpxfn5-", 7 | "applicationId": "29f760f8-7ce1-46c9-ade3-f2dedee4ed5f", 8 | "createdAt": 1727887464000, 9 | "size": 0, 10 | "duration": 0, 11 | "outputMode": "composed", 12 | "streamMode": "auto", 13 | "hasAudio": true, 14 | "hasVideo": true, 15 | "hasTranscription": false, 16 | "sha256sum": "", 17 | "password": "", 18 | "updatedAt": 1727887464000, 19 | "multiArchiveTag": "start-to-stop", 20 | "event": "archive", 21 | "resolution": "1280x720", 22 | "url": null 23 | } -------------------------------------------------------------------------------- /video/tests/data/stop_archive_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": 15002, 3 | "message": "You can only stop an archive that has one of the following statuses: started OR paused OR stopped", 4 | "description": "You can only stop an archive that has one of the following statuses: started OR paused OR stopped" 5 | } -------------------------------------------------------------------------------- /video/tests/data/stop_broadcast.json: -------------------------------------------------------------------------------- 1 | { 2 | "id": "f03fad17-4591-4422-8bd3-00a4df1e616a", 3 | "sessionId": "test_session_id", 4 | "applicationId": "test_application_id", 5 | "createdAt": 1728060457664, 6 | "broadcastUrls": null, 7 | "updatedAt": 1728060581508, 8 | "status": "stopped", 9 | "streamMode": "auto", 10 | "hasAudio": true, 11 | "hasVideo": true, 12 | "maxDuration": 3600, 13 | "multiBroadcastTag": "test-broadcast", 14 | "maxBitrate": 1000000, 15 | "settings": { 16 | "hls": { 17 | "lowLatency": false, 18 | "dvr": true 19 | } 20 | }, 21 | "event": "broadcast", 22 | "resolution": "1280x720" 23 | } -------------------------------------------------------------------------------- /video/tests/data/stop_broadcast_timeout_error.json: -------------------------------------------------------------------------------- 1 | { 2 | "code": -1, 3 | "message": "Request timed out.", 4 | "description": "Request timed out." 5 | } -------------------------------------------------------------------------------- /video/tests/test_signal.py: -------------------------------------------------------------------------------- 1 | from os.path import abspath 2 | 3 | import responses 4 | from vonage_http_client import HttpClient 5 | from vonage_video.models.signal import SignalData 6 | from vonage_video.video import Video 7 | 8 | from testutils import build_response, get_mock_jwt_auth 9 | 10 | path = abspath(__file__) 11 | 12 | 13 | video = Video(HttpClient(get_mock_jwt_auth())) 14 | 15 | 16 | @responses.activate 17 | def test_send_signal_all(): 18 | build_response( 19 | path, 20 | 'POST', 21 | 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/signal', 22 | status_code=204, 23 | ) 24 | 25 | video.send_signal( 26 | session_id='test_session_id', data=SignalData(type='msg', data='Hello, World!') 27 | ) 28 | 29 | assert responses.calls[0].response.status_code == 204 30 | 31 | 32 | @responses.activate 33 | def test_send_signal_to_connection_id(): 34 | build_response( 35 | path, 36 | 'POST', 37 | 'https://video.api.vonage.com/v2/project/test_application_id/session/test_session_id/connection/test_connection_id/signal', 38 | status_code=204, 39 | ) 40 | 41 | video.send_signal( 42 | session_id='test_session_id', 43 | data=SignalData(type='msg', data='Hello, World!'), 44 | connection_id='test_connection_id', 45 | ) 46 | 47 | assert responses.calls[0].response.status_code == 204 48 | -------------------------------------------------------------------------------- /voice/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | file(name='readme', source='README.md') 3 | 4 | files(sources=['tests/data/*']) 5 | 6 | python_distribution( 7 | name='vonage-voice', 8 | dependencies=[ 9 | ':pyproject', 10 | ':readme', 11 | 'voice/src/vonage_voice', 12 | ], 13 | provides=python_artifact(), 14 | generate_setup=False, 15 | repositories=['@pypi'], 16 | ) 17 | -------------------------------------------------------------------------------- /voice/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.4.0 2 | - Increase maximum value of call `length_timer` to 86400s 3 | - Add additional fields `eventUrl` and `eventMethod` to NCCO model 4 | 5 | # 1.3.0 6 | - Add new `headers` and `standard_headers` options to the `Sip` data model 7 | - Add new `standardHeaders` option to the `SipEndpoint` NCCO model 8 | - Add check for invalid hostnames when downloading a recording with `Voice.download_recording` 9 | - Allow the `CreateCallRequest` model to accept a SIP URI as well as a phone number in the `from_` field 10 | 11 | # 1.2.0 12 | - Make all models originally accessed by `vonage_voice.models.***` available at the top level of the package, i.e. `vonage_voice.***` 13 | 14 | # 1.1.2 15 | - Update incorrect return type annotation for `Voice.download_recording` 16 | 17 | # 1.1.1 18 | - Remove maximum webhook uri length constraint 19 | 20 | # 1.1.0 21 | - Add `Voice.get_recording` method to get call recordings 22 | - Add `Voice.verify_signature` method to expose the verification functionality from `vonage-jwt` 23 | - Updated dependency versions 24 | 25 | # 1.0.6 26 | - Update dependency versions 27 | 28 | # 1.0.5 29 | - Support for Python 3.13, drop support for 3.8 30 | 31 | # 1.0.4 32 | - Add docstrings to data models 33 | 34 | # 1.0.3 35 | - Internal refactoring 36 | 37 | # 1.0.2 38 | - Update minimum dependency version 39 | 40 | # 1.0.1 41 | - Initial upload 42 | 43 | # 1.0.0 44 | - This version was skipped due to a technical issue with the package distribution. Please use version 1.0.1 or later. -------------------------------------------------------------------------------- /voice/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-voice' 3 | dynamic = ["version"] 4 | description = 'Vonage voice package' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | requires-python = ">=3.9" 8 | dependencies = [ 9 | "vonage-http-client>=1.5.0", 10 | "vonage-utils>=1.1.4", 11 | "pydantic>=2.9.2", 12 | ] 13 | classifiers = [ 14 | "Programming Language :: Python", 15 | "Programming Language :: Python :: 3", 16 | "Programming Language :: Python :: 3.9", 17 | "Programming Language :: Python :: 3.10", 18 | "Programming Language :: Python :: 3.11", 19 | "Programming Language :: Python :: 3.12", 20 | "Programming Language :: Python :: 3.13", 21 | "License :: OSI Approved :: Apache Software License", 22 | ] 23 | 24 | [project.urls] 25 | homepage = "https://github.com/Vonage/vonage-python-sdk" 26 | 27 | [tool.setuptools.dynamic] 28 | version = { attr = "vonage_voice._version.__version__" } 29 | 30 | [build-system] 31 | requires = ["setuptools>=61.0", "wheel"] 32 | build-backend = "setuptools.build_meta" 33 | -------------------------------------------------------------------------------- /voice/src/vonage_voice/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /voice/src/vonage_voice/__init__.py: -------------------------------------------------------------------------------- 1 | from . import errors, models # Import models to access the module directly 2 | from .models import * # Need this to directly expose data models 3 | from .voice import Voice 4 | 5 | __all__ = ['Voice', 'errors', 'models'] 6 | -------------------------------------------------------------------------------- /voice/src/vonage_voice/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.4.0' 2 | -------------------------------------------------------------------------------- /voice/src/vonage_voice/errors.py: -------------------------------------------------------------------------------- 1 | from vonage_utils.errors import VonageError 2 | 3 | 4 | class VoiceError(VonageError): 5 | """Indicates an error when using the Vonage Voice API.""" 6 | 7 | 8 | class NccoActionError(VoiceError): 9 | """Indicates an error when using an NCCO action.""" 10 | -------------------------------------------------------------------------------- /voice/src/vonage_voice/models/BUILD: -------------------------------------------------------------------------------- 1 | python_sources() 2 | -------------------------------------------------------------------------------- /voice/src/vonage_voice/models/__init__.py: -------------------------------------------------------------------------------- 1 | from .common import AdvancedMachineDetection, Phone, Sip, Vbc, Websocket 2 | from .connect_endpoints import ( 3 | AppEndpoint, 4 | OnAnswer, 5 | PhoneEndpoint, 6 | SipEndpoint, 7 | VbcEndpoint, 8 | WebsocketEndpoint, 9 | ) 10 | from .enums import ( 11 | CallState, 12 | Channel, 13 | ConnectEndpointType, 14 | NccoActionType, 15 | TtsLanguageCode, 16 | ) 17 | from .input_types import Dtmf, Speech 18 | from .ncco import Connect, Conversation, Input, NccoAction, Notify, Record, Stream, Talk 19 | from .requests import ( 20 | AudioStreamOptions, 21 | CreateCallRequest, 22 | ListCallsFilter, 23 | ToPhone, 24 | TtsStreamOptions, 25 | ) 26 | from .responses import ( 27 | CallInfo, 28 | CallList, 29 | CallMessage, 30 | CreateCallResponse, 31 | Embedded, 32 | HalLinks, 33 | ) 34 | 35 | __all__ = [ 36 | 'AdvancedMachineDetection', 37 | 'AppEndpoint', 38 | 'AudioStreamOptions', 39 | 'CallInfo', 40 | 'CallList', 41 | 'CallMessage', 42 | 'CallState', 43 | 'Channel', 44 | 'Connect', 45 | 'ConnectEndpointType', 46 | 'Conversation', 47 | 'CreateCallRequest', 48 | 'CreateCallResponse', 49 | 'Dtmf', 50 | 'Embedded', 51 | 'Input', 52 | 'ListCallsFilter', 53 | 'HalLinks', 54 | 'NccoAction', 55 | 'NccoActionType', 56 | 'Notify', 57 | 'OnAnswer', 58 | 'Phone', 59 | 'PhoneEndpoint', 60 | 'Record', 61 | 'Sip', 62 | 'SipEndpoint', 63 | 'Speech', 64 | 'Stream', 65 | 'Talk', 66 | 'ToPhone', 67 | 'TtsLanguageCode', 68 | 'TtsStreamOptions', 69 | 'Vbc', 70 | 'VbcEndpoint', 71 | 'Websocket', 72 | 'WebsocketEndpoint', 73 | ] 74 | -------------------------------------------------------------------------------- /voice/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['voice', 'testutils']) 2 | -------------------------------------------------------------------------------- /voice/tests/data/create_call.json: -------------------------------------------------------------------------------- 1 | { 2 | "uuid": "106a581a-34d0-432a-a625-220221fd434f", 3 | "status": "started", 4 | "direction": "outbound", 5 | "conversation_uuid": "CON-2be039b2-d0a4-4274-afc8-d7b241c7c044" 6 | } -------------------------------------------------------------------------------- /voice/tests/data/file_stream.mp3: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Vonage/vonage-python-sdk/82a02e48b9793a2aa0bce3fa593de8cf97caf839/voice/tests/data/file_stream.mp3 -------------------------------------------------------------------------------- /voice/tests/data/get_call.json: -------------------------------------------------------------------------------- 1 | { 2 | "_links": { 3 | "self": { 4 | "href": "/v1/calls/e154eb57-2962-41e7-baf4-90f63e25e439" 5 | } 6 | }, 7 | "conversation_uuid": "CON-d4e1389a-b2c8-4621-97eb-c6f3a2b51c72", 8 | "direction": "outbound", 9 | "duration": "2", 10 | "end_time": "2024-04-19T01:34:20.000Z", 11 | "from": { 12 | "number": "9876543210", 13 | "type": "phone" 14 | }, 15 | "network": "23420", 16 | "price": "0.00333333", 17 | "rate": "0.10000000", 18 | "start_time": "2024-04-19T01:34:18.000Z", 19 | "status": "completed", 20 | "to": { 21 | "number": "1234567890", 22 | "type": "phone" 23 | }, 24 | "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" 25 | } -------------------------------------------------------------------------------- /voice/tests/data/list_calls_filter.json: -------------------------------------------------------------------------------- 1 | { 2 | "page_size": 1, 3 | "record_index": 1, 4 | "count": 3, 5 | "_embedded": { 6 | "calls": [ 7 | { 8 | "uuid": "1acdf499-83ae-4861-a694-9e47a98c505d", 9 | "status": "completed", 10 | "direction": "outbound", 11 | "rate": "0.10000000", 12 | "price": "0.00333333", 13 | "duration": "2", 14 | "network": "23420", 15 | "conversation_uuid": "CON-ee83ad86-ee21-4c28-bf7d-4ae67692721e", 16 | "start_time": "2024-04-18T16:01:51.000Z", 17 | "end_time": "2024-04-18T16:01:53.000Z", 18 | "to": { 19 | "type": "phone", 20 | "number": "1234567890" 21 | }, 22 | "from": { 23 | "type": "phone", 24 | "number": "9876543210" 25 | }, 26 | "_links": { 27 | "self": { 28 | "href": "/v1/calls/1acdf499-83ae-4861-a694-9e47a98c505d" 29 | } 30 | } 31 | } 32 | ] 33 | }, 34 | "_links": { 35 | "self": { 36 | "href": "/v1/calls?page_size=1&record_index=1" 37 | }, 38 | "first": { 39 | "href": "/v1/calls?page_size=1" 40 | }, 41 | "last": { 42 | "href": "/v1/calls?page_size=1&record_index=2" 43 | }, 44 | "next": { 45 | "href": "/v1/calls?page_size=1&record_index=2" 46 | }, 47 | "prev": { 48 | "href": "/v1/calls?page_size=1&record_index=0" 49 | } 50 | } 51 | } -------------------------------------------------------------------------------- /voice/tests/data/play_audio_into_call.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Stream started", 3 | "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" 4 | } -------------------------------------------------------------------------------- /voice/tests/data/play_dtmf_into_call.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "DTMF sent", 3 | "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" 4 | } -------------------------------------------------------------------------------- /voice/tests/data/play_tts_into_call.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Talk started", 3 | "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" 4 | } -------------------------------------------------------------------------------- /voice/tests/data/stop_audio_stream.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Stream stopped", 3 | "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" 4 | } -------------------------------------------------------------------------------- /voice/tests/data/stop_tts.json: -------------------------------------------------------------------------------- 1 | { 2 | "message": "Talk stopped", 3 | "uuid": "e154eb57-2962-41e7-baf4-90f63e25e439" 4 | } -------------------------------------------------------------------------------- /vonage/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | 3 | file(name='readme', source='README.md') 4 | 5 | python_distribution( 6 | name='vonage', 7 | dependencies=[':pyproject', ':readme', 'vonage/src/vonage'], 8 | provides=python_artifact(), 9 | generate_setup=False, 10 | repositories=['@pypi'], 11 | ) 12 | -------------------------------------------------------------------------------- /vonage/README.md: -------------------------------------------------------------------------------- 1 | # Vonage Python SDK 2 | 3 | The Vonage Python SDK Package `vonage` provides a streamlined interface for using Vonage APIs in Python projects. This package includes the `Vonage` class, which simplifies API interactions. 4 | 5 | The Vonage class in this package serves as the main entry point for using Vonage APIs. It abstracts away complexities with authentication, HTTP requests and more. 6 | 7 | For full API documentation refer to the [Vonage Developer documentation](https://developer.vonage.com). 8 | 9 | ## Installation 10 | 11 | Install the package using pip: 12 | 13 | ```bash 14 | pip install vonage 15 | ``` 16 | 17 | ## Usage 18 | 19 | ```python 20 | from vonage import Vonage, Auth, HttpClientOptions 21 | 22 | # Create an Auth instance 23 | auth = Auth(api_key='your_api_key', api_secret='your_api_secret') 24 | 25 | # Create HttpClientOptions instance 26 | # (not required unless you want to change options from the defaults) 27 | options = HttpClientOptions(api_host='api.nexmo.com', timeout=30) 28 | 29 | # Create a Vonage instance 30 | vonage = Vonage(auth=auth, http_client_options=options) 31 | ``` 32 | 33 | The Vonage class provides access to various Vonage APIs through its properties. For example, to use methods to call the SMS API: 34 | 35 | ```python 36 | from vonage_sms import SmsMessage 37 | 38 | message = SmsMessage(to='1234567890', from_='Vonage', text='Hello World') 39 | response = client.sms.send(message) 40 | print(response.model_dump_json(exclude_unset=True)) 41 | ``` 42 | 43 | You can also access the underlying `HttpClient` instance through the `http_client` property: 44 | 45 | ```python 46 | user_agent = vonage.http_client.user_agent 47 | ``` -------------------------------------------------------------------------------- /vonage/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = "vonage" 3 | dynamic = ["version"] 4 | description = "Python Server SDK for using Vonage APIs" 5 | readme = "README.md" 6 | requires-python = ">=3.9" 7 | dependencies = [ 8 | "vonage-utils>=1.1.4", 9 | "vonage-http-client>=1.5.1", 10 | "vonage-account>=1.1.1", 11 | "vonage-application>=2.0.1", 12 | "vonage-messages>=1.4.0", 13 | "vonage-network-auth>=1.0.2", 14 | "vonage-network-sim-swap>=1.1.2", 15 | "vonage-network-number-verification>=1.0.2", 16 | "vonage-number-insight>=1.0.7", 17 | "vonage-numbers>=1.0.4", 18 | "vonage-sms>=1.1.6", 19 | "vonage-subaccounts>=1.0.4", 20 | "vonage-users>=1.2.1", 21 | "vonage-verify>=2.1.0", 22 | "vonage-verify-legacy>=1.0.1", 23 | "vonage-video>=1.2.0", 24 | "vonage-voice>=1.4.0", 25 | ] 26 | classifiers = [ 27 | "Programming Language :: Python", 28 | "Programming Language :: Python :: 3", 29 | "Programming Language :: Python :: 3.9", 30 | "Programming Language :: Python :: 3.10", 31 | "Programming Language :: Python :: 3.11", 32 | "Programming Language :: Python :: 3.12", 33 | "Programming Language :: Python :: 3.13", 34 | "License :: OSI Approved :: Apache Software License", 35 | ] 36 | [[project.authors]] 37 | name = "Vonage" 38 | email = "devrel@vonage.com" 39 | 40 | [build-system] 41 | requires = ["setuptools>=61.0", "wheel"] 42 | build-backend = "setuptools.build_meta" 43 | 44 | [project.urls] 45 | homepage = "https://github.com/Vonage/vonage-python-sdk" 46 | 47 | [tool.setuptools.dynamic.version] 48 | attr = "vonage._version.__version__" 49 | -------------------------------------------------------------------------------- /vonage/src/vonage/BUILD: -------------------------------------------------------------------------------- 1 | python_sources(name='vonage') 2 | -------------------------------------------------------------------------------- /vonage/src/vonage/__init__.py: -------------------------------------------------------------------------------- 1 | from vonage_utils import VonageError 2 | 3 | from .vonage import ( 4 | Account, 5 | Application, 6 | Auth, 7 | HttpClientOptions, 8 | Messages, 9 | NetworkNumberVerification, 10 | NetworkSimSwap, 11 | NumberInsight, 12 | Numbers, 13 | Sms, 14 | Subaccounts, 15 | Users, 16 | Verify, 17 | VerifyLegacy, 18 | Video, 19 | Voice, 20 | Vonage, 21 | ) 22 | 23 | __all__ = [ 24 | 'Account', 25 | 'Application', 26 | 'Auth', 27 | 'HttpClientOptions', 28 | 'Messages', 29 | 'NetworkSimSwap', 30 | 'NetworkNumberVerification', 31 | 'NumberInsight', 32 | 'Numbers', 33 | 'Sms', 34 | 'Subaccounts', 35 | 'Users', 36 | 'Verify', 37 | 'VerifyLegacy', 38 | 'Video', 39 | 'Voice', 40 | 'Vonage', 41 | 'VonageError', 42 | ] 43 | -------------------------------------------------------------------------------- /vonage/src/vonage/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '4.4.3' 2 | -------------------------------------------------------------------------------- /vonage/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests() 2 | -------------------------------------------------------------------------------- /vonage/tests/test_vonage.py: -------------------------------------------------------------------------------- 1 | from vonage_http_client.http_client import HttpClient 2 | 3 | from vonage.vonage import Auth, Vonage, __version__ 4 | 5 | 6 | def test_create_vonage_class_instance(): 7 | vonage = Vonage(Auth(api_key='asdf', api_secret='qwerasdf')) 8 | 9 | assert vonage.http_client.auth.api_key == 'asdf' 10 | assert vonage.http_client.auth.api_secret == 'qwerasdf' 11 | assert ( 12 | vonage.http_client.auth.create_basic_auth_string() == 'Basic YXNkZjpxd2VyYXNkZg==' 13 | ) 14 | assert type(vonage.http_client) == HttpClient 15 | assert f'vonage-python-sdk/{__version__}' in vonage.http_client._user_agent 16 | -------------------------------------------------------------------------------- /vonage_utils/BUILD: -------------------------------------------------------------------------------- 1 | resource(name='pyproject', source='pyproject.toml') 2 | 3 | file(name='readme', source='README.md') 4 | 5 | python_distribution( 6 | name='vonage-utils', 7 | dependencies=[':pyproject', ':readme', 'vonage_utils/src/vonage_utils'], 8 | provides=python_artifact(), 9 | generate_setup=False, 10 | repositories=['@pypi'], 11 | ) 12 | -------------------------------------------------------------------------------- /vonage_utils/CHANGES.md: -------------------------------------------------------------------------------- 1 | # 1.1.4 2 | - Support for Python 3.13, drop support for 3.8 3 | 4 | # 1.1.3 5 | - Add docstrings to data models 6 | 7 | # 1.1.2 8 | - Refactoring common pydantic models across the monorepo into this package 9 | 10 | # 1.1.1 11 | - Update minimum dependency version 12 | 13 | # 1.1.0 14 | - Add `Dtmf` and `SipUri` types 15 | - Add `Link` model 16 | - Internal refactoring 17 | 18 | # 1.0.1 19 | - Add `PhoneNumber` type 20 | 21 | # 1.0.0 22 | - Initial upload -------------------------------------------------------------------------------- /vonage_utils/README.md: -------------------------------------------------------------------------------- 1 | # Vonage Utils Package 2 | 3 | This package contains utility code that is used by the Vonage Python SDK and other related packages. 4 | 5 | The utils module provides two utility functions: `format_phone_number` and `remove_none_values`. It also exposes the `VonageError` type that other exceptions related to Vonage SDK inherit from. This can also be accessed via the main SDK module with `vonage.VonageError`. 6 | 7 | ## Usage 8 | 9 | ```python 10 | from utils import format_phone_number, remove_none_values 11 | 12 | # Use format_phone_number 13 | try: 14 | formatted_number = format_phone_number('123-456-7890') 15 | print(formatted_number) 16 | except (InvalidPhoneNumberError, InvalidPhoneNumberTypeError) as e: 17 | print(e) 18 | 19 | # Use remove_none_values to remove null values from a Vonage API response when converting to a dictionary with the `asdict` method 20 | from dataclasses import asdict 21 | 22 | vonage_api_response = vonage.api.method() 23 | cleaned_dict = asdict(my_dataclass, dict_factory=remove_none_values) 24 | print(cleaned_dict) 25 | ``` -------------------------------------------------------------------------------- /vonage_utils/pyproject.toml: -------------------------------------------------------------------------------- 1 | [project] 2 | name = 'vonage-utils' 3 | dynamic = ["version"] 4 | description = 'Utils package containing objects for use with Vonage APIs' 5 | readme = "README.md" 6 | authors = [{ name = "Vonage", email = "devrel@vonage.com" }] 7 | dependencies = ["pydantic>=2.9.2"] 8 | requires-python = ">=3.9" 9 | classifiers = [ 10 | "Programming Language :: Python", 11 | "Programming Language :: Python :: 3", 12 | "Programming Language :: Python :: 3.9", 13 | "Programming Language :: Python :: 3.10", 14 | "Programming Language :: Python :: 3.11", 15 | "Programming Language :: Python :: 3.12", 16 | "Programming Language :: Python :: 3.13", 17 | "License :: OSI Approved :: Apache Software License", 18 | ] 19 | 20 | [project.urls] 21 | Homepage = "https://github.com/Vonage/vonage-python-sdk" 22 | 23 | [tool.setuptools.dynamic] 24 | version = { attr = "vonage_utils._version.__version__" } 25 | 26 | [build-system] 27 | requires = ["setuptools>=61.0", "wheel"] 28 | build-backend = "setuptools.build_meta" 29 | -------------------------------------------------------------------------------- /vonage_utils/src/vonage_utils/BUILD: -------------------------------------------------------------------------------- 1 | python_sources(name='vonage_utils') 2 | -------------------------------------------------------------------------------- /vonage_utils/src/vonage_utils/__init__.py: -------------------------------------------------------------------------------- 1 | from . import models, types 2 | from .errors import VonageError 3 | from .utils import format_phone_number, remove_none_values 4 | 5 | __all__ = ['VonageError', 'format_phone_number', 'remove_none_values', 'models', 'types'] 6 | -------------------------------------------------------------------------------- /vonage_utils/src/vonage_utils/_version.py: -------------------------------------------------------------------------------- 1 | __version__ = '1.1.4' 2 | -------------------------------------------------------------------------------- /vonage_utils/src/vonage_utils/errors.py: -------------------------------------------------------------------------------- 1 | class VonageError(Exception): 2 | """Base Error Class for all Vonage SDK errors.""" 3 | 4 | 5 | class InvalidPhoneNumberError(VonageError): 6 | """An invalid phone number was provided.""" 7 | 8 | 9 | class InvalidPhoneNumberTypeError(VonageError): 10 | """An invalid phone number type was provided. 11 | 12 | Should be a string or an integer. 13 | """ 14 | -------------------------------------------------------------------------------- /vonage_utils/src/vonage_utils/models.py: -------------------------------------------------------------------------------- 1 | from typing import Optional 2 | 3 | from pydantic import BaseModel 4 | 5 | 6 | class Link(BaseModel): 7 | """Model for a link object. 8 | 9 | Args: 10 | href (str): The URL of the link. 11 | """ 12 | 13 | href: str 14 | 15 | 16 | class ResourceLink(BaseModel): 17 | """Model for a resource link object. 18 | 19 | Args: 20 | self (Link): The self link of the resource. 21 | """ 22 | 23 | self: Link 24 | 25 | 26 | class HalLinks(BaseModel): 27 | """Model for links following a version of the HAL standard. 28 | 29 | Args: 30 | self (Link): The self link. 31 | first (Link, Optional): The first link. 32 | last (Link, Optional): The last link. 33 | prev (Link, Optional): The previous link. 34 | next (Link, Optional): The next link. 35 | """ 36 | 37 | self: Link 38 | first: Optional[Link] = None 39 | last: Optional[Link] = None 40 | prev: Optional[Link] = None 41 | next: Optional[Link] = None 42 | -------------------------------------------------------------------------------- /vonage_utils/src/vonage_utils/types.py: -------------------------------------------------------------------------------- 1 | from typing import Annotated 2 | 3 | from pydantic import Field 4 | 5 | PhoneNumber = Annotated[str, Field(pattern=r'^[1-9]\d{6,14}$')] 6 | """A phone number, which must be between 7 and 15 digits long and not start with 0. Don't 7 | use a leading `+` or `00` in the number. For example, use `447700900000` instead of 8 | `+447700900000` or `00447700900000`. 9 | 10 | Examples: 11 | - `447700900000` 12 | - `14155552671` 13 | """ 14 | 15 | Dtmf = Annotated[str, Field(pattern=r'^[0-9#*p]+$')] 16 | """A string of DTMF (Dual-Tone Multi-Frequency) tones. The string can contain the digits 17 | 0-9, the symbols `#`, `*`, and `p`. The `p` symbol represents a pause of 500ms. 18 | 19 | Examples: 20 | - `1234#*` 21 | - `1p2p3p4` 22 | """ 23 | 24 | SipUri = Annotated[str, Field(pattern=r'^(sip|sips):\+?([\w|:.\-@;,=%&]+)')] 25 | """A SIP URI, which must start with `sip:` or `sips:` and contain a valid SIP address.""" 26 | -------------------------------------------------------------------------------- /vonage_utils/src/vonage_utils/utils.py: -------------------------------------------------------------------------------- 1 | from re import search 2 | from typing import Union 3 | 4 | from vonage_utils.errors import InvalidPhoneNumberError, InvalidPhoneNumberTypeError 5 | 6 | 7 | def format_phone_number(number: Union[str, int]) -> str: 8 | """Formats a phone number by removing all non-numeric characters and leading zeros. 9 | 10 | Args: 11 | number (str, int): The phone number to format. 12 | 13 | Returns: 14 | str: The formatted phone number. 15 | 16 | Raises: 17 | InvalidPhoneNumberError: If the phone number is invalid. 18 | InvalidPhoneNumberTypeError: If the phone number is not a string or an integer. 19 | """ 20 | if type(number) is not str: 21 | if type(number) is int: 22 | number = str(number) 23 | else: 24 | raise InvalidPhoneNumberTypeError( 25 | f'The phone number provided has an invalid type. You provided: "{type(number)}". Must be a string or an integer.' 26 | ) 27 | 28 | # Remove all non-numeric characters and leading zeros 29 | formatted_number = ''.join(filter(str.isdigit, number)).lstrip('0') 30 | 31 | if search(r'^[1-9]\d{6,14}$', formatted_number): 32 | return formatted_number 33 | raise InvalidPhoneNumberError( 34 | f'Invalid phone number provided. You provided: "{number}".\n' 35 | 'Use the E.164 format and start with the country code, e.g. "447700900000".' 36 | ) 37 | 38 | 39 | def remove_none_values(my_dataclass) -> dict: 40 | """A dict_factory that can be passed into the dataclass.asdict() method to remove None 41 | values from a dict serialized from the dataclass my_dataclass. 42 | 43 | Args: 44 | my_dataclass (dataclass): A dataclass instance 45 | 46 | Returns: 47 | A dict based on the dataclass, excluding any key-value pairs where the 48 | value is None. 49 | """ 50 | return {k: v for (k, v) in my_dataclass if v is not None} 51 | -------------------------------------------------------------------------------- /vonage_utils/tests/BUILD: -------------------------------------------------------------------------------- 1 | python_tests(dependencies=['vonage_utils/src/vonage_utils']) 2 | -------------------------------------------------------------------------------- /vonage_utils/tests/test_format_phone_number.py: -------------------------------------------------------------------------------- 1 | from pytest import raises 2 | from vonage_utils.errors import InvalidPhoneNumberError, InvalidPhoneNumberTypeError 3 | from vonage_utils.utils import format_phone_number 4 | 5 | 6 | def test_format_phone_numbers(): 7 | number = '1234567890' 8 | assert format_phone_number('1234567890') == number 9 | assert format_phone_number(1234567890) == number 10 | assert format_phone_number('+1234567890') == number 11 | assert format_phone_number('+ 1 234 567 890') == number 12 | assert format_phone_number('00 1 234 567 890') == number 13 | assert format_phone_number('00 1234567890') == number 14 | assert format_phone_number('447700900000') == '447700900000' 15 | assert format_phone_number('1234567') == '1234567' 16 | assert format_phone_number('123456789012345') == '123456789012345' 17 | 18 | 19 | def test_format_phone_number_invalid_type(): 20 | number = ['1234567890'] 21 | with raises(InvalidPhoneNumberTypeError) as e: 22 | format_phone_number(number) 23 | 24 | assert e.match('""') 25 | 26 | 27 | def test_format_phone_number_invalid_format(): 28 | number = 'not a phone number' 29 | with raises(InvalidPhoneNumberError) as e: 30 | format_phone_number(number) 31 | assert e.match('"not a phone number"') 32 | -------------------------------------------------------------------------------- /vonage_utils/tests/test_remove_none_values.py: -------------------------------------------------------------------------------- 1 | from dataclasses import asdict, dataclass 2 | 3 | from vonage_utils.utils import remove_none_values 4 | 5 | 6 | @dataclass 7 | class MyDataClass: 8 | name: str 9 | age: int 10 | address: str = None 11 | 12 | 13 | def test_remove_none_values(): 14 | data = MyDataClass(name='John', age=30) 15 | result = asdict(data, dict_factory=remove_none_values) 16 | assert result == {'name': 'John', 'age': 30} 17 | --------------------------------------------------------------------------------