diff --git a/.github/PULL_REQUEST_TEMPLATE b/.github/PULL_REQUEST_TEMPLATE index 7ad590b42..b3b7a4446 100644 --- a/.github/PULL_REQUEST_TEMPLATE +++ b/.github/PULL_REQUEST_TEMPLATE @@ -21,4 +21,4 @@ Closes #2 - - -If you have questions, please send an email to [Sendgrid](mailto:dx@sendgrid.com), or file a Github Issue in this repository. +If you have questions, please send an email to [SendGrid](mailto:dx@sendgrid.com), or file a GitHub Issue in this repository. diff --git a/.travis.yml b/.travis.yml index 5ad33bccf..1b55a568b 100644 --- a/.travis.yml +++ b/.travis.yml @@ -19,6 +19,7 @@ install: - pip install pypandoc - pip install coverage - pip install codecov +- pip install pycodestyle # - sudo apt-get install -y pandoc addons: apt_packages: @@ -30,6 +31,10 @@ before_script: - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: +# errors W605 and W504 added to avoid inconsistent errors happening +# error W605 happening on validators.py:28:40 on a valid Regex +# error W504 happening on validators.py:54:29 on a valid line break operator +- pycodestyle --ignore=W605,W504 --exclude='build' - if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then coverage run -m unittest2 discover; else coverage run -m unittest discover; fi after_script: - codecov diff --git a/CHANGELOG.md b/CHANGELOG.md index 2c1869666..dd2334ba4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,10 +1,28 @@ # Change Log All notable changes to this project will be documented in this file. +## [5.6.0] - 2018-08-20 ## +### Added +- [PR #593](https://github.com/sendgrid/sendgrid-python/pull/593): Adds support for dynamic template data. Big thanks to [Slam](https://github.com/3lnc) for the PR! Also, big thanks to [Wojciech Bartosiak](https://github.com/wojtek-fliposports) for [PR #597](https://github.com/sendgrid/sendgrid-python/pull/597)! + +## [5.5.0] - 2018-08-16 ## +### Added +- [PR #588](https://github.com/sendgrid/sendgrid-python/pull/588): Updates the Readme to include environment variable setup in windows. Big thanks to [Bhargav Chandaka](https://github.com/bchandaka) for the PR! +- [PR #599](https://github.com/sendgrid/sendgrid-python/pull/599): Updates the Readme to include additional API Key instruction. Big thanks to [Anshul Singhal](https://github.com/af4ro) for the PR! +- [PR #600](https://github.com/sendgrid/sendgrid-python/pull/600): Add CodeTriage Badge. Big thanks to [Anshul Singhal](https://github.com/af4ro) for the PR! +- [PR #601](https://github.com/sendgrid/sendgrid-python/pull/601): Readability improvements to CONTRIBUTING.md. Big thanks to [Anshul Singhal](https://github.com/af4ro) for the PR! +- [PR #604](https://github.com/sendgrid/sendgrid-python/pull/604): Readability improvements to CONTRIBUTING.md. Big thanks to [agnesjang98](https://github.com/agnesjang98) for the PR! + +### Fixed +- [PR #595](https://github.com/sendgrid/sendgrid-python/pull/595): Change type of category in Mail.add_category from string to Category. Big thanks to [Phawin Khongkhasawan](https://github.com/lifez) for the PR! +- [PR #596](https://github.com/sendgrid/sendgrid-python/pull/596): Fix Docker build. Big thanks to [Phawin Khongkhasawan](https://github.com/lifez) for the PR! +- [PR #598](https://github.com/sendgrid/sendgrid-python/pull/598): Fix python3 print example in TROUBLESHOOTING.md. Big thanks to [Ryan Jarvis](https://github.com/Cabalist) for the PR! +- [PR #603](https://github.com/sendgrid/sendgrid-python/pull/603): Update TROUBLESHOOTING.md to link to correct use cases page. Big thanks to [James Purpura](https://github.com/jpurpura) for the PR! + ## [5.4.1] - 2018-06-26 ## ### Fixed - [PR #585](https://github.com/sendgrid/sendgrid-python/pull/585): Fix typo in `mail_example.py`. Big thanks to [Anurag Anand](https://github.com/theanuraganand) for the PR! -- [PR #583](https://github.com/sendgrid/sendgrid-python/pull/585): Fix `Personalization.substitutions` setter. Trying to set substitutions directly rather than with add_substitution was causing an infinite regress. Big thanks to [Richard Nias](https://github.com/richardnias) for the PR! +- [PR #583](https://github.com/sendgrid/sendgrid-python/pull/583): Fix `Personalization.substitutions` setter. Trying to set substitutions directly rather than with add_substitution was causing an infinite regress. Big thanks to [Richard Nias](https://github.com/richardnias) for the PR! ## [5.4.0] - 2018-06-07 ## ### Added diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b38f1c1c4..a875bf9c1 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -46,7 +46,7 @@ A software bug is a demonstrable issue in the code base. In order for us to diag 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. +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 issue has already been fixed 3. Copy and fill in the Bug Report Template we have provided below @@ -191,8 +191,10 @@ Please run your code through: ```bash # Clone your fork of the repo into the current directory git clone https://github.com/sendgrid/sendgrid-python + # Navigate to the newly cloned directory cd sendgrid-python + # Assign the original repo to a remote called "upstream" git remote add upstream https://github.com/sendgrid/sendgrid-python ``` @@ -240,4 +242,4 @@ If you have any additional questions, please feel free to [email](mailto:dx@send ## Code Reviews -If you can, please look at open PRs and review them. Give feedback and help us merge these PRs much faster! If you don't know how, Github has some great [information on how to review a Pull Request](https://help.github.com/articles/about-pull-request-reviews/). +If you can, please look at open PRs and review them. Give feedback and help us merge these PRs much faster! If you don't know how, GitHub has some great [information on how to review a Pull Request](https://help.github.com/articles/about-pull-request-reviews/). diff --git a/README.md b/README.md index dbcebafdc..023a289ec 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,7 @@ [![MIT licensed](https://img.shields.io/badge/license-MIT-blue.svg)](./LICENSE.txt) [![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) **NEW:** @@ -38,6 +39,7 @@ We appreciate your continued support, thank you! * [License](#license) + # Installation ## Prerequisites @@ -46,19 +48,28 @@ We appreciate your continued support, thank you! - The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) ## Setup Environment Variables +### Mac -Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys), for example: +Update the development environment with your [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) (more info [here](https://sendgrid.com/docs/User_Guide/Settings/api_keys.html)), for example: ```bash echo "export SENDGRID_API_KEY='YOUR_API_KEY'" > sendgrid.env echo "sendgrid.env" >> .gitignore source ./sendgrid.env ``` +SendGrid also supports local environment file `.env`. Copy or rename `.env_sample` into `.env` and update [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) with your key. -Sendgrid also supports local environment file `.env`. Copy or rename `.env_sample` into `.env` and update [SENDGRID_API_KEY](https://app.sendgrid.com/settings/api_keys) with your key. +### Windows +Temporarily set the environment variable(accessible only during the current cli session): +```bash +set SENDGRID_API_KEY=YOUR_API_KEY +``` +Permanently set the environment variable(accessible in all subsequent cli sessions): +```bash +setx SENDGRID_API_KEY "YOUR_API_KEY" +``` ## Install Package - ```bash pip install sendgrid ``` @@ -67,6 +78,7 @@ pip install sendgrid - [Python-HTTP-Client](https://github.com/sendgrid/python-http-client) + # Quick Start @@ -93,7 +105,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](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail/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](https://github.com/sendgrid/sendgrid-python/blob/master/examples/helpers/mail_example.py#L16) is an example of how to add it. ### Without Mail Helper Class @@ -180,7 +192,7 @@ Please see [our helper](https://github.com/sendgrid/sendgrid-python/tree/master/ # Announcements -Join an experienced and passionate team that focuses on making an impact. Opportunities abound to grow the product - and grow your career! Check out our [Data Platform Engineer role](http://grnh.se/wbx1701) +Join an experienced and passionate team that focuses on making an impact. [Opportunities abound](https://sendgrid.com/careers) to grow the product - and grow your career! Please see our announcement regarding [breaking changes](https://github.com/sendgrid/sendgrid-python/issues/217). Your support is appreciated! @@ -200,9 +212,9 @@ Quick links: - [Feature Request](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#feature-request) - [Bug Reports](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#submit-a-bug-report) -- [Sign the CLA to Create a Pull Request](https://cla.sendgrid.com/sendgrid/sendgrid-python) - [Improvements to the Codebase](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#improvements-to-the-codebase) - [Review Pull Requests](https://github.com/sendgrid/sendgrid-python/blob/master/CONTRIBUTING.md#code-reviews) +- [Sign the CLA to Create a Pull Request](https://cla.sendgrid.com/sendgrid/sendgrid-python) # Troubleshooting @@ -212,7 +224,9 @@ Please see our [troubleshooting guide](https://github.com/sendgrid/sendgrid-pyth # About -sendgrid-python is guided and supported by the SendGrid [Developer Experience Team](mailto:dx@sendgrid.com). +sendgrid-python is guided and supported by the SendGrid Developer Experience Team. + +Email the Developer Experience Team [here](mailto:dx@sendgrid.com) in case of any queries. sendgrid-python is maintained and funded by SendGrid, Inc. The names and logos for sendgrid-python are trademarks of SendGrid, Inc. diff --git a/TROUBLESHOOTING.md b/TROUBLESHOOTING.md index 29c0c8e89..1298ab7d1 100644 --- a/TROUBLESHOOTING.md +++ b/TROUBLESHOOTING.md @@ -50,7 +50,7 @@ import urllib try: response = sg.client.mail.send.post(request_body=mail.get()) except urllib.error.HTTPError as e: - print e.read() + print(e.read()) ``` @@ -111,4 +111,4 @@ print mail.get() # Error Handling -Please review [our use_cases](https://github.com/sendgrid/sendgrid-python/use_cases/README.md) for examples of error handling. +Please review [our use_cases](https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/README.md#use-cases) for examples of error handling. diff --git a/docker-test/entrypoint.sh b/docker-test/entrypoint.sh index f64d0cccb..6749e8cf3 100644 --- a/docker-test/entrypoint.sh +++ b/docker-test/entrypoint.sh @@ -13,5 +13,5 @@ fi cd sendgrid-python python3.6 setup.py install -pip install pyyaml six werkzeug flask +pip install pyyaml six werkzeug flask python-http-client pytest exec $SHELL diff --git a/docker/Dockerfile b/docker/Dockerfile index e891e497c..cf2d36b6b 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -2,6 +2,9 @@ FROM ubuntu:xenial ENV PYTHON_VERSIONS='python2.6 python2.7 python3.4 python3.5 python3.6' \ OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" +ARG SENDGRID-PYTHON_VERSION +ARG BRANCH_HTTP_CLIENT + # install testing versions of python, including old versions, from deadsnakes RUN set -x \ && apt-get update \ @@ -25,19 +28,20 @@ RUN chmod +x ./install.sh && sync && \ # install pip, tox ADD https://bootstrap.pypa.io/get-pip.py get-pip.py RUN python2.7 get-pip.py && \ + python3.6 get-pip.py && \ pip install tox && \ rm get-pip.py #install pyyaml, six, werkzeug RUN python3.6 -m pip install pyyaml RUN python3.6 -m pip install six -RUN Python3.6 -m pip install werkzeug -RUN Python3.6 -m pip install flask +RUN python3.6 -m pip install werkzeug +RUN python3.6 -m pip install flask # set up default sendgrid env WORKDIR /root/sources -RUN git clone https://github.com/sendgrid/sendgrid-python.git && \ - git clone https://github.com/sendgrid/python-http-client.git +RUN git clone https://github.com/sendgrid/sendgrid-python.git --branch $SENDGRID-PYTHON_VERSION && \ + git clone https://github.com/sendgrid/python-http-client.git --branch $HTTP-CLIENT_VERSION WORKDIR /root RUN ln -s /root/sources/sendgrid-python/sendgrid && \ ln -s /root/sources/python-http-client/python_http_client diff --git a/docker/Makefile b/docker/Makefile new file mode 100644 index 000000000..76ccb73af --- /dev/null +++ b/docker/Makefile @@ -0,0 +1,20 @@ +stop: + docker-compose stop + +rm: stop + docker-compose stop -fvs + +clean: + docker rmi %(docker images -aq) + +clean_untagged: + docker rmi $(docker images --quiet --filter "dangling=true") 2>/dev/null + +build: + docker-compose up -d + +build-build: + docker-compose up --build -d + +up: rm clean build-build + echo "Sendgrid-python environment is alive :D" diff --git a/docker/README.md b/docker/README.md index a523dc93d..b439fe892 100644 --- a/docker/README.md +++ b/docker/README.md @@ -1,5 +1,7 @@ # Supported tags and respective `Dockerfile` links - - `v5.4.1`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.6.0`, `latest` [(Dockerfile)](https://github.com/sendgrid/sendgrid-python/blob/master/docker/Dockerfile) + - `v5.5.0` + - `v5.4.1` - `v5.4.0` - `v5.3.0` - `v5.2.1` diff --git a/docker/USAGE.md b/docker/USAGE.md index cd543c402..d869a77ce 100644 --- a/docker/USAGE.md +++ b/docker/USAGE.md @@ -70,6 +70,63 @@ $ docker run -it -v /path/to/cool-sendgrid-python:/mnt/sendgrid-python sendgrid/ Note that the paths you specify in `-v` must be absolute. +# Docker Compose + + +# Quickstart + +1. Install docker-compose on your machine. +2. Must copy sendgrid.env to .env file. +3. Edit .env file for yours versions and paths. +4. Must create env folder for clone yours repo. +5. Have fun! :D + +## Using tag's for versions - DockerHub: + +### Edit variable TAG on .env/env_sample file + +```sh-session +$ sed -ie 's/TAG=latest/TAG=choice_a_version/g' +``` +### Run service using tags + +```sh-session +$ cd /path/to/sendgrid-python/docker +$ docker-compose up -d sendgrid +``` + +## Specifying specific versions: + +### Edit variable TAG on .env/env_sample file + +```sh-session +$ sed -ie 's/SENDGRID_PYTHON_VERSION=vy.x.z/SENDGRID_PYTHON_VERSION=vx.y.z/g' +$ sed -ie 's/HTTP_CLIENT_VERSION=vy.x.z/HTTP_CLIENT_VERSION=vx.y.z/g' +``` + +### Run service + +```sh-session +$ cd /path/to/sendgrid-python/docker +$ docker-compose up -d sendgrid-dev +``` + +## Specifying your own fork: + +### Edit variable TAG on .env/env_sample file + +```sh-session +$ sed -ie 's/TAG=latest/TAG=choice_a_version/g' +$ sed -ie 's/SENDGRID_PYTHON_VERSION=vy.x.z/SENDGRID_PYTHON_VERSION=vx.y.z/g' +``` + +### Run service + +```sh-session +$ cd /path/to/sendgrid-python/docker +$ docker-compose up -d sendgrid-beta +``` + # Testing Testing is easy! Run the container, `cd sendgrid`, and run `tox`. diff --git a/docker/docker-compose.yml b/docker/docker-compose.yml new file mode 100644 index 000000000..2a435b39f --- /dev/null +++ b/docker/docker-compose.yml @@ -0,0 +1,35 @@ +version: "3.3" + +services: + sendgrid: + image: sendgrid/sendgrid-python:${TAG} + restart: unless-stopped + container_name: sendgrid-prod + tty: true + env_file: + - .env + + sendgrid-dev: + build: + context: . + args: + - SENDGRID-PYTHON_VERSION=${SENDGRID_PYTHON_VERSION} + - HTTP-CLIENT_VERSION=${HTTP_CLIENT_VERSION} + restart: unless-stopped + container_name: sendgrid-dev + tty: true + env_file: + - .env + volumes: + - ${PATH_TO_SENDGRID_PYTHON_DEV}:/mnt/sendgrid-python + - ${PATH_TO_HTTP_CLIENT_DEV}:/mnt/python-http-client + + sendgrid-beta: + image: sendgrid/sendgrid-python:${TAG} + restart: unless-stopped + container_name: sendgrid-beta + tty: true + env_file: + - .env + volumes: + - ${PATH_TO_SENDGRID_PYTHON_FORK}:/root/sources/sendgrid-python/sendgrid diff --git a/docker/env/python-dev/sendgrid-python b/docker/env/python-dev/sendgrid-python new file mode 160000 index 000000000..28cf42f6d --- /dev/null +++ b/docker/env/python-dev/sendgrid-python @@ -0,0 +1 @@ +Subproject commit 28cf42f6d590695de7e7ecdedcb67e9d8d4729ac diff --git a/docker/sendgrid.env b/docker/sendgrid.env new file mode 100644 index 000000000..ace58fafa --- /dev/null +++ b/docker/sendgrid.env @@ -0,0 +1,8 @@ +TAG=latest +SENDGRID_PYTHON_VERSION="v3.6.1" +HTTP_CLIENT_VERSION="v1.2.4" +PATH_TO_SENDGRID_PYTHON_DEV=../env/python-dev/sendgrid-python +PATH_TO_HTTP_CLIENT_DEV=../env/python-dev/python-http-client +PATH_TO_SENDGRID_PYTHON_PROD=../env/python-prod/sendgrid-python +PATH_TO_HTTP_CLIENT_PROD=../env/python-prod/python-http-client +PATH_TO_SENDGRID_PYTHON_FORK=../env/python-fork/sendgrid-python diff --git a/examples/campaigns/campaigns.py b/examples/campaigns/campaigns.py index 76bdaeb9e..effda1015 100644 --- a/examples/campaigns/campaigns.py +++ b/examples/campaigns/campaigns.py @@ -14,7 +14,8 @@ "spring line" ], "custom_unsubscribe_url": "", - "html_content": "
Check out our spring line!
", + "html_content": "Check out our " + "spring line!
", "ip_pool": "marketing", "list_ids": [ 110, @@ -52,7 +53,8 @@ "categories": [ "summer line" ], - "html_content": "Check out our summer line!
", + "html_content": "Check out our " + "summer line!
", "plain_content": "Check out our summer line!", "subject": "New Products for Summer!", "title": "May Newsletter" diff --git a/examples/categories/categories.py b/examples/categories/categories.py index 774284ee8..4517645a8 100644 --- a/examples/categories/categories.py +++ b/examples/categories/categories.py @@ -27,7 +27,8 @@ print(response.headers) ################################################## -# Retrieve sums of email stats for each category [Needs: Stats object defined, has category ID?] # +# Retrieve sums of email stats for each category [Needs: Stats object defined, +# has category ID?] # # GET /categories/stats/sums # params = {'end_date': '2016-04-01', diff --git a/examples/helpers/mail/mail_example.py b/examples/helpers/mail_example.py similarity index 85% rename from examples/helpers/mail/mail_example.py rename to examples/helpers/mail_example.py index b2de7f0a0..afa2f1ed5 100644 --- a/examples/helpers/mail/mail_example.py +++ b/examples/helpers/mail_example.py @@ -1,8 +1,6 @@ -import json -import os -import urllib2 +from sendgrid import SendGridAPIClient from sendgrid.helpers.mail import * -from sendgrid import * + # NOTE: you will need move this file to the root # directory of this project to execute properly. @@ -80,7 +78,9 @@ def get_mock_personalization_dict(): def build_attachment1(): - """Build attachment mock.""" + """Build attachment mock. Make sure your content is base64 encoded before + passing into attachment.content. + Another example: https://github.com/sendgrid/sendgrid-python/blob/master/use_cases/attachment.md""" # noqa attachment = Attachment() attachment.content = ("TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNl" "Y3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12") @@ -179,8 +179,8 @@ def build_kitchen_sink(): mail.send_at = 1443636842 # This must be a valid [batch ID] - # (https://sendgrid.com/docs/API_Reference/SMTP_API/scheduling_parameters.html) to work - # mail.set_batch_id("N2VkYjBjYWItMGU4OC0xMWU2LWJhMzYtZjQ1Yzg5OTBkNzkxLWM5ZTUyZjNhOA") + # (https://sendgrid.com/docs/API_Reference/SMTP_API/scheduling_parameters.html) to work # noqa + # mail.set_batch_id("N2VkYjBjYWItMGU4OC0xMWU2LWJhMzYtZjQ1Yzg5OTBkNzkxLWM5ZTUyZjNhOA") # noqa mail.asm = ASM(99, [4, 5, 6, 7, 8]) mail.ip_pool_name = "24" mail.mail_settings = build_mail_settings() @@ -217,3 +217,33 @@ def send_kitchen_sink(): # this will only send an email if you set SandBox Mode to False send_kitchen_sink() + + +def transactional_template_usage(): + # Assumes you set your environment variable: + # https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key + + """ + Sample usage of dynamic (handlebars) transactional templates. + To make this work, you should have dynamic template created within your + SendGrid account. For this particular example, template may be like:: + +Hello, {{name}}! Your current balance is {{balance}}
+ + """ + mail = Mail() + mail.from_email = Email('templates@sendgrid.com') + mail.template_id = 'd-your-dynamic-template-uid' + p = Personalization() + p.add_to(Email('user@example.com')) + p.dynamic_template_data = { + 'name': 'Bob', + 'balance': 42 + } + mail.add_personalization(p) + + sg = SendGridAPIClient() + response = sg.client.mail.send.post(request_body=mail.get()) + print(response.status_code) + print(response.headers) + print(response.body) diff --git a/examples/helpers/stats/stats_example.py b/examples/helpers/stats/stats_example.py new file mode 100644 index 000000000..e54f45354 --- /dev/null +++ b/examples/helpers/stats/stats_example.py @@ -0,0 +1,102 @@ +import json +import os +from sendgrid.helpers.stats import * +from sendgrid import * + +# NOTE: you will need move this file to the root directory of this project to +# execute properly. + +# Assumes you set your environment variable: +# https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key +sg = SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + + +def pprint_json(json_raw): + print(json.dumps(json.loads(json_raw), indent=2, sort_keys=True)) + + +def build_global_stats(): + global_stats = Stats() + global_stats.start_date = '2017-10-14' + global_stats.end_date = '2017-10-20' + global_stats.aggregated_by = 'day' + return global_stats.get() + + +def build_category_stats(): + category_stats = CategoryStats('2017-10-15', ['foo', 'bar']) + # category_stats.start_date = '2017-10-15' + # category_stats.add_category(Category("foo")) + # category_stats.add_category(Category("bar")) + return category_stats.get() + + +def build_category_stats_sums(): + category_stats = CategoryStats() + category_stats.start_date = '2017-10-15' + category_stats.limit = 5 + category_stats.offset = 1 + return category_stats.get() + + +def build_subuser_stats(): + subuser_stats = SubuserStats('2017-10-20', ['aaronmakks', 'foo']) + # subuser_stats.start_date = '2017-10-15' + # subuser_stats.add_subuser(Subuser("foo")) + # subuser_stats.add_subuser(Subuser("bar")) + return subuser_stats.get() + + +def build_subuser_stats_sums(): + subuser_stats = SubuserStats() + subuser_stats.start_date = '2017-10-15' + subuser_stats.limit = 5 + subuser_stats.offset = 1 + return subuser_stats.get() + + +def get_global_stats(): + stats_params = build_global_stats() + response = sg.client.stats.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_category_stats(): + stats_params = build_category_stats() + response = sg.client.categories.stats.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_category_stats_sums(): + stats_params = build_category_stats_sums() + response = sg.client.categories.stats.sums.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_subuser_stats(): + stats_params = build_subuser_stats() + response = sg.client.subusers.stats.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +def get_subuser_stats_sums(): + stats_params = build_subuser_stats_sums() + response = sg.client.subusers.stats.sums.get(query_params=stats_params) + print(response.status_code) + print(response.headers) + pprint_json(response.body) + + +get_global_stats() +get_category_stats() +get_category_stats_sums() +get_subuser_stats() +get_subuser_stats_sums() diff --git a/examples/mail/mail.py b/examples/mail/mail.py index e853d422c..43d301dfa 100644 --- a/examples/mail/mail.py +++ b/examples/mail/mail.py @@ -57,7 +57,8 @@ "content": [ { "type": "text/html", - "value": "
Hello, world!
" + "value": "Hello, world!
" + "" } ], "custom_args": { @@ -151,7 +152,8 @@ "ganalytics": { "enable": True, "utm_campaign": "[NAME OF YOUR REFERRER SOURCE]", - "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL FROM ADS]", + "utm_content": "[USE THIS SPACE TO DIFFERENTIATE YOUR EMAIL" + " FROM ADS]", "utm_medium": "[NAME OF YOUR MARKETING MEDIUM e.g. email]", "utm_name": "[NAME OF YOUR CAMPAIGN]", "utm_term": "[IDENTIFY PAID KEYWORDS HERE]" @@ -162,9 +164,11 @@ }, "subscription_tracking": { "enable": True, - "html": "If you would like to unsubscribe and stop receiving these emails <% clickhere %>.", + "html": "If you would like to unsubscribe and stop receiving " + "these emails <% clickhere %>.", "substitution_tag": "<%click here%>", - "text": "If you would like to unsubscribe and stop receiving these emails <% click here %>." + "text": "If you would like to unsubscribe and stop receiving " + "these emails <% click here %>." } } } diff --git a/register.py b/register.py index 0a7ffe8d8..00ddca15c 100644 --- a/register.py +++ b/register.py @@ -17,4 +17,4 @@ ''' final_text = readme_rst.replace(replace, replacement) with open('./README.txt', 'w', encoding='utf-8') as f: - f.write(final_text) + f.write(final_text) diff --git a/requirements.txt b/requirements.txt index 34d770b5b..9756ebf8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -2,3 +2,4 @@ Flask==0.10.1 PyYAML==3.11 python-http-client==2.2.1 six==1.10.0 +pytest==3.7.1 \ No newline at end of file diff --git a/sendgrid/__init__.py b/sendgrid/__init__.py index 2bbd38b59..1b5100c01 100644 --- a/sendgrid/__init__.py +++ b/sendgrid/__init__.py @@ -2,7 +2,7 @@ This library allows you to quickly and easily use the SendGrid Web API v3 via Python. -For more information on this library, see the README on Github. +For more information on this library, see the README on GitHub. http://github.com/sendgrid/sendgrid-python For more information on the SendGrid v3 API, see the v3 docs: http://sendgrid.com/docs/API_Reference/api_v3.html diff --git a/sendgrid/helpers/endpoints/__init__.py b/sendgrid/helpers/endpoints/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sendgrid/helpers/endpoints/ip/__init__.py b/sendgrid/helpers/endpoints/ip/__init__.py new file mode 100644 index 000000000..e69de29bb diff --git a/sendgrid/helpers/endpoints/ip/unassigned.py b/sendgrid/helpers/endpoints/ip/unassigned.py new file mode 100644 index 000000000..ba07fd747 --- /dev/null +++ b/sendgrid/helpers/endpoints/ip/unassigned.py @@ -0,0 +1,59 @@ +import json + + +def format_ret(return_set, as_json=False): + """ decouple, allow for modifications to return type + returns a list of ip addresses in object or json form """ + ret_list = list() + for item in return_set: + d = {"ip": item} + ret_list.append(d) + + if as_json: + return json.dumps(ret_list) + + return ret_list + + +def unassigned(data, as_json=False): + """ https://sendgrid.com/docs/API_Reference/api_v3.html#ip-addresses + The /ips rest endpoint returns information about the IP addresses + and the usernames assigned to an IP + + unassigned returns a listing of the IP addresses that are allocated + but have 0 users assigned + + data (response.body from sg.client.ips.get()) + as_json False -> get list of dicts + True -> get json object + + example: + sg = sendgrid.SendGridAPIClient( + apikey=os.environ.get('SENDGRID_API_KEY') + ) + + params = { + 'subuser': 'test_string', + 'ip': 'test_string', + 'limit': 1, + 'exclude_whitelabels': 'true', + 'offset': 1 + } + response = sg.client.ips.get(query_params=params) + if response.status_code == 201: + data = response.body + unused = unassinged(data) """ + + no_subusers = set() + + if not isinstance(data, list): + return format_ret(no_subusers, as_json=as_json) + + for current in data: + num_subusers = len(current["subusers"]) + if num_subusers == 0: + current_ip = current["ip"] + no_subusers.add(current_ip) + + ret_val = format_ret(no_subusers, as_json=as_json) + return ret_val diff --git a/sendgrid/helpers/inbound/app.py b/sendgrid/helpers/inbound/app.py index 0d4435907..c199cb725 100755 --- a/sendgrid/helpers/inbound/app.py +++ b/sendgrid/helpers/inbound/app.py @@ -3,13 +3,13 @@ See README.txt for usage instructions.""" try: from config import Config -except: +except: # noqa # Python 3+, Travis from sendgrid.helpers.inbound.config import Config try: from parse import Parse -except: +except: # noqa # Python 3+, Travis from sendgrid.helpers.inbound.parse import Parse diff --git a/sendgrid/helpers/inbound/config.py b/sendgrid/helpers/inbound/config.py index d0c6517bc..36ba89595 100644 --- a/sendgrid/helpers/inbound/config.py +++ b/sendgrid/helpers/inbound/config.py @@ -10,8 +10,8 @@ def __init__(self, **opts): if os.environ.get('ENV') != 'prod': # We are not in Heroku self.init_environment() - """Allow variables assigned in config.yml available the following variables - via properties""" + """Allow variables assigned in config.yml available the following + variables via properties""" self.path = opts.get( 'path', os.path.abspath(os.path.dirname(__file__)) ) diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index 6de575aab..d5138b89e 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -37,9 +37,12 @@ def url(self): """URL to send to.""" return self._url + def main(): config = Config() - parser = argparse.ArgumentParser(description='Test data and optional host.') + parser = argparse.ArgumentParser( + description='Test data and optional host.' + ) parser.add_argument('data', type=str, help='path to the sample data') @@ -54,5 +57,6 @@ def main(): print(response.headers) print(response.body) + if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index cff8ac498..da4ed8027 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,5 +1,6 @@ from .validators import ValidateAPIKey + class Content(object): """Content to be included in your email. diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py index ab4dd9c0c..1b5da92fc 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -2,6 +2,7 @@ # Various types of extensible SendGrid related exceptions ################################################################ + class SendGridException(Exception): """Wrapper/default SendGrid-related exception""" pass @@ -14,9 +15,8 @@ class APIKeyIncludedException(SendGridException): message -- explanation of the error """ - def __init__(self, - expression="Email body", + def __init__(self, + expression="Email body", message="SendGrid API Key detected"): self.expression = expression self.message = message - diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index 6554d4508..3114e5b21 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -1,6 +1,7 @@ """v3/mail/send response body builder""" from .personalization import Personalization from .header import Header +from .email import Email class Mail(object): @@ -8,14 +9,15 @@ class Mail(object): Use get() to get the request body. """ - def __init__(self, - from_email=None, - subject=None, - to_email=None, + def __init__(self, + from_email=None, + subject=None, + to_email=None, content=None): """Create a Mail object. - If any parameters are not supplied, they must be set after initialization. + If any parameters are not supplied, they must be set after + initialization. :param from_email: Email address to send from. :type from_email: Email, optional :param subject: Subject line of emails. @@ -73,7 +75,7 @@ def get(self): if self.from_email is not None: mail["from"] = self.from_email.get() - + if self.subject is not None: mail["subject"] = self.subject @@ -147,6 +149,8 @@ def from_email(self): @from_email.setter def from_email(self, value): + if isinstance(value, str): + value = Email(value) self._from_email = value @property @@ -200,7 +204,8 @@ def batch_id(self): This represents a batch of emails sent at the same time. Including a batch_id in your request allows you include this email in that batch, and also enables you to cancel or pause the delivery of that batch. - For more information, see https://sendgrid.com/docs/API_Reference/Web_API_v3/cancel_schedule_send.html + For more information, see + https://sendgrid.com/docs/API_Reference/Web_API_v3/cancel_schedule_send.html # noqa :rtype: int """ @@ -306,7 +311,7 @@ def add_content(self, content): """ if self._contents is None: self._contents = [] - + # Text content should be before HTML content if content._type == "text/plain": self._contents.insert(0, content) @@ -376,9 +381,9 @@ def categories(self): return self._categories def add_category(self, category): - """Add a Category to this Mail. Must be less than 255 characters. + """Add a Category to this Mail. - :type category: string + :type category: Category """ self._categories.append(category) diff --git a/sendgrid/helpers/mail/personalization.py b/sendgrid/helpers/mail/personalization.py index 8bb4bed0b..97499a34c 100644 --- a/sendgrid/helpers/mail/personalization.py +++ b/sendgrid/helpers/mail/personalization.py @@ -1,6 +1,10 @@ class Personalization(object): """A Personalization defines who should receive an individual message and how that message should be handled. + + :var dynamic_template_data: data for dynamic transactional template. + Should be JSON-serializeable structure. No pre-processing will be done + prior to sending this via http client. """ def __init__(self): @@ -13,6 +17,7 @@ def __init__(self): self._substitutions = [] self._custom_args = [] self._send_at = None + self._dynamic_template_data = None @property def tos(self): @@ -55,7 +60,8 @@ def add_cc(self, email): @property def bccs(self): - """A list of recipients who will receive blind carbon copies of this email. + """A list of recipients who will receive blind carbon copies of + this email. :rtype: list(dict) """ @@ -158,6 +164,18 @@ def send_at(self): def send_at(self, value): self._send_at = value + @property + def dynamic_template_data(self): + """Data for dynamic transactional template. + + :rtype: JSON-serializeable structure + """ + return self._dynamic_template_data + + @dynamic_template_data.setter + def dynamic_template_data(self, json): + self._dynamic_template_data = json + def get(self): """ Get a JSON-ready representation of this Personalization. @@ -198,4 +216,9 @@ def get(self): if self.send_at is not None: personalization["send_at"] = self.send_at + + if self.dynamic_template_data is not None: + personalization['dynamic_template_data'] = \ + self.dynamic_template_data + return personalization diff --git a/sendgrid/helpers/mail/subscription_tracking.py b/sendgrid/helpers/mail/subscription_tracking.py index 204e427f0..552642ed8 100644 --- a/sendgrid/helpers/mail/subscription_tracking.py +++ b/sendgrid/helpers/mail/subscription_tracking.py @@ -4,7 +4,8 @@ class SubscriptionTracking(object): location of the link within your email, you may use the substitution_tag. """ - def __init__(self, enable=None, text=None, html=None, substitution_tag=None): + def __init__(self, enable=None, text=None, html=None, + substitution_tag=None): """Create a SubscriptionTracking to customize subscription management. :param enable: Whether this setting is enabled. @@ -13,7 +14,8 @@ def __init__(self, enable=None, text=None, html=None, substitution_tag=None): :type text: string, optional :param html: HTML to be appended to the email with the link as "<% %>". :type html: string, optional - :param substitution_tag: Tag replaced with URL. Overrides text, html params. + :param substitution_tag: Tag replaced with URL. Overrides text, + html params. :type substitution_tag: string, optional """ self.enable = enable diff --git a/sendgrid/helpers/mail/tracking_settings.py b/sendgrid/helpers/mail/tracking_settings.py index cb19e2bae..73642586c 100644 --- a/sendgrid/helpers/mail/tracking_settings.py +++ b/sendgrid/helpers/mail/tracking_settings.py @@ -10,7 +10,8 @@ def __init__(self): @property def click_tracking(self): - """Allows you to track whether a recipient clicked a link in your email. + """Allows you to track whether a recipient clicked a link in your + email. :rtype: ClickTracking """ diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index b4a69f697..b12d95bd0 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -3,6 +3,7 @@ # Various types of Validators ################################################################ + class ValidateAPIKey(object): """Validates content to ensure SendGrid API key is not present""" @@ -27,15 +28,16 @@ def __init__(self, regex_strings=None, use_default=True): default_regex_string = 'SG\.[0-9a-zA-Z]+\.[0-9a-zA-Z]+' self.regexes.add(re.compile(default_regex_string)) - def validate_message_dict(self, request_body): - """With the JSON dict that will be sent to SendGrid's API, - check the content for SendGrid API keys - throw exception if found + """With the JSON dict that will be sent to SendGrid's API, + check the content for SendGrid API keys - throw exception if found + Args: request_body (:obj:`dict`): message parameter that is an argument to: mail.send.post() Raises: - APIKeyIncludedException: If any content in request_body matches regex + APIKeyIncludedException: If any content in request_body + matches regex """ # Handle string in edge-case @@ -44,9 +46,9 @@ def validate_message_dict(self, request_body): # Default param elif isinstance(request_body, dict): - + contents = request_body.get("content", list()) - + for content in contents: if content is not None: if (content.get("type") == "text/html" or @@ -54,10 +56,10 @@ def validate_message_dict(self, request_body): message_text = content.get("value", "") self.validate_message_text(message_text) - def validate_message_text(self, message_string): - """With a message string, check to see if it contains a SendGrid API Key - If a key is found, throw an exception + """With a message string, check to see if it contains a + SendGrid API Key If a key is found, throw an exception + Args: message_string (str): message that will be sent Raises: @@ -68,4 +70,3 @@ def validate_message_text(self, message_string): for regex in self.regexes: if regex.match(message_string) is not None: raise APIKeyIncludedException() - diff --git a/sendgrid/helpers/stats/README.md b/sendgrid/helpers/stats/README.md new file mode 100644 index 000000000..1fe31558b --- /dev/null +++ b/sendgrid/helpers/stats/README.md @@ -0,0 +1,10 @@ +**This helper allows you to quickly and easily build a Stats object for sending your email stats to a database.** + +# Quick Start + +Run the [example](https://github.com/sendgrid/sendgrid-python/tree/master/examples/helpers/stats) (make sure you have set your environment variable to include your SENDGRID_API_KEY). + +## Usage + +- See the [examples](https://github.com/sendgrid/sendgrid-python/tree/master/examples/helpers/stats) for complete working examples. +- [Documentation](https://sendgrid.com/docs/API_Reference/Web_API_v3/Stats/index.html) \ No newline at end of file diff --git a/sendgrid/helpers/stats/__init__.py b/sendgrid/helpers/stats/__init__.py new file mode 100644 index 000000000..9ee4dcdd8 --- /dev/null +++ b/sendgrid/helpers/stats/__init__.py @@ -0,0 +1 @@ +from .stats import * # noqa diff --git a/sendgrid/helpers/stats/stats.py b/sendgrid/helpers/stats/stats.py new file mode 100644 index 000000000..8fe1399a2 --- /dev/null +++ b/sendgrid/helpers/stats/stats.py @@ -0,0 +1,222 @@ +class Stats(object): + def __init__( + self, start_date=None): + self._start_date = None + self._end_date = None + self._aggregated_by = None + self._sort_by_metric = None + self._sort_by_direction = None + self._limit = None + self._offset = None + + # Minimum required for stats + if start_date: + self.start_date = start_date + + def __str__(self): + return str(self.get()) + + def get(self): + """ + :return: response stats dict + """ + stats = {} + if self.start_date is not None: + stats["start_date"] = self.start_date + if self.end_date is not None: + stats["end_date"] = self.end_date + if self.aggregated_by is not None: + stats["aggregated_by"] = self.aggregated_by + if self.sort_by_metric is not None: + stats["sort_by_metric"] = self.sort_by_metric + if self.sort_by_direction is not None: + stats["sort_by_direction"] = self.sort_by_direction + if self.limit is not None: + stats["limit"] = self.limit + if self.offset is not None: + stats["offset"] = self.offset + return stats + + @property + def start_date(self): + return self._start_date + + @start_date.setter + def start_date(self, value): + self._start_date = value + + @property + def end_date(self): + return self._end_date + + @end_date.setter + def end_date(self, value): + self._end_date = value + + @property + def aggregated_by(self): + return self._aggregated_by + + @aggregated_by.setter + def aggregated_by(self, value): + self._aggregated_by = value + + @property + def sort_by_metric(self): + return self._sort_by_metric + + @sort_by_metric.setter + def sort_by_metric(self, value): + self._sort_by_metric = value + + @property + def sort_by_direction(self): + return self._sort_by_direction + + @sort_by_direction.setter + def sort_by_direction(self, value): + self._sort_by_direction = value + + @property + def limit(self): + return self._limit + + @limit.setter + def limit(self, value): + self._limit = value + + @property + def offset(self): + return self._offset + + @offset.setter + def offset(self, value): + self._offset = value + + +class CategoryStats(Stats): + def __init__(self, start_date=None, categories=None): + self._categories = None + super(CategoryStats, self).__init__() + + # Minimum required for category stats + if start_date and categories: + self.start_date = start_date + for cat_name in categories: + self.add_category(Category(cat_name)) + + def get(self): + """ + :return: response stats dict + """ + stats = {} + if self.start_date is not None: + stats["start_date"] = self.start_date + if self.end_date is not None: + stats["end_date"] = self.end_date + if self.aggregated_by is not None: + stats["aggregated_by"] = self.aggregated_by + if self.sort_by_metric is not None: + stats["sort_by_metric"] = self.sort_by_metric + if self.sort_by_direction is not None: + stats["sort_by_direction"] = self.sort_by_direction + if self.limit is not None: + stats["limit"] = self.limit + if self.offset is not None: + stats["offset"] = self.offset + if self.categories is not None: + stats['categories'] = [category.get() for category in + self.categories] + return stats + + @property + def categories(self): + return self._categories + + def add_category(self, category): + if self._categories is None: + self._categories = [] + self._categories.append(category) + + +class SubuserStats(Stats): + def __init__(self, start_date=None, subusers=None): + self._subusers = None + super(SubuserStats, self).__init__() + + # Minimum required for subusers stats + if start_date and subusers: + self.start_date = start_date + for subuser_name in subusers: + self.add_subuser(Subuser(subuser_name)) + + def get(self): + """ + :return: response stats dict + """ + stats = {} + if self.start_date is not None: + stats["start_date"] = self.start_date + if self.end_date is not None: + stats["end_date"] = self.end_date + if self.aggregated_by is not None: + stats["aggregated_by"] = self.aggregated_by + if self.sort_by_metric is not None: + stats["sort_by_metric"] = self.sort_by_metric + if self.sort_by_direction is not None: + stats["sort_by_direction"] = self.sort_by_direction + if self.limit is not None: + stats["limit"] = self.limit + if self.offset is not None: + stats["offset"] = self.offset + if self.subusers is not None: + stats['subusers'] = [subuser.get() for subuser in + self.subusers] + return stats + + @property + def subusers(self): + return self._subusers + + def add_subuser(self, subuser): + if self._subusers is None: + self._subusers = [] + self._subusers.append(subuser) + + +class Category(object): + + def __init__(self, name=None): + self._name = None + if name is not None: + self._name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + def get(self): + return self.name + + +class Subuser(object): + + def __init__(self, name=None): + self._name = None + if name is not None: + self._name = name + + @property + def name(self): + return self._name + + @name.setter + def name(self, value): + self._name = value + + def get(self): + return self.name diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index 0f09bd542..9811c2292 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -2,7 +2,7 @@ This library allows you to quickly and easily use the SendGrid Web API v3 via Python. -For more information on this library, see the README on Github. +For more information on this library, see the README on GitHub. http://github.com/sendgrid/sendgrid-python For more information on the SendGrid v3 API, see the v3 docs: http://sendgrid.com/docs/API_Reference/api_v3.html @@ -25,7 +25,9 @@ class SendGridAPIClient(object): """The SendGrid API Client. Use this object to interact with the v3 API. For example: - sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + sg = sendgrid.SendGridAPIClient(apikey=os.environ.get( + 'SENDGRID_API_KEY') + ) ... mail = Mail(from_email, subject, to_email, content) response = sg.client.mail.send.post(request_body=mail.get()) @@ -43,28 +45,34 @@ def __init__( **opts): # TODO: remove **opts for 6.x release """ Construct SendGrid v3 API object. - Note that underlying client being set up during initialization, therefore changing - attributes in runtime will not affect HTTP client behaviour. + Note that underlying client being set up during initialization, + therefore changing attributes in runtime will not affect HTTP + client behaviour. - :param apikey: SendGrid API key to use. If not provided, key will be read from - environment variable "SENDGRID_API_KEY" + :param apikey: SendGrid API key to use. If not provided, key will be + read from environment variable "SENDGRID_API_KEY" :type apikey: basestring - :param api_key: SendGrid API key to use. Provides backward compatibility + :param api_key: SendGrid API key to use. Provides backward + compatibility .. deprecated:: 5.3 Use apikey instead :type api_key: basestring - :param impersonate_subuser: the subuser to impersonate. Will be passed by - "On-Behalf-Of" header by underlying client. - See https://sendgrid.com/docs/User_Guide/Settings/subusers.html for more details + :param impersonate_subuser: the subuser to impersonate. Will be passed + by "On-Behalf-Of" header by underlying client. + See https://sendgrid.com/docs/User_Guide/Settings/subusers.html + for more details :type impersonate_subuser: basestring :param host: base URL for API calls :type host: basestring - :param opts: dispatcher for deprecated arguments. Added for backward-compatibility - with `path` parameter. Should be removed during 6.x release + :param opts: dispatcher for deprecated arguments. Added for + backward-compatibility with `path` parameter. + Should be removed during 6.x release """ if opts: warnings.warn( - 'Unsupported argument(s) provided: {}'.format(list(opts.keys())), + 'Unsupported argument(s) provided: {}'.format( + list(opts.keys()) + ), DeprecationWarning) self.apikey = apikey or api_key or os.environ.get('SENDGRID_API_KEY') self.impersonate_subuser = impersonate_subuser @@ -72,9 +80,11 @@ def __init__( self.useragent = 'sendgrid/{0};python'.format(__version__) self.version = __version__ - self.client = python_http_client.Client(host=self.host, - request_headers=self._default_headers, - version=3) + self.client = python_http_client.Client( + host=self.host, + request_headers=self._default_headers, + version=3 + ) @property def _default_headers(self): diff --git a/sendgrid/version.py b/sendgrid/version.py index c191754f8..fffe2c469 100644 --- a/sendgrid/version.py +++ b/sendgrid/version.py @@ -1,2 +1,2 @@ -version_info = (5, 4, 1) +version_info = (5, 6, 0) __version__ = '.'.join(str(v) for v in version_info) diff --git a/setup.py b/setup.py index 014691b61..11aa3a07e 100644 --- a/setup.py +++ b/setup.py @@ -20,6 +20,7 @@ def getRequires(): deps.append('unittest2py3k') return deps + setup( name='sendgrid', version=str(__version__), diff --git a/test/test_app.py b/test/test_app.py index 1a8e4a698..13b0a9522 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -23,4 +23,4 @@ def test_up_and_running(self): def test_used_port_true(self): if self.config.debug_mode: port = int(os.environ.get("PORT", self.config.port)) - self.assertEqual(port, self.config.port) \ No newline at end of file + self.assertEqual(port, self.config.port) diff --git a/test/test_mail.py b/test/test_mail.py index 7721b5205..711f8e4c9 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -52,35 +52,43 @@ def test_sendgridAPIKey(self): personalization.add_to(Email("test@example.com")) mail.add_personalization(personalization) - #Try to include SendGrid API key + # Try to include SendGrid API key try: - mail.add_content(Content("text/plain", "some SG.2123b1B.1212lBaC here")) + mail.add_content( + Content( + "text/plain", + "some SG.2123b1B.1212lBaC here" + ) + ) mail.add_content( Content( "text/html", - "some SG.Ba2BlJSDba.232Ln2 here")) + "some SG.Ba2BlJSDba.232Ln2 here" + ) + ) self.assertEqual( json.dumps( mail.get(), sort_keys=True), - '{"content": [{"type": "text/plain", "value": "some text here"}, ' - '{"type": "text/html", ' - '"value": "some text here"}], ' + '{"content": [' + '{"type": "text/plain", "value": "some text here"' + '}, {"type": "text/html", "value": "some text ' + 'here"' + '}], ' '"from": {"email": "test@example.com"}, "personalizations": ' '[{"to": [{"email": "test@example.com"}]}], ' '"subject": "Hello World from the SendGrid Python Library"}' ) - #Exception should be thrown + # Exception should be thrown except Exception as e: pass - #Exception not thrown + # Exception not thrown else: self.fail("Should have failed as SendGrid API key included") - def test_helloEmail(self): self.max_diff = None @@ -116,7 +124,9 @@ def test_helloEmail(self): self.assertTrue(isinstance(str(mail), str)) def test_helloEmailAdditionalContent(self): - """Tests bug found in Issue-451 with Content ordering causing a crash""" + """ + Tests bug found in Issue-451 with Content ordering causing a crash + """ self.maxDiff = None @@ -130,8 +140,9 @@ def test_helloEmailAdditionalContent(self): personalization = Personalization() personalization.add_to(Email("test@example.com")) mail.add_personalization(personalization) - - mail.add_content(Content("text/html", "some text here")) + + mail.add_content(Content("text/html", "some text " + "here")) mail.add_content(Content("text/plain", "some text here")) self.assertEqual( @@ -165,7 +176,8 @@ def test_kitchenSink(self): personalization.add_cc(Email("test@example.com", "Example User")) personalization.add_bcc(Email("test@example.com")) personalization.add_bcc(Email("test@example.com")) - personalization.subject = "Hello World from the Personalized SendGrid Python Library" + personalization.subject = "Hello World from the Personalized " \ + "SendGrid Python Library" personalization.add_header(Header("X-Test", "test")) personalization.add_header(Header("X-Mock", "true")) personalization.add_substitution( @@ -183,7 +195,8 @@ def test_kitchenSink(self): personalization2.add_cc(Email("test@example.com", "Example User")) personalization2.add_bcc(Email("test@example.com")) personalization2.add_bcc(Email("test@example.com")) - personalization2.subject = "Hello World from the Personalized SendGrid Python Library" + personalization2.subject = "Hello World from the Personalized " \ + "SendGrid Python Library" personalization2.add_header(Header("X-Test", "test")) personalization2.add_header(Header("X-Mock", "true")) personalization2.add_substitution( @@ -201,7 +214,8 @@ def test_kitchenSink(self): "some text here")) attachment = Attachment() - attachment.content = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12" + attachment.content = "TG9yZW0gaXBzdW0gZG9sb3Igc2l0IGFtZXQsIGNvbnNlY" \ + "3RldHVyIGFkaXBpc2NpbmcgZWxpdC4gQ3JhcyBwdW12" attachment.type = "application/pdf" attachment.filename = "balance_001.pdf" attachment.disposition = "attachment" @@ -264,12 +278,15 @@ def test_kitchenSink(self): True, True) tracking_settings.open_tracking = OpenTracking( True, - "Optional tag to replace with the open image in the body of the message") + "Optional tag to replace with the open image in the body " + "of the message") tracking_settings.subscription_tracking = SubscriptionTracking( True, "text to insert into the text/plain portion of the message", - "html to insert into the text/html portion of the message", - "Optional tag to replace with the open image in the body of the message") + "html to insert into the text/html portion of " + "the message", + "Optional tag to replace with the open image in the body of " + "the message") tracking_settings.ganalytics = Ganalytics( True, "some source", @@ -500,7 +517,8 @@ def test_unicode_values_in_substitutions_helper(self): mail.from_email = Email("test@example.com") - mail.subject = "Testing unicode substitutions with the SendGrid Python Library" + mail.subject = "Testing unicode substitutions with the SendGrid " \ + "Python Library" personalization = Personalization() personalization.add_to(Email("test@example.com")) @@ -539,7 +557,8 @@ def test_unicode_values_in_substitutions_helper(self): ] } ], - "subject": "Testing unicode substitutions with the SendGrid Python Library", + "subject": "Testing unicode substitutions with the " + "SendGrid Python Library", } self.assertEqual( @@ -562,3 +581,26 @@ def test_disable_tracking(self): def test_directly_setting_substitutions(self): personalization = Personalization() personalization.substitutions = [{'a': 0}] + + def test_dynamic_template_data(self): + p = Personalization() + p.add_to(Email('test@sendgrid.com')) + p.dynamic_template_data = { + 'customer': { + 'name': 'Bob', + 'returning': True + }, + 'total': 42 + } + + expected = { + 'to': [{'email': 'test@sendgrid.com'}], + 'dynamic_template_data': { + 'customer': { + 'name': 'Bob', + 'returning': True + }, + 'total': 42 + } + } + self.assertDictEqual(p.get(), expected) diff --git a/test/test_project.py b/test/test_project.py index a762474ec..5e269ae43 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -5,19 +5,16 @@ except ImportError: import unittest + class ProjectTests(unittest.TestCase): # ./docker def test_docker_dir(self): - self.assertTrue(os.path.isdir("./docker")) - - # ./docker-test - def test_docker_test_dir(self): - self.assertTrue(os.path.isdir("./docker-test")) + self.assertTrue(os.path.isfile("./docker/Dockerfile")) - # # ./docker-compose.yml or ./docker/docker-compose.yml - # def test_docker_compose(self): - # self.assertTrue(os.path.isfile('docker-compose.yml')) + # ./docker-compose.yml or ./docker/docker-compose.yml + def test_docker_compose(self): + self.assertTrue(os.path.isfile('./docker/docker-compose.yml')) # ./.env_sample def test_env(self): @@ -75,5 +72,6 @@ def test_usage(self): def test_use_cases(self): self.assertTrue(os.path.isfile('./use_cases/README.md')) + if __name__ == '__main__': unittest.main() diff --git a/test/test_send.py b/test/test_send.py index 16d496b85..003214892 100644 --- a/test/test_send.py +++ b/test/test_send.py @@ -30,13 +30,27 @@ def test_send(self): x = send.Send(fake_url) x.test_payload(fake_url) - send.Client.assert_called_once_with(host=fake_url, request_headers={'User-Agent': 'SendGrid-Test', - 'Content-Type': 'multipart/form-data; boundary=xYzZY'}) + send.Client.assert_called_once_with( + host=fake_url, + request_headers={ + 'User-Agent': 'SendGrid-Test', + 'Content-Type': 'multipart/form-data; boundary=xYzZY' + } + ) def test_main_call(self): fake_url = 'https://fake_url' - with mock.patch('argparse.ArgumentParser.parse_args', return_value=argparse.Namespace(host=fake_url, data='test_file.txt')): + with mock.patch( + 'argparse.ArgumentParser.parse_args', + return_value=argparse.Namespace(host=fake_url, + data='test_file.txt') + ): send.main() - send.Client.assert_called_once_with(host=fake_url, request_headers={'User-Agent': 'SendGrid-Test', - 'Content-Type': 'multipart/form-data; boundary=xYzZY'}) + send.Client.assert_called_once_with( + host=fake_url, + request_headers={ + 'User-Agent': 'SendGrid-Test', + 'Content-Type': 'multipart/form-data; boundary=xYzZY' + } + ) diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index c545cbb2d..68193c9b4 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -25,18 +25,22 @@ def setUpClass(cls): cls.sg = sendgrid.SendGridAPIClient(host=host) cls.devnull = open(os.devnull, 'w') prism_cmd = None - + # try: # # check for prism in the PATH - # if subprocess.call('prism version'.split(), stdout=cls.devnull) == 0: + # if subprocess.call('prism version'.split(), + # stdout=cls.devnull) == 0: # prism_cmd = 'prism' # except OSError: # prism_cmd = None # if not prism_cmd: # # check for known prism locations - # for path in ('/usr/local/bin/prism', os.path.expanduser(os.path.join('~', 'bin', 'prism')), - # os.path.abspath(os.path.join(os.getcwd(), 'prism', 'bin', 'prism'))): + # for path in ('/usr/local/bin/prism', + # os.path.expanduser(os.path.join('~', 'bin', + # 'prism')), + # os.path.abspath(os.path.join(os.getcwd(), 'prism', + # 'bin', 'prism'))): # prism_cmd = path if os.path.isfile(path) else None # if prism_cmd: # break @@ -45,10 +49,14 @@ def setUpClass(cls): # if sys.platform != 'win32': # # try to install with prism.sh # try: - # print("Warning: no prism detected, I will try to install it locally") - # prism_sh = os.path.abspath(os.path.join(cls.path, 'test', 'prism.sh')) + # print("Warning: no prism detected, I will try to install" + # " it locally") + # prism_sh = os.path.abspath(os.path.join(cls.path, 'test', + # 'prism.sh')) # if subprocess.call(prism_sh) == 0: - # prism_cmd = os.path.expanduser(os.path.join('~', 'bin', 'prism')) + # prism_cmd = os.path.expanduser(os.path.join('~', + # 'bin', + # 'prism')) # else: # raise RuntimeError() # except Exception as e: @@ -68,7 +76,8 @@ def setUpClass(cls): # cls.p = subprocess.Popen([ # prism_cmd, "run", "-s", # "https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/" - # "oai_stoplight.json"], stdout=cls.devnull, stderr=subprocess.STDOUT) + # "oai_stoplight.json"], stdout=cls.devnull, + # stderr=subprocess.STDOUT) # time.sleep(15) # print("Prism Started") @@ -134,7 +143,7 @@ def test_reset_request_headers(self): self.assertNotIn('blah', self.sg.client.request_headers) self.assertNotIn('blah2x', self.sg.client.request_headers) - for k,v in self.sg._default_headers.items(): + for k, v in self.sg._default_headers.items(): self.assertEqual(v, self.sg.client.request_headers[k]) def test_hello_world(self): @@ -144,8 +153,14 @@ def test_hello_world(self): content = Content( "text/plain", "and easy to do anywhere, even with Python") mail = Mail(from_email, subject, to_email, content) - self.assertTrue(mail.get() == {'content': [{'type': 'text/plain', 'value': 'and easy to do anywhere, even with Python'}], 'personalizations': [ - {'to': [{'email': 'test@example.com'}]}], 'from': {'email': 'test@example.com'}, 'subject': 'Sending with SendGrid is Fun'}) + self.assertTrue(mail.get() == {'content': + [{'type': 'text/plain', + 'value': 'and easy to do anywhere, ' + 'even with Python'}], + 'personalizations': [{'to': [{'email': + 'test@example.com'}]}], + 'from': {'email': 'test@example.com'}, + 'subject': 'Sending with SendGrid is Fun'}) def test_access_settings_activity_get(self): params = {'limit': 1} @@ -2373,7 +2388,8 @@ def test_license_year(self): LICENSE_FILE = 'LICENSE.txt' with open(LICENSE_FILE, 'r') as f: copyright_line = f.readline().rstrip() - self.assertEqual('Copyright (c) 2012-%s SendGrid, Inc.' % datetime.datetime.now().year, copyright_line) + self.assertEqual('Copyright (c) 2012-%s SendGrid, Inc.' % + datetime.datetime.now().year, copyright_line) # @classmethod # def tearDownClass(cls): diff --git a/test/test_stats.py b/test/test_stats.py new file mode 100644 index 000000000..c1bb88d8b --- /dev/null +++ b/test/test_stats.py @@ -0,0 +1,88 @@ +# -*- coding: utf-8 -*- +import json +from sendgrid.helpers.stats import * + +try: + import unittest2 as unittest +except ImportError: + import unittest + + +class UnitTests(unittest.TestCase): + + def test_basicStats(self): + + """Minimum required for stats""" + global_stats = Stats(start_date='12-09-2017') + + self.assertEqual( + json.dumps( + global_stats.get(), + sort_keys=True), + '{"start_date": "12-09-2017"}' + ) + + self.assertTrue(isinstance(str(global_stats), str)) + + def test_Stats(self): + + all_stats = Stats(start_date='12-09-2017') + all_stats.end_date = '12-10-2017' + all_stats.aggregated_by = 'day' + all_stats._sort_by_direction = 'asc' + all_stats.sort_by_metric = 'clicks' + all_stats._limit = 100 + all_stats._offset = 2 + + self.assertEqual( + json.dumps( + all_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "end_date": "12-10-2017", ' + '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' + '"sort_by_metric": "clicks", "start_date": "12-09-2017"}' + ) + + def test_categoryStats(self): + + category_stats = CategoryStats(start_date='12-09-2017', + categories=['foo', 'bar']) + category_stats.add_category(Category('woo')) + category_stats.end_date = '12-10-2017' + category_stats.aggregated_by = 'day' + category_stats._sort_by_direction = 'asc' + category_stats.sort_by_metric = 'clicks' + category_stats._limit = 100 + category_stats._offset = 2 + + self.assertEqual( + json.dumps( + category_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "categories": ["foo", "bar", "woo"], ' + '"end_date": "12-10-2017", "limit": 100, "offset": 2, ' + '"sort_by_direction": "asc", "sort_by_metric": "clicks", ' + '"start_date": "12-09-2017"}' + ) + + def test_subuserStats(self): + + subuser_stats = SubuserStats(start_date='12-09-2017', + subusers=['foo', 'bar']) + subuser_stats.add_subuser(Subuser('blah')) + subuser_stats.end_date = '12-10-2017' + subuser_stats.aggregated_by = 'day' + subuser_stats._sort_by_direction = 'asc' + subuser_stats.sort_by_metric = 'clicks' + subuser_stats._limit = 100 + subuser_stats._offset = 2 + + self.assertEqual( + json.dumps( + subuser_stats.get(), + sort_keys=True), + '{"aggregated_by": "day", "end_date": "12-10-2017", ' + '"limit": 100, "offset": 2, "sort_by_direction": "asc", ' + '"sort_by_metric": "clicks", "start_date": "12-09-2017", ' + '"subusers": ["foo", "bar", "blah"]}' + ) diff --git a/test/test_unassigned.py b/test/test_unassigned.py new file mode 100644 index 000000000..80ff9bb1e --- /dev/null +++ b/test/test_unassigned.py @@ -0,0 +1,97 @@ +import json +import pytest + +from sendgrid.helpers.endpoints.ip.unassigned import unassigned + + +ret_json = '''[ { + "ip": "167.89.21.3", + "pools": [ + "pool1", + "pool2" + ], + "whitelabeled": false, + "start_date": 1409616000, + "subusers": [ + "tim@sendgrid.net" + ], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "192.168.1.1", + "pools": [ + "pool1", + "pool2" + ], + "whitelabeled": false, + "start_date": 1409616000, + "subusers": [ + "tim@sendgrid.net" + ], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "208.115.214.22", + "pools": [], + "whitelabeled": true, + "rdns": "o1.email.burgermail.com", + "start_date": 1409616000, + "subusers": [], + "warmup": false, + "assigned_at": 1482883200 + }, + { + "ip": "208.115.214.23", + "pools": [], + "whitelabeled": true, + "rdns": "o1.email.burgermail.com", + "start_date": 1409616000, + "subusers": [], + "warmup": false, + "assigned_at": 1482883200 + + } ] + ''' + + +def get_all_ip(): + ret_val = json.loads(ret_json) + return ret_val + + +def make_data(): + data = set() + data.add("208.115.214.23") + data.add("208.115.214.22") + return data + + +def test_unassigned_ip_json(): + + data = make_data() + + as_json = True + calculated = unassigned(get_all_ip(), as_json=as_json) + calculated = json.loads(calculated) + + for item in calculated: + assert item["ip"] in data + + +def test_unassigned_ip_obj(): + + data = make_data() + + as_json = False + calculated = unassigned(get_all_ip(), as_json=as_json) + + for item in calculated: + assert item["ip"] in data + + +def test_unassigned_baddata(): + as_json = False + calculated = unassigned(dict(), as_json=as_json) + assert calculated == [] diff --git a/use_cases/README.md b/use_cases/README.md index 188464d09..9966616e5 100644 --- a/use_cases/README.md +++ b/use_cases/README.md @@ -8,6 +8,7 @@ This directory provides examples for specific use cases of this library. Please * [How to Create a Django app, Deployed on Heroku, to Send Email with SendGrid](django.md) * [How to Deploy A Simple Hello Email App on AWS](aws.md) +* [How to Deploy a simple Flask app, to send Email with SendGrid, on Heroku](flask_heroku.md) * [How to Setup a Domain Whitelabel](domain_whitelabel.md) * [How to View Email Statistics](email_stats.md) @@ -15,6 +16,7 @@ This directory provides examples for specific use cases of this library. Please * [Asynchronous Mail Send](asynchronous_mail_send.md) * [Attachment](attachment.md) * [Transactional Templates](transational_templates.md) +* [Integrate with Slack Events API](slack_event_api_integration.md) ### Library Features * [Error Handling](error_handling.md) \ No newline at end of file diff --git a/use_cases/aws.md b/use_cases/aws.md index 2ff04bd1f..d07d5769c 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -13,7 +13,7 @@ Python 2.6, 2.7, 3.4, or 3.5 are supported by the sendgrid Python library, howev Before starting this tutorial, you will need to have access to an AWS account in which you are allowed to provision resources. This tutorial also assumes you've already created a SendGrid account with free-tier access. Finally, it is highly recommended you utilize [virtualenv](https://virtualenv.pypa.io/en/stable/). -*DISCLAIMER*: Any resources provisioned here may result in charges being incurred to your account. Sendgrid is in no way responsible for any billing charges. +*DISCLAIMER*: Any resources provisioned here may result in charges being incurred to your account. SendGrid is in no way responsible for any billing charges. ## Getting Started @@ -36,15 +36,15 @@ On the next menu, you have the option to choose what programming language you'll Follow the steps on the next screen. Choose a name for your API key, such as "hello-email". Follow the remaining steps to create an environment variable, install the sendgrid module, and copy the test code. Once that is complete, check the "I've integrated the code above" box, and click the "Next: Verify Integration" button. -Assuming all the steps were completed correctly, you should be greeted with a success message. If not, go back and verify that everything is correct, including your API key environment varible, and Python code. +Assuming all the steps were completed correctly, you should be greeted with a success message. If not, go back and verify that everything is correct, including your API key environment variable, and Python code. ## Deploy hello-world app using CodeStar -For the rest of the tutorial, we'll be working out of the git repository we cloned from AWS earlier: +For the rest of the tutorial, we'll be working out of the Git repository we cloned from AWS earlier: ``` $ cd hello-email ``` -note: this assumes you cloned the git repo inside your current directory. My directory is: +note: this assumes you cloned the Git repo inside your current directory. My directory is: ``` ~/projects/hello-email @@ -139,7 +139,7 @@ def handler(event, context): 'headers': {'Content-Type': 'application/json'}} ``` -Note that for the most part, we've simply copied the intial code from the API verification with SendGrid. Some slight modifications were needed to allow it to run as a lambda function, and for the output to be passed cleanly from the API endpoint. +Note that for the most part, we've simply copied the initial code from the API verification with SendGrid. Some slight modifications were needed to allow it to run as a lambda function, and for the output to be passed cleanly from the API endpoint. Change the `test@example.com` emails appropriately so that you may receive the test email. diff --git a/use_cases/flask_heroku.md b/use_cases/flask_heroku.md new file mode 100644 index 000000000..ef0fa599a --- /dev/null +++ b/use_cases/flask_heroku.md @@ -0,0 +1,9 @@ +# Create a Flask app to send email with SendGrid + +This tutorial explains how to deploy a simple Flask app, to send an email using the SendGrid Python SDK, on Heroku. + +1. Create a SendGrid API key at https://app.sendgrid.com/settings/api_keys +1. Go to https://github.com/swapagarwal/sendgrid-flask-heroku +1. Click on `Deploy to Heroku` button, and follow the instructions. + +That's all. You'll be sending your first email within seconds! diff --git a/use_cases/slack_event_api_integration.md b/use_cases/slack_event_api_integration.md new file mode 100644 index 000000000..ecce40695 --- /dev/null +++ b/use_cases/slack_event_api_integration.md @@ -0,0 +1,46 @@ +# Integrate with Slack Events API + +It's fairly straightforward to integrate SendGrid with Slack, to allow emails to be triggered by events happening on Slack. + +For this, we make use of the [Official Slack Events API](https://github.com/slackapi/python-slack-events-api), which can be installed using pip. + +To allow our application to get notifications of slack events, we first create a Slack App with Event Subscriptions as described [here](https://github.com/slackapi/python-slack-events-api#--development-workflow) + +Then, we set `SENDGRID_API_KEY` _(which you can create on the SendGrid dashboard)_ and `SLACK_VERIFICATION_TOKEN` _(which you can get in the App Credentials section of the Slack App)_ as environment variables. + +Once this is done, we can subscribe to [events on Slack](https://api.slack.com/events) and trigger emails when an event occurs. In the example below, we trigger an email to `test@example.com` whenever someone posts a message on Slack that has the word "_help_" in it. + +``` +from slackeventsapi import SlackEventAdapter +from slackclient import SlackClient +import os +import sendgrid +from sendgrid.helpers.mail import * + +sg = sendgrid.SendGridAPIClient(apikey=os.environ.get('SENDGRID_API_KEY')) + +SLACK_VERIFICATION_TOKEN = os.environ["SLACK_VERIFICATION_TOKEN"] +slack_events_adapter = SlackEventAdapter(SLACK_VERIFICATION_TOKEN, "/slack/events") + +@slack_events_adapter.on("message") +def handle_message(event_data): + message = event_data["event"] + # If the incoming message contains "help", then send an email using SendGrid + if message.get("subtype") is None and "help" in message.get('text').lower(): + message = "Someone needs your help: \n\n %s" % message["text"] + r = send_email(message) + print(r) + + +def send_email(message): + from_email = Email("slack_integration@example.com") + to_email = Email("test@example.com") + subject = "Psst... Someone needs help!" + content = Content("text/plain", message) + mail = Mail(from_email, subject, to_email, content) + response = sg.client.mail.send.post(request_body=mail.get()) + return response.status_code + +# Start the slack event listener server on port 3000 +slack_events_adapter.start(port=3000) +``` \ No newline at end of file diff --git a/use_cases/transational_templates.md b/use_cases/transational_templates.md index d3e3a005d..491d528bd 100644 --- a/use_cases/transational_templates.md +++ b/use_cases/transational_templates.md @@ -1,6 +1,71 @@ -# Transactional Templates +### Transactional Templates -For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/index.html). Following is the template content we used for testing. +SendGrid transactional templates let you leverage power of [handlebars](https://handlebarsjs.com/) +syntax to easily manage complex dynamic content in transactional emails. + +For this example, we assume you have created a [transactional template](https://sendgrid.com/docs/User_Guide/Transactional_Templates/create_and_edit_transactional_templates.html). Following is the template content we used for testing. + +This example also assumes you [set your environment variable](https://github.com/sendgrid/sendgrid-python/blob/master/TROUBLESHOOTING.md#environment-variables-and-your-sendgrid-api-key) with your SendGrid API Key. + +Template ID (replace with your own): + +```text +d-13b8f94fbcae4ec6b75270d6cb59f932 +``` + +Email Subject: + +```text +{{ subject }} +``` + +Template Body: + +```html + + +