diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml deleted file mode 100644 index afcba3446..000000000 --- a/.github/ISSUE_TEMPLATE/config.yml +++ /dev/null @@ -1,10 +0,0 @@ -contact_links: - - name: Twilio SendGrid Support - url: https://support.sendgrid.com - about: Get Support - - name: Stack Overflow - url: https://stackoverflow.com/questions/tagged/sendgrid-python+or+sendgrid+python - about: Ask questions on Stack Overflow - - name: Documentation - url: https://sendgrid.com/docs/for-developers/ - about: View Reference Documentation diff --git a/.github/workflows/lint-python.yml b/.github/workflows/lint-python.yml new file mode 100644 index 000000000..5dcf0328c --- /dev/null +++ b/.github/workflows/lint-python.yml @@ -0,0 +1,16 @@ +# https://beta.ruff.rs +name: ruff +on: + push: + branches: + - main + pull_request: + branches: + - main +jobs: + ruff: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v3 + - run: pip install --user ruff + - run: ruff check --ignore="E722,F40,F841" --line-length=320 --target-version=py37 . diff --git a/.github/workflows/pr-lint.yml b/.github/workflows/pr-lint.yml new file mode 100644 index 000000000..31520079c --- /dev/null +++ b/.github/workflows/pr-lint.yml @@ -0,0 +1,21 @@ +name: Lint PR +on: + pull_request_target: + types: [ opened, edited, synchronize, reopened ] + +jobs: + validate: + name: Validate title + runs-on: ubuntu-latest + steps: + - uses: amannn/action-semantic-pull-request@v5 + with: + types: | + chore + docs + fix + feat + misc + test + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml deleted file mode 100644 index 2c59e9075..000000000 --- a/.github/workflows/release.yml +++ /dev/null @@ -1,49 +0,0 @@ -name: Publish Python distributions -on: - push: - tags: - - '*' - workflow_dispatch: - -jobs: - release: - name: Release - runs-on: ubuntu-latest - steps: - - name: Checkout sendgrid-python - uses: actions/checkout@v2 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: '3.6' - - - name: Install dependencies - run: | - python -m pip install --upgrade pip - pip install build - python setup.py sdist bdist_wheel - - - name: Publish package to PyPI - uses: pypa/gh-action-pypi-publish@release/v1 - with: - user: __token__ - password: ${{ secrets.PYPI_TOKEN }} - - notify-on-failure: - name: Slack notify on failure - if: ${{ failure() }} - needs: [ release ] - runs-on: ubuntu-latest - steps: - - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: 'danger' - SLACK_ICON_EMOJI: ':github:' - SLACK_MESSAGE: ${{ format('Failed to release {1}{3} {0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id, ':') }} - SLACK_TITLE: Release Failure - SLACK_USERNAME: GitHub Actions - SLACK_MSG_AUTHOR: twilio-dx - SLACK_FOOTER: Posted automatically using GitHub Actions - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - MSG_MINIMAL: true diff --git a/.github/workflows/test-and-deploy.yml b/.github/workflows/test-and-deploy.yml new file mode 100644 index 000000000..98bbeefbd --- /dev/null +++ b/.github/workflows/test-and-deploy.yml @@ -0,0 +1,97 @@ +name: Test and Deploy +on: + push: + branches: [ '*' ] + tags: [ '*' ] + pull_request: + branches: [ main ] + schedule: + # Run automatically at 8AM PST Monday-Friday + - cron: '0 15 * * 1-5' + workflow_dispatch: + +jobs: + test: + name: Test + runs-on: ubuntu-latest + timeout-minutes: 20 + strategy: + matrix: + python-version: [ '3.9', '3.10', '3.11', '3.12' , '3.13'] + env: + DOCKER_LOGIN: ${{ secrets.DOCKER_USERNAME && secrets.DOCKER_AUTH_TOKEN }} + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v3 + + - name: Login to Docker Hub + if: env.DOCKER_LOGIN + uses: docker/login-action@v2 + with: + username: ${{ secrets.DOCKER_USERNAME }} + password: ${{ secrets.DOCKER_AUTH_TOKEN }} + + - name: Install Docker Compose + run: | + sudo apt-get update + sudo apt-get install -y docker-compose + + - name: Build & Test + run: make test + + deploy: + name: Deploy + if: success() && github.ref_type == 'tag' + needs: [ test ] + runs-on: ubuntu-latest + steps: + - name: Checkout sendgrid-python + uses: actions/checkout@v3 + + - name: Set up Python + uses: actions/setup-python@v4 + with: + python-version: '3.10' + + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install build + pip install wheel + python setup.py sdist bdist_wheel + + - name: Create GitHub Release + uses: sendgrid/dx-automator/actions/release@main + with: + footer: '**[pypi](https://pypi.org/project/sendgrid/${version})**' + env: + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + + - name: Publish package to PyPI + uses: pypa/gh-action-pypi-publish@release/v1 + with: + user: __token__ + password: ${{ secrets.PYPI_TOKEN }} + + - name: Submit metric to Datadog + uses: sendgrid/dx-automator/actions/datadog-release-metric@main + env: + DD_API_KEY: ${{ secrets.DATADOG_API_KEY }} + + notify-on-failure: + name: Slack notify on failure + if: failure() && github.event_name != 'pull_request' && (github.ref == 'refs/heads/main' || github.ref_type == 'tag') + needs: [ test, deploy ] + runs-on: ubuntu-latest + steps: + - uses: rtCamp/action-slack-notify@v2 + env: + SLACK_COLOR: failure + SLACK_ICON_EMOJI: ':github:' + SLACK_MESSAGE: ${{ format('Test *{0}*, Deploy *{1}*, {2}/{3}/actions/runs/{4}', needs.test.result, needs.deploy.result, github.server_url, github.repository, github.run_id) }} + SLACK_TITLE: Action Failure - ${{ github.repository }} + SLACK_USERNAME: GitHub Actions + SLACK_MSG_AUTHOR: twilio-dx + SLACK_FOOTER: Posted automatically using GitHub Actions + SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} + MSG_MINIMAL: true diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml deleted file mode 100644 index fe2015a6e..000000000 --- a/.github/workflows/tests.yml +++ /dev/null @@ -1,50 +0,0 @@ -name: Run Tests -on: - push: - branches: [ '*' ] - pull_request: - branches: [ main ] - schedule: - # Run automatically at 8AM PST Monday-Friday - - cron: '0 15 * * 1-5' - workflow_dispatch: - -jobs: - tests: - name: Run Tests - runs-on: ubuntu-latest - timeout-minutes: 20 - strategy: - matrix: - python-version: [ '2.7', '3.5', '3.6', '3.7', '3.8', '3.9' ] - steps: - - name: Checkout sendgrid-python - uses: actions/checkout@v2 - with: - fetch-depth: 0 - - - name: Set up Python - uses: actions/setup-python@v2 - with: - python-version: ${{ matrix.python-version }} - - - name: Build & Test - run: make test-docker test-install - - notify-on-failure: - name: Slack notify on failure - if: ${{ failure() && github.ref == 'refs/heads/main' && github.event_name != 'pull_request' }} - needs: [ tests ] - runs-on: ubuntu-latest - steps: - - uses: rtCamp/action-slack-notify@v2 - env: - SLACK_COLOR: 'danger' - SLACK_ICON_EMOJI: ':github:' - SLACK_MESSAGE: ${{ format('Failed running build on {1}{3} {0}/{1}/actions/runs/{2}', github.server_url, github.repository, github.run_id, ':') }} - SLACK_TITLE: Build Failure - SLACK_USERNAME: GitHub Actions - SLACK_MSG_AUTHOR: twilio-dx - SLACK_FOOTER: Posted automatically using GitHub Actions - SLACK_WEBHOOK: ${{ secrets.SLACK_WEBHOOK }} - MSG_MINIMAL: true diff --git a/CHANGELOG.md b/CHANGELOG.md index 35318143e..67d327649 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,123 @@ # Change Log All notable changes to this project will be documented in this file. +[2025-09-19] Version 6.12.5 +--------------------------- +**Library - Fix** +- [PR #1114](https://github.com/sendgrid/sendgrid-python/pull/1114): #1108 - Replace ecdsa with cryptography. Thanks to [@dacevedo12](https://github.com/dacevedo12)! + +**Library - Chore** +- [PR #1117](https://github.com/sendgrid/sendgrid-python/pull/1117): use make-test instead of make test-docker. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + + +[2025-06-12] Version 6.12.4 +--------------------------- +**Library - Chore** +- [PR #1109](https://github.com/sendgrid/sendgrid-python/pull/1109): bug-fix. Thanks to [@manisha1997](https://github.com/manisha1997)! + + +[2025-05-29] Version 6.12.3 +--------------------------- +**Library - Chore** +- [PR #1107](https://github.com/sendgrid/sendgrid-python/pull/1107): export EventWebhookHeader. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! +- [PR #1104](https://github.com/sendgrid/sendgrid-python/pull/1104): fix werkzeug lower versions. Thanks to [@eladkal](https://github.com/eladkal)! +- [PR #1103](https://github.com/sendgrid/sendgrid-python/pull/1103): Relax werkzeug version. Thanks to [@eladkal](https://github.com/eladkal)! + + +[2025-05-13] Version 6.12.2 +--------------------------- +**Library - Chore** +- [PR #1102](https://github.com/sendgrid/sendgrid-python/pull/1102): update ecdsa in setup.py. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + + +[2025-05-13] Version 6.12.1 +--------------------------- +**Library - Fix** +- [PR #1085](https://github.com/sendgrid/sendgrid-python/pull/1085): Vulnerability fix for starkbank-ecdsa 2.2.0 dependency. Thanks to [@ranjanprasad1996](https://github.com/ranjanprasad1996)! + + +[2025-05-05] Version 6.12.0 +--------------------------- +**Library - Chore** +- [PR #1098](https://github.com/sendgrid/sendgrid-python/pull/1098): Add werkzeug package to setup file. Thanks to [@gopidesupavan](https://github.com/gopidesupavan)! +- [PR #1099](https://github.com/sendgrid/sendgrid-python/pull/1099): install docker-compose. Thanks to [@tiwarishubham635](https://github.com/tiwarishubham635)! + +**Library - Feature** +- [PR #1087](https://github.com/sendgrid/sendgrid-python/pull/1087): add support for python3.12 and 3.13. Thanks to [@meysam81](https://github.com/meysam81)! + + +[2023-12-01] Version 6.11.0 +--------------------------- +**Library - Feature** +- [PR #1073](https://github.com/sendgrid/sendgrid-python/pull/1073): geolocation setter in sendgrid-python for GDPR compliance. Thanks to [@manisha1997](https://github.com/manisha1997)! + +**Library - Test** +- [PR #1066](https://github.com/sendgrid/sendgrid-python/pull/1066): removing codedev test dependency. Thanks to [@sethgrid](https://github.com/sethgrid)! + + +[2023-03-22] Version 6.10.0 +--------------------------- +**Library - Feature** +- [PR #1062](https://github.com/sendgrid/sendgrid-python/pull/1062): Add reply_to_list functionality. Thanks to [@thepuzzlemaster](https://github.com/thepuzzlemaster)! +- [PR #1059](https://github.com/sendgrid/sendgrid-python/pull/1059): Add Python 3.11 to the testing. Thanks to [@cclauss](https://github.com/cclauss)! + +**Library - Miscellaneous** +- [PR #1065](https://github.com/sendgrid/sendgrid-python/pull/1065): Create GitHub Action to lint Python code. Thanks to [@cclauss](https://github.com/cclauss)! +- [PR #1064](https://github.com/sendgrid/sendgrid-python/pull/1064): Upgrade GitHub Action test-and-deploy.yml. Thanks to [@cclauss](https://github.com/cclauss)! +- [PR #1063](https://github.com/sendgrid/sendgrid-python/pull/1063): Upgrade GitHub Action pr-lint.yml. Thanks to [@cclauss](https://github.com/cclauss)! + +**Library - Test** +- [PR #1058](https://github.com/sendgrid/sendgrid-python/pull/1058): Adding misc as PR type. Thanks to [@rakatyal](https://github.com/rakatyal)! + +**Library - Docs** +- [PR #1055](https://github.com/sendgrid/sendgrid-python/pull/1055): Modify README.md in alignment with SendGrid Support. Thanks to [@garethpaul](https://github.com/garethpaul)! +- [PR #1052](https://github.com/sendgrid/sendgrid-python/pull/1052): Fix link that has drifted. Thanks to [@jonathanberger](https://github.com/jonathanberger)! + + +[2022-03-09] Version 6.9.7 +-------------------------- +**Library - Chore** +- [PR #1048](https://github.com/sendgrid/sendgrid-python/pull/1048): Update mail_example.py. Thanks to [@vmesel](https://github.com/vmesel)! +- [PR #1049](https://github.com/sendgrid/sendgrid-python/pull/1049): push Datadog Release Metric upon deploy success. Thanks to [@eshanholtz](https://github.com/eshanholtz)! +- [PR #1050](https://github.com/sendgrid/sendgrid-python/pull/1050): fix flask dependency test issues. Thanks to [@eshanholtz](https://github.com/eshanholtz)! + + +[2022-02-09] Version 6.9.6 +-------------------------- +**Library - Chore** +- [PR #1044](https://github.com/sendgrid/sendgrid-python/pull/1044): drop pytest which was not being used. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #1043](https://github.com/sendgrid/sendgrid-python/pull/1043): upgrade supported language versions. Thanks to [@childish-sambino](https://github.com/childish-sambino)! +- [PR #1041](https://github.com/sendgrid/sendgrid-python/pull/1041): add gh release to workflow. Thanks to [@shwetha-manvinkurke](https://github.com/shwetha-manvinkurke)! +- [PR #1039](https://github.com/sendgrid/sendgrid-python/pull/1039): merge test and deploy workflows. Thanks to [@Hunga1](https://github.com/Hunga1)! + + +[2022-01-26] Version 6.9.5 +-------------------------- +**Library - Docs** +- [PR #1036](https://github.com/sendgrid/sendgrid-python/pull/1036): Removing unused json import. Thanks to [@vital101](https://github.com/vital101)! + + +[2022-01-12] Version 6.9.4 +-------------------------- +**Library - Chore** +- [PR #1031](https://github.com/sendgrid/sendgrid-python/pull/1031): Remove unused import from distutils. Thanks to [@tirkarthi](https://github.com/tirkarthi)! + +**Library - Docs** +- [PR #1032](https://github.com/sendgrid/sendgrid-python/pull/1032): remove leading spaces on error handling example. Thanks to [@thinkingserious](https://github.com/thinkingserious)! + + +[2021-12-15] Version 6.9.3 +-------------------------- +**Library - Test** +- [PR #1029](https://github.com/sendgrid/sendgrid-python/pull/1029): split up unit and integ tests. Thanks to [@childish-sambino](https://github.com/childish-sambino)! + + +[2021-12-01] Version 6.9.2 +-------------------------- +**Library - Chore** +- [PR #1027](https://github.com/sendgrid/sendgrid-python/pull/1027): migrate to GitHub Actions. Thanks to [@JenniferMah](https://github.com/JenniferMah)! + + [2021-11-17] Version 6.9.1 -------------------------- **Library - Chore** diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index c2769176d..af9507154 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -2,9 +2,6 @@ Hello! Thank you for choosing to help contribute to one of the Twilio SendGrid o All third party contributors acknowledge that any contributions they provide will be made under the same open source license that the open source project is provided under. -- [Feature Request](#feature-request) -- [Submit a Bug Report](#submit-a-bug-report) - - [Please use our Bug Report Template](#please-use-our-bug-report-template) - [Improvements to the Codebase](#improvements-to-the-codebase) - [Development Environment](#development-environment) - [Prerequisites](#prerequisites) @@ -19,31 +16,6 @@ All third party contributors acknowledge that any contributions they provide wil There are a few ways to contribute, which we'll enumerate below: -## Feature Request - -If you'd like to make a feature request, please read this section. - -The GitHub issue tracker is the preferred channel for library feature requests, but please respect the following restrictions: - -- Please **search for existing issues** in order to ensure we don't have duplicate bugs/feature requests. -- Please be respectful and considerate of others when commenting on issues - -## Submit a Bug Report - -Note: DO NOT include your credentials in ANY code examples, descriptions, or media you make public. - -A software bug is a demonstrable issue in the code base. In order for us to diagnose the issue and respond as quickly as possible, please add as much detail as possible into your bug report. - -Before you decide to create a new issue, please try the following: - -1. Check the GitHub issues tab if the identified issue has already been reported, if so, please add a +1 to the existing post. -2. Update to the latest version of this code and check if the issue has already been fixed -3. Copy and fill in the Bug Report Template we have provided below - -### Please use our Bug Report Template - -In order to make the process easier, we've included a [sample bug report template](ISSUE_TEMPLATE.md). - ## Improvements to the Codebase We welcome direct contributions to the sendgrid-python code base. Thank you! @@ -54,7 +26,7 @@ We welcome direct contributions to the sendgrid-python code base. Thank you! - Python version 2.7, 3.5, 3.6, 3.7, or 3.8 - [python_http_client](https://github.com/sendgrid/python-http-client) -- [ecdsa_python](https://github.com/starkbank/ecdsa-python) +- [cryptography](https://github.com/pyca/cryptography) - [pyenv](https://github.com/yyuu/pyenv) - [tox](https://pypi.python.org/pypi/tox) diff --git a/FIRST_TIMERS.md b/FIRST_TIMERS.md index 528580c34..a1d563a75 100644 --- a/FIRST_TIMERS.md +++ b/FIRST_TIMERS.md @@ -51,29 +51,3 @@ git push origin ## Important notice Before creating a pull request, make sure that you respect the repository's constraints regarding contributions. You can find them in the [CONTRIBUTING.md](CONTRIBUTING.md) file. - -## Repositories with Open, Easy, Help Wanted, Issue Filters - -* [Python SDK](https://github.com/sendgrid/sendgrid-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP SDK](https://github.com/sendgrid/sendgrid-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# SDK](https://github.com/sendgrid/sendgrid-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby SDK](https://github.com/sendgrid/sendgrid-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Node.js SDK](https://github.com/sendgrid/sendgrid-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java SDK](https://github.com/sendgrid/sendgrid-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go SDK](https://github.com/sendgrid/sendgrid-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Python SMTPAPI Client](https://github.com/sendgrid/smtpapi-python/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP SMTPAPI Client](https://github.com/sendgrid/smtpapi-php/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# SMTPAPI Client](https://github.com/sendgrid/smtpapi-csharp/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby SMTPAPI Client](https://github.com/sendgrid/smtpapi-ruby/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Node.js SMTPAPI Client](https://github.com/sendgrid/smtpapi-nodejs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java SMTPAPI Client](https://github.com/sendgrid/smtpapi-java/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go SMTPAPI Client](https://github.com/sendgrid/smtpapi-go/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Python HTTP Client](https://github.com/sendgrid/python-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [PHP HTTP Client](https://github.com/sendgrid/php-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [C# HTTP Client](https://github.com/sendgrid/csharp-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Java HTTP Client](https://github.com/sendgrid/java-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Ruby HTTP Client](https://github.com/sendgrid/ruby-http-client/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Go HTTP Client](https://github.com/sendgrid/rest/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Open API Definition](https://github.com/sendgrid/sendgrid-oai/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [DX Automator](https://github.com/sendgrid/dx-automator/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) -* [Documentation](https://github.com/sendgrid/docs/issues?utf8=%E2%9C%93&q=is%3Aopen+label%3A%22difficulty%3A+easy%22+label%3A%22status%3A+help+wanted%22) diff --git a/ISSUE_TEMPLATE.md b/ISSUE_TEMPLATE.md deleted file mode 100644 index fb2e15cef..000000000 --- a/ISSUE_TEMPLATE.md +++ /dev/null @@ -1,30 +0,0 @@ - - -### Issue Summary -A summary of the issue and the environment in which it occurs. If suitable, include the steps required to reproduce the bug. Please feel free to include screenshots, screencasts, or code examples. - -### Steps to Reproduce -1. This is the first step -2. This is the second step -3. Further steps, etc. - -### Code Snippet -```python -# paste code here -``` - -### Exception/Log -``` -# paste exception/log here -``` - -### Technical details: -* sendgrid-python version: -* python version: - diff --git a/LICENSE b/LICENSE index e5439a92d..126ceb1a3 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (C) 2021, Twilio SendGrid, Inc. +Copyright (C) 2025, Twilio SendGrid, Inc. Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in diff --git a/Makefile b/Makefile index bb22db4df..96161106d 100644 --- a/Makefile +++ b/Makefile @@ -6,16 +6,15 @@ venv: clean virtualenv --python=python venv install: venv + . venv/bin/activate; pip install -r test/requirements.txt . venv/bin/activate; python setup.py install . venv/bin/activate; pip install -r requirements.txt -test-install: install - . venv/bin/activate; pip install -r test/requirements.txt - -test: test-install +test: install + . venv/bin/activate; coverage run -m unittest discover -s test/unit test-integ: test - . venv/bin/activate; coverage run -m unittest discover + . venv/bin/activate; coverage run -m unittest discover -s test/integ version ?= latest test-docker: diff --git a/PULL_REQUEST_TEMPLATE.md b/PULL_REQUEST_TEMPLATE.md index 7c2789ae4..f9448a3b1 100644 --- a/PULL_REQUEST_TEMPLATE.md +++ b/PULL_REQUEST_TEMPLATE.md @@ -3,7 +3,7 @@ We appreciate the effort for this pull request but before that please make sure Please format the PR title appropriately based on the type of change: [!]: -Where is one of: docs, chore, feat, fix, test. +Where is one of: docs, chore, feat, fix, test, misc. Add a '!' after the type for breaking changes (e.g. feat!: new breaking feature). **All third-party contributors acknowledge that any contributions they provide will be made under the same open-source license that the open-source project is provided under.** @@ -28,4 +28,4 @@ A short description of what this PR does. - [ ] I have added the necessary documentation about the functionality in the appropriate .md file - [ ] I have added inline documentation to the code I modified -If you have questions, please file a [support ticket](https://support.sendgrid.com), or create a GitHub Issue in this repository. +If you have questions, please file a [support ticket](https://support.sendgrid.com). \ No newline at end of file diff --git a/README.md b/README.md index e2208293d..b1b36686f 100644 --- a/README.md +++ b/README.md @@ -1,23 +1,21 @@ ![SendGrid Logo](twilio_sendgrid_logo.png) -[![Tests](https://github.com/sendgrid/sendgrid-python/actions/workflows/tests.yml/badge.svg)](https://github.com/sendgrid/sendgrid-python/actions/workflows/tests.yml) +[![BuildStatus](https://github.com/sendgrid/sendgrid-python/actions/workflows/test-and-deploy.yml/badge.svg)](https://github.com/sendgrid/sendgrid-python/actions/workflows/test-and-deploy.yml) [![Docker Badge](https://img.shields.io/docker/automated/sendgrid/sendgrid-python.svg)](https://hub.docker.com/r/sendgrid/sendgrid-python/) [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](LICENSE) [![Twitter Follow](https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow)](https://twitter.com/sendgrid) [![GitHub contributors](https://img.shields.io/github/contributors/sendgrid/sendgrid-python.svg)](https://github.com/sendgrid/sendgrid-python/graphs/contributors) [![Open Source Helpers](https://www.codetriage.com/sendgrid/sendgrid-python/badges/users.svg)](https://www.codetriage.com/sendgrid/sendgrid-python) -**The default branch name for this repository has been changed to `main` as of 07/27/2020.** - **This library allows you to quickly and easily use the SendGrid Web API v3 via Python.** Version 3.X.X+ of this library provides full support for all SendGrid [Web API v3](https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html) endpoints, including the new [v3 /mail/send](https://sendgrid.com/blog/introducing-v3mailsend-sendgrids-new-mail-endpoint). This library represents the beginning of a new path for SendGrid. We want this library to be community driven and SendGrid led. We need your help to realize this goal. To help make sure we are building the right things in the right order, we ask that you create [issues](https://github.com/sendgrid/sendgrid-python/issues) and [pull requests](CONTRIBUTING.md) or simply upvote or comment on existing issues or pull requests. -Please browse the rest of this README for further detail. +**If you need help using SendGrid, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com).** -We appreciate your continued support, thank you! +Please browse the rest of this README for further detail. # Table of Contents @@ -30,6 +28,7 @@ We appreciate your continued support, thank you! * [How to Contribute](#contribute) * [Troubleshooting](#troubleshooting) * [About](#about) +* [Support](#support) * [License](#license) @@ -71,7 +70,7 @@ pip install sendgrid ## Dependencies - [Python-HTTP-Client](https://github.com/sendgrid/python-http-client) -- [ECDSA-Python](https://github.com/starkbank/ecdsa-python) +- [Cryptography](https://github.com/pyca/cryptography) @@ -100,7 +99,7 @@ print(response.body) print(response.headers) ``` -The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](examples/helpers/mail_example.py#L16) is an example of how to add it. +The `Mail` constructor creates a [personalization object](https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html) for you. [Here](examples/helpers/mail_example.py#L28) is an example of how to add it. ### Without Mail Helper Class @@ -187,9 +186,7 @@ Please see [our helper](sendgrid/helpers/inbound) for utilizing our Inbound Pars # Announcements -Please see our announcement regarding [breaking changes](https://github.com/sendgrid/sendgrid-python/issues/217). Your support is appreciated! - -All updates to this library are documented in our [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases). You may also subscribe to email [release notifications](https://dx.sendgrid.com/newsletter/java) for releases and breaking changes. +All updates to this library are documented in our [CHANGELOG](CHANGELOG.md) and [releases](https://github.com/sendgrid/sendgrid-python/releases). # How to Contribute @@ -213,9 +210,10 @@ Please see our [troubleshooting guide](TROUBLESHOOTING.md) for common library is sendgrid-python is maintained and funded by Twilio SendGrid, Inc. The names and logos for sendgrid-python are trademarks of Twilio SendGrid, Inc. -If you need help installing or using the library, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). + +# Support -If you've instead found a bug in the library or would like new features added, go ahead and open issues or pull requests against this repo! +If you need support, please check the [Twilio SendGrid Support Help Center](https://support.sendgrid.com). # License diff --git a/README.rst b/README.rst index 59f693f0f..526c4ca40 100644 --- a/README.rst +++ b/README.rst @@ -3,15 +3,12 @@ -|Tests Badge| |codecov| |Python Versions| |PyPI Version| |Docker Badge| |Email Notifications Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| +|Tests Badge| |Python Versions| |PyPI Version| |Docker Badge| |MIT licensed| |Twitter Follow| |GitHub contributors| |Open Source Helpers| **This library allows you to quickly and easily use the Twilio SendGrid Web API v3 via Python.** **NEW:** -**The default branch name for this repository has been changed to `main` as of 07/27/2020.** - -- Subscribe to email `notifications`_ for releases and breaking changes. - Version 6.X release is a BREAKING CHANGE from version 5.X, please see the `release notes`_ for details. - Send SMS messages with `Twilio`_. @@ -93,7 +90,7 @@ Dependencies ------------ - `Python-HTTP-Client`_ -- `ECDSA-Python`_ +- `Cryptography`_ Quick Start =========== @@ -223,7 +220,6 @@ Announcements ============= All updates to this library are documented in our `CHANGELOG`_ and `releases`_. -You may also subscribe to email `release notifications`_ for releases and breaking changes. How to Contribute ================= @@ -253,7 +249,6 @@ License `The MIT License (MIT)`_ -.. _notifications: https://dx.sendgrid.com/newsletter/python .. _Twilio: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/sms.md .. _release notes: https://github.com/sendgrid/sendgrid-python/releases/tag/v6.0.0 .. _Web API v3: https://sendgrid.com/docs/API_Reference/Web_API_v3/index.html @@ -264,7 +259,7 @@ License .. _Twilio account: https://www.twilio.com/try-twilio?source=sendgrid-python .. _SENDGRID_API_KEY: https://app.sendgrid.com/settings/api_keys .. _Python-HTTP-Client: https://github.com/sendgrid/python-http-client -.. _ECDSA-Python: https://github.com/starkbank/ecdsa-python +.. _Cryptography: https://github.com/pyca/cryptography .. _/mail/send Helper: https://github.com/sendgrid/sendgrid-python/tree/HEAD/sendgrid/helpers/mail .. _personalization object: https://sendgrid.com/docs/Classroom/Send/v3_Mail_Send/personalizations.html .. _Fluent Interface: https://sendgrid.com/blog/using-python-to-implement-a-fluent-interface-to-any-rest-api/ @@ -279,7 +274,6 @@ License .. _breaking changes: https://github.com/sendgrid/sendgrid-python/issues/217 .. _CHANGELOG: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CHANGELOG.md .. _releases: https://github.com/sendgrid/sendgrid-python/releases -.. _release notifications: https://dx.sendgrid.com/newsletter/python .. _CONTRIBUTING: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md .. _Feature Request: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#feature-request .. _Bug Reports: https://github.com/sendgrid/sendgrid-python/blob/HEAD/CONTRIBUTING.md#submit-a-bug-report @@ -294,12 +288,8 @@ License :target: https://pypi.org/project/sendgrid/ .. |PyPI Version| image:: https://img.shields.io/pypi/v/sendgrid.svg :target: https://pypi.org/project/sendgrid/ -.. |codecov| image:: https://img.shields.io/codecov/c/github/sendgrid/sendgrid-python/main.svg?style=flat-square&label=Codecov+Coverage - :target: https://codecov.io/gh/sendgrid/sendgrid-python .. |Docker Badge| image:: https://img.shields.io/docker/automated/sendgrid/sendgrid-python.svg :target: https://hub.docker.com/r/sendgrid/sendgrid-python/ -.. |Email Notifications Badge| image:: https://dx.sendgrid.com/badge/python - :target: https://dx.sendgrid.com/newsletter/python .. |MIT licensed| image:: https://img.shields.io/badge/license-MIT-blue.svg :target: ./LICENSE .. |Twitter Follow| image:: https://img.shields.io/twitter/follow/sendgrid.svg?style=social&label=Follow diff --git a/examples/dataresidency/set_region.py b/examples/dataresidency/set_region.py new file mode 100644 index 000000000..9aae2611f --- /dev/null +++ b/examples/dataresidency/set_region.py @@ -0,0 +1,37 @@ +import sendgrid +import os + +from sendgrid import Email, To, Content, Mail + +# Example 1 +# setting region to be "global" + +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY')) +from_email = Email("example@abc.com") +to_email = To("example@abc.com") +subject = "Sending with SendGrid is Fun" +content = Content("text/plain", "and easy to do anywhere, even with Python") +mail = Mail(from_email, to_email, subject, content) +sg.set_sendgrid_data_residency("global") +print(sg.client.host) +response = sg.client.mail.send.post(request_body=mail.get()) +print(response) +print(response.status_code) +print(response.body) +print(response.headers) + +# Example 2 +# setting region to "eu" +sg = sendgrid.SendGridAPIClient(api_key=os.environ.get('SENDGRID_API_KEY_EU')) +sg.set_sendgrid_data_residency("eu") +from_email = Email("example@abc.com") +to_email = To("example@abc.com") +subject = "Sending with SendGrid is Fun" +content = Content("text/plain", "and easy to do anywhere, even with Python") +print(sg.client.host) +mail = Mail(from_email, to_email, subject, content) +response = sg.client.mail.send.post(request_body=mail.get()) +print(response) +print(response.status_code) +print(response.body) +print(response.headers) \ No newline at end of file diff --git a/examples/helpers/mail_example.py b/examples/helpers/mail_example.py index 384181501..f6905787b 100644 --- a/examples/helpers/mail_example.py +++ b/examples/helpers/mail_example.py @@ -1,3 +1,6 @@ +import os +import json + from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import * @@ -8,11 +11,7 @@ def build_hello_email(): ## Send a Single Email to a Single Recipient - import os - import json - from sendgrid import SendGridAPIClient - from sendgrid.helpers.mail import Mail, From, To, Subject, PlainTextContent, HtmlContent, SendGridException - + message = Mail(from_email=From('from@example.com', 'Example From Name'), to_emails=To('to@example.com', 'Example To Name'), subject=Subject('Sending with SendGrid is Fun'), @@ -84,11 +83,8 @@ def get_mock_personalization_dict(): return mock_pers def build_multiple_emails_personalized(): - import json - from sendgrid.helpers.mail import Mail, From, To, Cc, Bcc, Subject, PlainTextContent, \ - HtmlContent, SendGridException, Personalization - # Note that the domain for all From email addresses must match + message = Mail(from_email=From('from@example.com', 'Example From Name'), subject=Subject('Sending with SendGrid is Fun'), plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), @@ -117,6 +113,7 @@ def build_multiple_emails_personalized(): def build_attachment1(): """Build attachment mock. Make sure your content is base64 encoded before passing into attachment.content. Another example: https://github.com/sendgrid/sendgrid-python/blob/HEAD/use_cases/attachment.md""" + attachment = Attachment() attachment.file_content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") diff --git a/requirements.txt b/requirements.txt index f4b4ba105..ed2594a90 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,7 +1,6 @@ -Flask==1.1.2 +Flask==3.1.0 PyYAML>=4.2b1 python-http-client>=3.2.1 -six==1.11.0 -pytest==3.8.2 -starkbank-ecdsa>=2.0.1 +six==1.17.0 +cryptography>=45.0.6 more-itertools==5.0.0 diff --git a/sendgrid/base_interface.py b/sendgrid/base_interface.py index 92b38247e..f94f09479 100644 --- a/sendgrid/base_interface.py +++ b/sendgrid/base_interface.py @@ -1,5 +1,6 @@ import python_http_client +region_host_dict = {'eu':'https://api.eu.sendgrid.com','global':'https://api.sendgrid.com'} class BaseInterface(object): def __init__(self, auth, host, impersonate_subuser): @@ -22,10 +23,10 @@ def __init__(self, auth, host, impersonate_subuser): """ from . import __version__ self.auth = auth - self.host = host self.impersonate_subuser = impersonate_subuser self.version = __version__ self.useragent = 'sendgrid/{};python'.format(self.version) + self.host = host self.client = python_http_client.Client( host=self.host, @@ -60,3 +61,23 @@ def send(self, message): message = message.get() return self.client.mail.send.post(request_body=message) + + def set_sendgrid_data_residency(self, region): + """ + Client libraries contain setters for specifying region/edge. + This supports global and eu regions only. This set will likely expand in the future. + Global is the default residency (or region) + Global region means the message will be sent through https://api.sendgrid.com + EU region means the message will be sent through https://api.eu.sendgrid.com + :param region: string + :return: + """ + if region in region_host_dict.keys(): + self.host = region_host_dict[region] + if self._default_headers is not None: + self.client = python_http_client.Client( + host=self.host, + request_headers=self._default_headers, + version=3) + else: + raise ValueError("region can only be \"eu\" or \"global\"") diff --git a/sendgrid/helpers/eventwebhook/__init__.py b/sendgrid/helpers/eventwebhook/__init__.py index c1ec7d1c8..9d618bf3a 100644 --- a/sendgrid/helpers/eventwebhook/__init__.py +++ b/sendgrid/helpers/eventwebhook/__init__.py @@ -1,7 +1,8 @@ -from ellipticcurve.ecdsa import Ecdsa -from ellipticcurve.publicKey import PublicKey -from ellipticcurve.signature import Signature - +from cryptography.exceptions import InvalidSignature +from cryptography.hazmat.primitives import hashes +from cryptography.hazmat.primitives.asymmetric import ec +from cryptography.hazmat.primitives.serialization import load_pem_public_key +import base64 from .eventwebhook_header import EventWebhookHeader class EventWebhook: @@ -20,14 +21,15 @@ def __init__(self, public_key=None): def convert_public_key_to_ecdsa(self, public_key): """ - Convert the public key string to a ECPublicKey. + Convert the public key string to an EllipticCurvePublicKey object. :param public_key: verification key under Mail Settings :type public_key string - :return: public key using the ECDSA algorithm - :rtype PublicKey + :return: An EllipticCurvePublicKey object using the ECDSA algorithm + :rtype cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey """ - return PublicKey.fromPem('\n-----BEGIN PUBLIC KEY-----\n'+public_key+'\n-----END PUBLIC KEY-----\n') + pem_key = "-----BEGIN PUBLIC KEY-----\n" + public_key + "\n-----END PUBLIC KEY-----" + return load_pem_public_key(pem_key.encode("utf-8")) def verify_signature(self, payload, signature, timestamp, public_key=None): """ @@ -40,11 +42,15 @@ def verify_signature(self, payload, signature, timestamp, public_key=None): :param timestamp: value obtained from the 'X-Twilio-Email-Event-Webhook-Timestamp' header :type timestamp: string :param public_key: elliptic curve public key - :type public_key: PublicKey + :type public_key: cryptography.hazmat.primitives.asymmetric.ec.EllipticCurvePublicKey :return: true or false if signature is valid """ - timestamped_payload = timestamp + payload - decoded_signature = Signature.fromBase64(signature) + timestamped_payload = (timestamp + payload).encode('utf-8') + decoded_signature = base64.b64decode(signature) key = public_key or self.public_key - return Ecdsa.verify(timestamped_payload, decoded_signature, key) + try: + key.verify(decoded_signature, timestamped_payload, ec.ECDSA(hashes.SHA256())) + return True + except InvalidSignature: + return False diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index ba21f7891..e475fe764 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -60,6 +60,7 @@ def __init__( self._ip_pool_name = None self._mail_settings = None self._reply_to = None + self._reply_to_list = None self._send_at = None self._subject = None self._template_id = None @@ -566,7 +567,7 @@ def add_custom_arg(self, custom_arg): :param value: A CustomArg object or a dict of custom arg key/values :type value: CustomArg, dict """ - if custom_arg.personalization is not None: + if not isinstance(custom_arg, dict) and custom_arg.personalization is not None: try: personalization = \ self._personalizations[custom_arg.personalization] @@ -695,6 +696,32 @@ def reply_to(self, value): value = ReplyTo(value[0], value[1]) self._reply_to = value + @property + def reply_to_list(self): + """A list of ReplyTo email addresses + + :rtype: list(ReplyTo), tuple + """ + return self._reply_to_list + + @reply_to_list.setter + def reply_to_list(self, value): + """A list of ReplyTo email addresses + + :param value: A list of ReplyTo email addresses + :type value: list(ReplyTo), tuple + """ + if isinstance(value, list): + for reply in value: + if isinstance(reply, ReplyTo): + if not isinstance(reply.email, str): + raise ValueError('You must provide an email for each entry in a reply_to_list') + else: + raise ValueError( + 'Please use a list of ReplyTos for a reply_to_list.' + ) + self._reply_to_list = value + @property def contents(self): """The contents of the email @@ -981,6 +1008,7 @@ def get(self): 'mail_settings': self._get_or_none(self.mail_settings), 'tracking_settings': self._get_or_none(self.tracking_settings), 'reply_to': self._get_or_none(self.reply_to), + 'reply_to_list': [r.get() for r in self.reply_to_list or []], } return {key: value for key, value in mail.items() diff --git a/sendgrid/version.py b/sendgrid/version.py index ae7f14541..c1d623f90 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1 +1 @@ -__version__ = '6.9.1' +__version__ = '6.12.5' diff --git a/setup.py b/setup.py index 365ebb50b..904cd654a 100644 --- a/setup.py +++ b/setup.py @@ -1,6 +1,5 @@ import io import os -from distutils.file_util import copy_file from setuptools import setup, find_packages @@ -11,7 +10,14 @@ def getRequires(): deps = [ 'python_http_client>=3.2.1', - 'starkbank-ecdsa>=2.0.1' + 'cryptography>=45.0.6', + "werkzeug>=0.11.15,<1.0.0 ; python_version < '3.0'", + "werkzeug>=0.15.0,<2.0.0 ; python_version >= '3.0' and python_version < '3.7'", + "werkzeug>=0.15.0,<2.3.0 ; python_version >= '3.0' and python_version < '3.8'", # version 2.3.0 dropped support for Python 3.7 + "werkzeug>=0.16.0,<3.1.0 ; python_version >= '3.0' and python_version < '3.9'", # version 3.1.0 dropped support for Python 3.8 + "werkzeug>=1.0.0 ; python_version >= '3.9'", + "werkzeug>=2.2.0 ; python_version >= '3.11'", + "werkzeug>=2.3.5 ; python_version >= '3.12'" ] return deps @@ -35,9 +41,14 @@ def getRequires(): classifiers=[ 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', - 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6', 'Programming Language :: Python :: 3.7', + 'Programming Language :: Python :: 3.8', + 'Programming Language :: Python :: 3.9', + 'Programming Language :: Python :: 3.10', + 'Programming Language :: Python :: 3.11', + 'Programming Language :: Python :: 3.12', + 'Programming Language :: Python :: 3.13', ] ) diff --git a/test/integ/__init__.py b/test/integ/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_sendgrid.py b/test/integ/test_sendgrid.py similarity index 100% rename from test/test_sendgrid.py rename to test/integ/test_sendgrid.py diff --git a/test/requirements.txt b/test/requirements.txt index 6cb2e96d2..40552deba 100644 --- a/test/requirements.txt +++ b/test/requirements.txt @@ -1,6 +1,8 @@ pyyaml -flask +Flask==1.1.4 six coverage -codecov mock +itsdangerous==1.1.0 +markupsafe==1.1.1 +setuptools diff --git a/test/unit/__init__.py b/test/unit/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/test/test_app.py b/test/unit/test_app.py similarity index 100% rename from test/test_app.py rename to test/unit/test_app.py diff --git a/test/test_config.py b/test/unit/test_config.py similarity index 100% rename from test/test_config.py rename to test/unit/test_config.py diff --git a/test/test_email.py b/test/unit/test_email.py similarity index 100% rename from test/test_email.py rename to test/unit/test_email.py diff --git a/test/test_eventwebhook.py b/test/unit/test_eventwebhook.py similarity index 100% rename from test/test_eventwebhook.py rename to test/unit/test_eventwebhook.py diff --git a/test/test_inbound_send.py b/test/unit/test_inbound_send.py similarity index 100% rename from test/test_inbound_send.py rename to test/unit/test_inbound_send.py diff --git a/test/test_mail_helpers.py b/test/unit/test_mail_helpers.py similarity index 94% rename from test/test_mail_helpers.py rename to test/unit/test_mail_helpers.py index a7d08d890..c00307d46 100644 --- a/test/test_mail_helpers.py +++ b/test/unit/test_mail_helpers.py @@ -235,6 +235,58 @@ def test_send_a_single_email_to_multiple_recipients(self): }''') ) + def test_send_a_single_email_with_multiple_reply_to_addresses(self): + from sendgrid.helpers.mail import (Mail, From, ReplyTo, To, Subject, + PlainTextContent, HtmlContent) + self.maxDiff = None + message = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=To('test+to0@example.com', 'Example To Name'), + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent('and easy to do anywhere, even with Python'), + html_content=HtmlContent('and easy to do anywhere, even with Python')) + + message.reply_to_list = [ReplyTo(email = 'test+reply_to_1@example.com'), ReplyTo(email = 'test+reply_to_2@example.com')] + + self.assertEqual( + message.get(), + json.loads(r'''{ + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + }, + { + "type": "text/html", + "value": "and easy to do anywhere, even with Python" + } + ], + "from": { + "email": "test+from@example.com", + "name": "Example From Name" + }, + "personalizations": [ + { + "to": [ + { + "email": "test+to0@example.com", + "name": "Example To Name" + } + ] + } + ], + "reply_to_list": [ + { + "email": "test+reply_to_1@example.com" + }, + { + "email": "test+reply_to_2@example.com" + } + ], + "subject": "Sending with SendGrid is Fun" + }''') + ) + def test_multiple_emails_to_multiple_recipients(self): from sendgrid.helpers.mail import (Mail, From, To, Subject, PlainTextContent, HtmlContent, @@ -568,6 +620,44 @@ def test_value_error_is_raised_on_to_emails_set_to_list_of_lists(self): 'and easy to do anywhere, even with Python'), html_content=HtmlContent( 'and easy to do anywhere, even with Python')) + + def test_value_error_is_raised_on_to_emails_set_to_reply_to_list_of_strs(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + mail = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + with self.assertRaises(ValueError): + mail.reply_to_list = ['test+reply_to0@example.com', 'test+reply_to1@example.com'] + + def test_value_error_is_raised_on_to_emails_set_to_reply_to_list_of_tuples(self): + from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) + self.maxDiff = None + to_emails = [ + ('test+to0@example.com', 'Example To Name 0'), + ('test+to1@example.com', 'Example To Name 1') + ] + + mail = Mail( + from_email=From('test+from@example.com', 'Example From Name'), + to_emails=to_emails, + subject=Subject('Sending with SendGrid is Fun'), + plain_text_content=PlainTextContent( + 'and easy to do anywhere, even with Python'), + html_content=HtmlContent( + 'and easy to do anywhere, even with Python')) + with self.assertRaises(ValueError): + mail.reply_to_list = [('test+reply_to@example.com', 'Test Name')] def test_error_is_not_raised_on_to_emails_set_to_list_of_tuples(self): from sendgrid.helpers.mail import (PlainTextContent, HtmlContent) diff --git a/test/test_parse.py b/test/unit/test_parse.py similarity index 100% rename from test/test_parse.py rename to test/unit/test_parse.py diff --git a/test/test_project.py b/test/unit/test_project.py similarity index 91% rename from test/test_project.py rename to test/unit/test_project.py index c78293dff..40282bdb7 100644 --- a/test/test_project.py +++ b/test/unit/test_project.py @@ -23,10 +23,6 @@ def test_code_of_conduct(self): def test_contributing(self): self.assertTrue(os.path.isfile('./CONTRIBUTING.md')) - # ./ISSUE_TEMPLATE.md - def test_issue_template(self): - self.assertTrue(os.path.isfile('./ISSUE_TEMPLATE.md')) - # ./LICENSE def test_license(self): self.assertTrue(os.path.isfile('./LICENSE')) diff --git a/test/unit/test_sendgrid.py b/test/unit/test_sendgrid.py new file mode 100644 index 000000000..328d978ab --- /dev/null +++ b/test/unit/test_sendgrid.py @@ -0,0 +1,27 @@ +import unittest +import sendgrid + +class UnitTests(unittest.TestCase): + def test_host_with_no_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + self.assertEqual("https://api.sendgrid.com",sg.client.host) + + def test_host_with_eu_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + sg.set_sendgrid_data_residency("eu") + self.assertEqual("https://api.eu.sendgrid.com",sg.client.host) + + def test_host_with_global_region(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + sg.set_sendgrid_data_residency("global") + self.assertEqual("https://api.sendgrid.com",sg.client.host) + + def test_with_region_is_none(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + with self.assertRaises(ValueError): + sg.set_sendgrid_data_residency(None) + + def test_with_region_is_invalid(self): + sg = sendgrid.SendGridAPIClient(api_key='MY_API_KEY') + with self.assertRaises(ValueError): + sg.set_sendgrid_data_residency("abc") \ No newline at end of file diff --git a/test/test_spam_check.py b/test/unit/test_spam_check.py similarity index 100% rename from test/test_spam_check.py rename to test/unit/test_spam_check.py diff --git a/test/test_stats.py b/test/unit/test_stats.py similarity index 100% rename from test/test_stats.py rename to test/unit/test_stats.py diff --git a/test/test_twilio_email.py b/test/unit/test_twilio_email.py similarity index 100% rename from test/test_twilio_email.py rename to test/unit/test_twilio_email.py diff --git a/test/test_unassigned.py b/test/unit/test_unassigned.py similarity index 96% rename from test/test_unassigned.py rename to test/unit/test_unassigned.py index 6054447d8..08ab943bb 100644 --- a/test/test_unassigned.py +++ b/test/unit/test_unassigned.py @@ -1,9 +1,7 @@ import json -import pytest from sendgrid.helpers.endpoints.ip.unassigned import unassigned - ret_json = '''[ { "ip": "167.89.21.3", "pools": [ @@ -69,8 +67,7 @@ def make_data(): def test_unassigned_ip_json(): - - data = make_data() + data = make_data() as_json = True calculated = unassigned(get_all_ip(), as_json=as_json) @@ -81,8 +78,7 @@ def test_unassigned_ip_json(): def test_unassigned_ip_obj(): - - data = make_data() + data = make_data() as_json = False calculated = unassigned(get_all_ip(), as_json=as_json) diff --git a/tox.ini b/tox.ini index 926a3c9dd..8f4f2db9a 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py27, py34, py35, py36, py37 +envlist = py27, py34, py35, py36, py37, py38, py39, py310, py311, py312, py313 [testenv] commands = coverage erase @@ -39,3 +39,33 @@ basepython = python3.6 commands = {[testenv]commands} deps = {[testenv]deps} basepython = python3.7 + +[testenv:py38] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.8 + +[testenv:py39] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.9 + +[testenv:py310] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.10 + +[testenv:py311] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.11 + +[testenv:py312] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.12 + +[testenv:py313] +commands = {[testenv]commands} +deps = {[testenv]deps} +basepython = python3.13 diff --git a/use_cases/error_handling.md b/use_cases/error_handling.md index 187703b60..d0bdf0945 100644 --- a/use_cases/error_handling.md +++ b/use_cases/error_handling.md @@ -6,24 +6,24 @@ Please see [here](https://github.com/sendgrid/python-http-client/blob/HEAD/pytho There are also email specific exceptions located [here](../sendgrid/helpers/mail/exceptions.py) ```python - import os - from sendgrid import SendGridAPIClient - from sendgrid.helpers.mail import (From, To, Subject, PlainTextContent, HtmlContent, Mail) - from python_http_client import exceptions +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import (From, To, Subject, PlainTextContent, HtmlContent, Mail) +from python_http_client import exceptions - sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) - from_email = From("help@twilio.com") - to_email = To("ethomas@twilio.com") - subject = Subject("Sending with Twilio SendGrid is Fun") - plain_text_content = PlainTextContent("and easy to do anywhere, even with Python") - html_content = HtmlContent("and easy to do anywhere, even with Python") - message = Mail(from_email, to_email, subject, plain_text_content, html_content) - try: - response = sendgrid_client.send(message) - print(response.status_code) - print(response.body) - print(response.headers) - except exceptions.BadRequestsError as e: - print(e.body) - exit() +sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) +from_email = From("help@twilio.com") +to_email = To("ethomas@twilio.com") +subject = Subject("Sending with Twilio SendGrid is Fun") +plain_text_content = PlainTextContent("and easy to do anywhere, even with Python") +html_content = HtmlContent("and easy to do anywhere, even with Python") +message = Mail(from_email, to_email, subject, plain_text_content, html_content) +try: + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except exceptions.BadRequestsError as e: + print(e.body) + exit() ``` diff --git a/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md b/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md new file mode 100644 index 000000000..55d6adf18 --- /dev/null +++ b/use_cases/send_a_single_email_with_multiple_reply_to_addresses.md @@ -0,0 +1,29 @@ +```python +import os +from sendgrid import SendGridAPIClient +from sendgrid.helpers.mail import Mail + +message = Mail( + from_email='from_email@example.com', + to_emails='to@example.com', + subject='Sending with Twilio SendGrid is Fun', + html_content='and easy to do anywhere, even with Python') +message.reply_to_list = [ + ReplyTo( + email='reply-to-1@example.com', + name="Reply To Name 1", + ), + ReplyTo( + email='reply-to-2@example.com', + name="Reply To Name 2", + ) +] +try: + sendgrid_client = SendGridAPIClient(os.environ.get('SENDGRID_API_KEY')) + response = sendgrid_client.send(message) + print(response.status_code) + print(response.body) + print(response.headers) +except Exception as e: + print(e) +``` diff --git a/use_cases/transactional_templates.md b/use_cases/transactional_templates.md index 9ee4848c3..460fd65ee 100644 --- a/use_cases/transactional_templates.md +++ b/use_cases/transactional_templates.md @@ -28,7 +28,6 @@ I hope you are having a great day in {{ city }} :) ```python import os -import json from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail @@ -86,7 +85,6 @@ I hope you are having a great day in {{{ city }}} :) ```python import os -import json from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import Mail