pax_global_header00006660000000000000000000000064134136557220014522gustar00rootroot0000000000000052 comment=32d6092e236ea3df63a9f9e41981b7e41cd8ffe2 python-sparkpost-1.3.7/000077500000000000000000000000001341365572200150775ustar00rootroot00000000000000python-sparkpost-1.3.7/.editorconfig000066400000000000000000000002331341365572200175520ustar00rootroot00000000000000root = true [*] end_of_line = lf charset = utf-8 trim_trailing_whitespace = true insert_final_newline = true [*.py] indent_style = space indent_size = 4 python-sparkpost-1.3.7/.gitignore000066400000000000000000000013531341365572200170710ustar00rootroot00000000000000# Byte-compiled / optimized / DLL files __pycache__/ *.py[cod] # C extensions *.so # Distribution / packaging .Python env/ build/ develop-eggs/ dist/ downloads/ eggs/ lib/ lib64/ parts/ sdist/ var/ *.egg-info/ .installed.cfg *.egg MANIFEST # PyInstaller # Usually these files are written by a python script from a template # before PyInstaller builds the exe, so as to inject date/other infos into it. *.manifest *.spec # Installer logs pip-log.txt pip-delete-this-directory.txt # Unit test / coverage reports htmlcov/ .tox/ .coverage .cache nosetests.xml coverage.xml # Translations *.mo *.pot # Django stuff: *.log # Sphinx documentation docs/_build/ # PyBuilder target/ # virtualenv venv/ # autoenv .env # Mac OSX .DS_Store .idea python-sparkpost-1.3.7/.travis.yml000066400000000000000000000013561341365572200172150ustar00rootroot00000000000000language: python python: - "2.7" - "3.4" - "3.5" env: - DJANGO_VERSION='>=1.7,<1.8' - DJANGO_VERSION='>=1.8,<1.9' - DJANGO_VERSION='>=1.9,<1.10' matrix: exclude: - python: "3.5" env: DJANGO_VERSION='>=1.7,<1.8' install: - pip install -r dev-requirements.txt - pip uninstall django --yes - pip install -q django$DJANGO_VERSION - pip install coveralls - pip install -e . before_script: - flake8 sparkpost test script: - py.test --cov sparkpost test/ --cov-report term-missing after_success: - coveralls notifications: slack: secure: VNIwgvrbcwj0b2gfYSOeTyK7rkV62/belwhYthasHmN+DoTsEJF4+HFVtzOykS72LM/f5Id5zieWtxYG+soy+yEOc1iZizXRpRJORtTYfZJB9RCffavosl322BcpoTX99cGyiZjWjOFH70UWlFMB3zT0jS+9icuTfk7ZqBX/zDA= python-sparkpost-1.3.7/AUTHORS.rst000066400000000000000000000021441341365572200167570ustar00rootroot00000000000000Maintainers ----------------- - Jose Zamora `@jgzamora `_ Contributors ----------------------- - Bob Evans `@bizob2828 `_ - Aydrian Howard `@aydrian `_ - Rich Leland `@richleland `_ - Artur Felipe Sousa `@arturfelipe `_ - Barthelemy Dagenais `@bartdag `_ - Dmitry Tyukin `@deems `_ - Jared Morse `@jarcoal `_ - Marko Mrdjenovic `@friedcell `_ - Mohammad Hossain `@rajumsys `_ - Simeon Visser `@svisser `_ - Zdeněk Softič `@btx `_ - `@amatissart `_ - `@gnarvaja `_ - `@pegler `_ - `@puttu `_ - Janusz Skonieczny `@wooyek `_ - Richard Dawe `@richdawe77 `_ - ADD YOURSELF HERE (and link to your github page) python-sparkpost-1.3.7/CHANGELOG.md000066400000000000000000000165171341365572200167220ustar00rootroot00000000000000# Change Log All notable changes to this project will be documented in this file. This project adheres to [Semantic Versioning](http://semver.org/). ## Unreleased - [Compare to latest release][unreleased] ## [1.3.6] - 2018-03-23 ### Added - [#160](https://github.com/SparkPost/python-sparkpost/pull/160) Extra header support for Django messages ## [1.3.5] - 2017-03-07 ### Added - [#141](https://github.com/SparkPost/python-sparkpost/pull/141) Validation for recipients parameter for transmissions - [#142](https://github.com/SparkPost/python-sparkpost/pull/142) URI parameter support for transmissions list endpoint. Also added deprecation warning ## [1.3.4] - 2017-02-16 ### Added - Examples for exception handling and using the base resource class ### Fixed - [#137](https://github.com/SparkPost/python-sparkpost/pull/137) Added missing `campaign` support for Django backend ## [1.3.3] - 2017-01-13 ### Fixed - [#135](https://github.com/SparkPost/python-sparkpost/pull/135) Issue where exceptions were not returning properly for some underlying API errors ## [1.3.2] - 2016-11-14 ### Fixed - [#129](https://github.com/SparkPost/python-sparkpost/pull/129) Reverted change for emojis in the body of a message, needs further investigation - [#129](https://github.com/SparkPost/python-sparkpost/pull/129) `substitution_data`, `metadata`, and `tags` are now supplied properly for cc/bcc recipients ## [1.3.1] - 2016-11-13 ### Added - Instructions for use with Google Cloud ### Fixed - [#114](https://github.com/SparkPost/python-sparkpost/pull/114) Issue where emojis in the body of a message were being forced to ASCII - [#118](https://github.com/SparkPost/python-sparkpost/pull/118) Fixed improper setting of header_to value when using cc and primary recipient has substitution data - [#119](https://github.com/SparkPost/python-sparkpost/pull/119) Added missing `sources` to suppression list key map ## [1.3.0] - 2016-10-01 ### Added - [#121](https://github.com/SparkPost/python-sparkpost/pull/121) Added extended error code to `SparkPostAPIException` class - [#124](https://github.com/SparkPost/python-sparkpost/pull/124) Added `delete` method to `Transmission` class - CI tests now also run against Python 3.5 ### Changed - [#123](https://github.com/SparkPost/python-sparkpost/pull/123) Updated RequestsTransport to use a requests session so HTTP Keep Alive is honored ### Fixed - [#115](https://github.com/SparkPost/python-sparkpost/pull/115) Guess attachment mimetype in Django email backend if not provided ## [1.2.0] - 2016-04-19 ### Added - [#109](https://github.com/SparkPost/python-sparkpost/pull/109) Support for specifying `template`, `substitution_data` when using the Django email backend - [#100](https://github.com/SparkPost/python-sparkpost/pull/100) Support for the `content_subtype` attribute when using the `EmailMessage` class in Django ## [1.1.1] - 2016-04-08 ### Fixed - [#99](https://github.com/SparkPost/python-sparkpost/pull/99) Issue where inline images were always passed to the API, which in turn required HTML content ## [1.1.0] - 2016-03-30 ### Added - [#94](https://github.com/SparkPost/python-sparkpost/pull/94) Better extensibility with support for Tornado - [#95](https://github.com/SparkPost/python-sparkpost/pull/95) Support for CSS inlining - [#98](https://github.com/SparkPost/python-sparkpost/pull/98) Support for inline images - [#98](https://github.com/SparkPost/python-sparkpost/pull/98) Support for specifying IP pool ### Fixed - [#97](https://github.com/SparkPost/python-sparkpost/pull/97) Issue where substitution data was being improperly passed to the templates preview endpoint - [#91](https://github.com/SparkPost/python-sparkpost/pull/91) Issue where Django backend was not properly base64 encoding attachments ## [1.0.5] - 2016-03-18 ### Fixed - [#89](https://github.com/SparkPost/python-sparkpost/pull/89) Issue where global Django settings object was being modified, causing mixed emails ## [1.0.4] - 2016-03-10 ### Added - `SPARKPOST_OPTIONS` setting for Django for passing through additional transmission options like `track_opens`, `track_clicks`, and `transactional` - Support for cc, bcc, reply to, and attachments for Django ### Changed - Refactored some of the bits in the Django email backend out into a `SparkPostMessage` class which prepares parameters for calls to the `Transmissions.send` method ## [1.0.3] - 2016-03-03 ### Added - Tox for local testing - Allow unicode recipients - Automatically parse emails with friendly from e.g. `Friendly Name ` - Support for cc/bcc ## [1.0.2] - 2016-02-25 ### Added - Support for attachments via the `attachments` parameter in `Transmissions` ## [1.0.1] - 2016-02-25 ### Fixed - Subpackages now get included properly - Updated examples to use plural `transmissions` ## [1.0.0] - 2015-11-06 ### Added - Django email backend - Support for scheduled sending via the `start_time` parameter in `Transmissions` - Support for marking messages as transactional or non-transactional via the `transactional` parameter in `Transmissions` - Support for skipping suppression (SparkPost Elite only) via the `skip_suppression` parameter in `Transmissions` ## [1.0.0.dev2] - 2015-09-01 ### Added - Code coverage via [coveralls] - CONTRIBUTING file for notes on how to contribute - `Templates` class to manage templates - `RecipientLists` class to manage recipients we want to send to - `SuppressionLists` class to manage recipients that are suppressed ### Changed - Renamed `Transmission` class to `Transmissions` (backwards compatible) ### Removed - Tox file for running tests in favor of `make test` and Travis CI ### Fixed - Engagement tracking no longer automatically enabled for all transmissions - Documentation generation issues ## 1.0.0.dev1 - 2014-02-09 ### Added - Base SparkPost class - `Transmission` class for sending messages - Examples for Transmission usage - Metrics class for getting a list of campaigns and domains - Docs on readthedocs.org [unreleased]: https://github.com/sparkpost/python-sparkpost/compare/v1.3.6...HEAD [1.3.6]: https://github.com/sparkpost/python-sparkpost/compare/v1.3.5...v1.3.6 [1.3.5]: https://github.com/sparkpost/python-sparkpost/compare/v1.3.4...v1.3.5 [1.3.4]: https://github.com/sparkpost/python-sparkpost/compare/v1.3.3...v1.3.4 [1.3.3]: https://github.com/sparkpost/python-sparkpost/compare/v1.3.2...v1.3.3 [1.3.2]: https://github.com/sparkpost/python-sparkpost/compare/v1.3.1...v1.3.2 [1.3.1]: https://github.com/sparkpost/python-sparkpost/compare/v1.3.0...v1.3.1 [1.3.0]: https://github.com/sparkpost/python-sparkpost/compare/v1.2.0...v1.3.0 [1.2.0]: https://github.com/sparkpost/python-sparkpost/compare/v1.1.1...v1.2.0 [1.1.1]: https://github.com/sparkpost/python-sparkpost/compare/v1.1.0...v1.1.1 [1.1.0]: https://github.com/sparkpost/python-sparkpost/compare/v1.0.5...v1.1.0 [1.0.5]: https://github.com/sparkpost/python-sparkpost/compare/v1.0.4...v1.0.5 [1.0.4]: https://github.com/sparkpost/python-sparkpost/compare/v1.0.3...v1.0.4 [1.0.3]: https://github.com/sparkpost/python-sparkpost/compare/v1.0.2...v1.0.3 [1.0.2]: https://github.com/sparkpost/python-sparkpost/compare/v1.0.1...v1.0.2 [1.0.1]: https://github.com/sparkpost/python-sparkpost/compare/v1.0.0...v1.0.1 [1.0.0]: https://github.com/sparkpost/python-sparkpost/compare/1.0.0.dev2...v1.0.0 [1.0.0.dev2]: https://github.com/sparkpost/python-sparkpost/compare/1.0.0.dev1...1.0.0.dev2 [coveralls]: https://coveralls.io/github/SparkPost/python-sparkpost python-sparkpost-1.3.7/CODE_OF_CONDUCT.md000066400000000000000000000062251341365572200177030ustar00rootroot00000000000000# Contributor Covenant Code of Conduct ## Our Pledge In the interest of fostering an open and welcoming environment, we as contributors and maintainers pledge to making participation in our project and our community a harassment-free experience for everyone, regardless of age, body size, disability, ethnicity, gender identity and expression, level of experience, nationality, personal appearance, race, religion, or sexual identity and orientation. ## Our Standards Examples of behavior that contributes to creating a positive environment include: * Using welcoming and inclusive language * Being respectful of differing viewpoints and experiences * Gracefully accepting constructive criticism * Focusing on what is best for the community * Showing empathy towards other community members Examples of unacceptable behavior by participants include: * The use of sexualized language or imagery and unwelcome sexual attention or advances * Trolling, insulting/derogatory comments, and personal or political attacks * Public or private harassment * Publishing others' private information, such as a physical or electronic address, without explicit permission * Other conduct which could reasonably be considered inappropriate in a professional setting ## Our Responsibilities Project maintainers are responsible for clarifying the standards of acceptable behavior and are expected to take appropriate and fair corrective action in response to any instances of unacceptable behavior. Project maintainers have the right and responsibility to remove, edit, or reject comments, commits, code, wiki edits, issues, and other contributions that are not aligned to this Code of Conduct, or to ban temporarily or permanently any contributor for other behaviors that they deem inappropriate, threatening, offensive, or harmful. ## Scope This Code of Conduct applies both within project spaces and in public spaces when an individual is representing the project or its community. Examples of representing a project or community include using an official project e-mail address, posting via an official social media account, or acting as an appointed representative at an online or offline event. Representation of a project may be further defined and clarified by project maintainers. ## Enforcement Instances of abusive, harassing, or otherwise unacceptable behavior may be reported by contacting the project team at developers@sparkpost.com. The project team will review and investigate all complaints, and will respond in a way that it deems appropriate to the circumstances. The project team is obligated to maintain confidentiality with regard to the reporter of an incident. Further details of specific enforcement policies may be posted separately. Project maintainers who do not follow or enforce the Code of Conduct in good faith may face temporary or permanent repercussions as determined by other members of the project's leadership. ## Attribution This Code of Conduct is adapted from the [Contributor Covenant][homepage], version 1.4, available at [http://contributor-covenant.org/version/1/4][version] [homepage]: http://contributor-covenant.org [version]: http://contributor-covenant.org/version/1/4/ python-sparkpost-1.3.7/CONTRIBUTING.md000066400000000000000000000055171341365572200173400ustar00rootroot00000000000000# Contributing to python-sparkpost Transparency is one of our core values, and we encourage developers to contribute and become part of the SparkPost developer community. The following is a set of guidelines for contributing to python-sparkpost, which is hosted in the [SparkPost Organization](https://github.com/sparkpost) on GitHub. These are just guidelines, not rules, use your best judgment and feel free to propose changes to this document in a pull request. ## Submitting Issues * You can create an issue [here](https://github.com/sparkpost/python-sparkpost/issues/new), but before doing that please read the notes below on debugging and submitting issues, and include as many details as possible with your report. * Include the version of python-sparkpost you are using. * Perform a [cursory search](https://github.com/SparkPost/python-sparkpost/issues?q=is%3Aissue+is%3Aopen) to see if a similar issue has already been submitted. ## Local development * Fork this repository * Clone your fork * Install virtualenv: ``pip install virtualenv`` * Run ``make install`` * Run ``source venv/bin/activate`` * Write code! ## Contribution Steps ### Guidelines - Provide documentation for any newly added code. - Provide tests for any newly added code. - Follow PEP8. 1. Create a new branch named after the issue you’ll be fixing (include the issue number as the branch name, example: Issue in GH is #8 then the branch name should be ISSUE-8)) 2. Write corresponding tests and code (only what is needed to satisfy the issue and tests please) * Include your tests in the 'test' directory in an appropriate test file * Write code to satisfy the tests 3. Ensure automated tests pass 4. Submit a new Pull Request applying your feature/fix branch to the develop branch ## Testing Once you are set up for local development: * Run ``make test`` to test against your current Python environment * Open htmlcov/index.html to view coverage information ### Testing all version combinations You can also test all the supported Python and dependencies versions with tox: 1. Install tox: ``pip install tox`` 2. Run tox: ``tox`` If you do not have Python 2.7, 3.4, and 3.5, you can install them with pyenv: 1. Install [pyenv](https://github.com/yyuu/pyenv) 2. Install the required versions of Python: 1. ``pyenv install 2.7.11`` 2. ``pyenv install 3.4.4`` 3. ``pyenv install 3.5.1`` 3. Set the global versions: ``pyenv global 2.7.11 3.4.4 3.5.1`` 4. Run tox: ``tox`` ## Releasing To put python-sparkpost on PyPI * Ensure you have maintainer privileges in PyPI * Update your ``~/.pypirc`` if necessary to contain your username and password (hint: you can run ``python setup.py register``) * Run ``make release``, which will create the dists and upload them to PyPI * Confirm you are able to successfully install the new version by running ``pip install sparkpost`` python-sparkpost-1.3.7/LICENSE000066400000000000000000000010711341365572200161030ustar00rootroot00000000000000Copyright 2015 Message Systems, Inc. or its affiliates. All Rights Reserved. Licensed under the Apache License, Version 2.0 (the "License"). You may not use this software except in compliance with the License. A copy of the License is located at http://www.apache.org/licenses/LICENSE-2.0.html or in the "license" file accompanying this software. This file is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. python-sparkpost-1.3.7/MANIFEST.in000066400000000000000000000000471341365572200166360ustar00rootroot00000000000000include AUTHORS.rst README.rst LICENSE python-sparkpost-1.3.7/Makefile000066400000000000000000000017341341365572200165440ustar00rootroot00000000000000.PHONY: analysis all build clean docs docs-install docs-open install release release-test test venv all: clean venv install venv: virtualenv venv install: venv . venv/bin/activate; pip install -r dev-requirements.txt . venv/bin/activate; pip install -e . analysis: . venv/bin/activate; flake8 sparkpost test test: analysis . venv/bin/activate; py.test --cov-report term-missing --cov-report html --cov sparkpost test/ docs-install: . venv/bin/activate; pip install -r docs/requirements.txt docs: . venv/bin/activate; cd docs && make html docs-open: docs . venv/bin/activate; open docs/_build/html/index.html release: install . venv/bin/activate; python setup.py sdist bdist_wheel; twine upload -r pypi dist/* release-test: install . venv/bin/activate; python setup.py sdist bdist_wheel; twine upload -r test dist/* build: install . venv/bin/activate; python setup.py sdist . venv/bin/activate; python setup.py bdist_wheel clean: rm -rf venv build dist *.egg-info python-sparkpost-1.3.7/README.rst000066400000000000000000000112571341365572200165740ustar00rootroot00000000000000.. image:: https://www.sparkpost.com/sites/default/files/attachments/SparkPost_Logo_2-Color_Gray-Orange_RGB.svg :target: https://www.sparkpost.com :width: 200px `Sign up`_ for a SparkPost account and visit our `Developer Hub`_ for even more content. .. _Sign up: https://app.sparkpost.com/join?plan=free-0817?src=Social%20Media&sfdcid=70160000000pqBb&pc=GitHubSignUp&utm_source=github&utm_medium=social-media&utm_campaign=github&utm_content=sign-up .. _Developer Hub: https://developers.sparkpost.com SparkPost Python API client =========================== .. image:: https://travis-ci.org/SparkPost/python-sparkpost.svg?branch=master :target: https://travis-ci.org/SparkPost/python-sparkpost :alt: Build Status .. image:: https://readthedocs.org/projects/python-sparkpost/badge/?version=latest :target: https://python-sparkpost.readthedocs.io/en/latest/ :alt: Documentation Status .. image:: https://coveralls.io/repos/SparkPost/python-sparkpost/badge.svg?branch=master&service=github :target: https://coveralls.io/github/SparkPost/python-sparkpost?branch=master :alt: Coverage Status The official Python package for using the SparkPost API. Documentation ------------- * Documentation for `python-sparkpost`_ * `SparkPost API Reference`_ .. _python-sparkpost: https://python-sparkpost.readthedocs.io/ .. _SparkPost API Reference: https://www.sparkpost.com/api Installation ------------ Install from PyPI using `pip`_: .. code-block:: bash $ pip install sparkpost .. _pip: http://www.pip-installer.org/en/latest/ .. _pip: http://www.pip-installer.org/en/latest/ Python 2.7 or later is required. Get a key --------- Go to `API & SMTP`_ in the SparkPost app and create an API key. We recommend using the ``SPARKPOST_API_KEY`` environment variable: .. code-block:: python from sparkpost import SparkPost sp = SparkPost() # uses environment variable Alternatively, you can pass the API key to the SparkPost class: .. code-block:: python from sparkpost import SparkPost sp = SparkPost('YOUR API KEY') For SparkPost EU and Enterprise accounts, pass in a second parameter to set the API host. .. code-block:: python from sparkpost import SparkPost sp = SparkPost('YOUR API KEY', 'https://api.eu.sparkpost.com') .. _API & SMTP: https://app.sparkpost.com/#/configuration/credentials Send a message -------------- Here at SparkPost, our messages are known as transmissions. Let's use the underlying `transmissions API`_ to send a friendly test message: .. code-block:: python from sparkpost import SparkPost sp = SparkPost() response = sp.transmissions.send( use_sandbox=True, recipients=['someone@somedomain.com'], html='

Hello world

', from_email='test@sparkpostbox.com', subject='Hello from python-sparkpost' ) print(response) # outputs {u'total_accepted_recipients': 1, u'id': u'47960765679942446', u'total_rejected_recipients': 0} .. _transmissions API: https://www.sparkpost.com/api#/reference/transmissions Django Integration ------------------ The SparkPost python library comes with an email backend for Django. Put the following configuration in `settings.py` file. .. code-block:: python SPARKPOST_API_KEY = 'API_KEY' EMAIL_BACKEND = 'sparkpost.django.email_backend.SparkPostEmailBackend' Replace *API_KEY* with an actual API key that you've generated in `Get a Key`_ section. Check out the `full documentation`_ on the Django email backend. .. _full documentation: https://python-sparkpost.readthedocs.io/en/latest/django/backend.html Using with Google Cloud ----------------------- There are a few simple modifications necessary to enable the use of the underlying ``requests`` library that python-sparkpost uses. First, add the ``requests`` and ``requests-toolbelt`` to your project's ``requirements.txt``: .. code-block:: requests requests-toolbelt Then create or update your ``appengine_config.py`` file to include the following: .. code-block:: python import requests import requests_toolbelt.adapters.appengine requests_toolbelt.adapters.appengine.monkeypatch() Then deploy your app and you should be able to send using python-sparkpost on Google Cloud. Contribute ---------- #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. #. Fork `the repository`_ on GitHub and make your changes in a branch on your fork #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request. Make sure to add yourself to AUTHORS_. .. _`the repository`: http://github.com/SparkPost/python-sparkpost .. _AUTHORS: https://github.com/SparkPost/python-sparkpost/blob/master/AUTHORS.rst python-sparkpost-1.3.7/dev-requirements.txt000066400000000000000000000001031341365572200211310ustar00rootroot00000000000000-r test-requirements.txt wheel twine Django>=1.7,<1.10 tornado>=3.2python-sparkpost-1.3.7/docs/000077500000000000000000000000001341365572200160275ustar00rootroot00000000000000python-sparkpost-1.3.7/docs/Makefile000066400000000000000000000152221341365572200174710ustar00rootroot00000000000000# Makefile for Sphinx documentation # # You can set these variables from the command line. SPHINXOPTS = SPHINXBUILD = sphinx-build PAPER = BUILDDIR = _build # User-friendly check for sphinx-build ifeq ($(shell which $(SPHINXBUILD) >/dev/null 2>&1; echo $$?), 1) $(error The '$(SPHINXBUILD)' command was not found. Make sure you have Sphinx installed, then set the SPHINXBUILD environment variable to point to the full path of the '$(SPHINXBUILD)' executable. Alternatively you can add the directory with the executable to your PATH. If you don't have Sphinx installed, grab it from http://sphinx-doc.org/) endif # Internal variables. PAPEROPT_a4 = -D latex_paper_size=a4 PAPEROPT_letter = -D latex_paper_size=letter ALLSPHINXOPTS = -d $(BUILDDIR)/doctrees $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . # the i18n builder cannot share the environment and doctrees with the others I18NSPHINXOPTS = $(PAPEROPT_$(PAPER)) $(SPHINXOPTS) . .PHONY: help clean html dirhtml singlehtml pickle json htmlhelp qthelp devhelp epub latex latexpdf text man changes linkcheck doctest gettext help: @echo "Please use \`make ' where is one of" @echo " html to make standalone HTML files" @echo " dirhtml to make HTML files named index.html in directories" @echo " singlehtml to make a single large HTML file" @echo " pickle to make pickle files" @echo " json to make JSON files" @echo " htmlhelp to make HTML files and a HTML help project" @echo " qthelp to make HTML files and a qthelp project" @echo " devhelp to make HTML files and a Devhelp project" @echo " epub to make an epub" @echo " latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter" @echo " latexpdf to make LaTeX files and run them through pdflatex" @echo " latexpdfja to make LaTeX files and run them through platex/dvipdfmx" @echo " text to make text files" @echo " man to make manual pages" @echo " texinfo to make Texinfo files" @echo " info to make Texinfo files and run them through makeinfo" @echo " gettext to make PO message catalogs" @echo " changes to make an overview of all changed/added/deprecated items" @echo " xml to make Docutils-native XML files" @echo " pseudoxml to make pseudoxml-XML files for display purposes" @echo " linkcheck to check all external links for integrity" @echo " doctest to run all doctests embedded in the documentation (if enabled)" clean: rm -rf $(BUILDDIR)/* html: $(SPHINXBUILD) -b html $(ALLSPHINXOPTS) $(BUILDDIR)/html @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/html." dirhtml: $(SPHINXBUILD) -b dirhtml $(ALLSPHINXOPTS) $(BUILDDIR)/dirhtml @echo @echo "Build finished. The HTML pages are in $(BUILDDIR)/dirhtml." singlehtml: $(SPHINXBUILD) -b singlehtml $(ALLSPHINXOPTS) $(BUILDDIR)/singlehtml @echo @echo "Build finished. The HTML page is in $(BUILDDIR)/singlehtml." pickle: $(SPHINXBUILD) -b pickle $(ALLSPHINXOPTS) $(BUILDDIR)/pickle @echo @echo "Build finished; now you can process the pickle files." json: $(SPHINXBUILD) -b json $(ALLSPHINXOPTS) $(BUILDDIR)/json @echo @echo "Build finished; now you can process the JSON files." htmlhelp: $(SPHINXBUILD) -b htmlhelp $(ALLSPHINXOPTS) $(BUILDDIR)/htmlhelp @echo @echo "Build finished; now you can run HTML Help Workshop with the" \ ".hhp project file in $(BUILDDIR)/htmlhelp." qthelp: $(SPHINXBUILD) -b qthelp $(ALLSPHINXOPTS) $(BUILDDIR)/qthelp @echo @echo "Build finished; now you can run "qcollectiongenerator" with the" \ ".qhcp project file in $(BUILDDIR)/qthelp, like this:" @echo "# qcollectiongenerator $(BUILDDIR)/qthelp/python-sparkpost.qhcp" @echo "To view the help file:" @echo "# assistant -collectionFile $(BUILDDIR)/qthelp/python-sparkpost.qhc" devhelp: $(SPHINXBUILD) -b devhelp $(ALLSPHINXOPTS) $(BUILDDIR)/devhelp @echo @echo "Build finished." @echo "To view the help file:" @echo "# mkdir -p $$HOME/.local/share/devhelp/python-sparkpost" @echo "# ln -s $(BUILDDIR)/devhelp $$HOME/.local/share/devhelp/python-sparkpost" @echo "# devhelp" epub: $(SPHINXBUILD) -b epub $(ALLSPHINXOPTS) $(BUILDDIR)/epub @echo @echo "Build finished. The epub file is in $(BUILDDIR)/epub." latex: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo @echo "Build finished; the LaTeX files are in $(BUILDDIR)/latex." @echo "Run \`make' in that directory to run these through (pdf)latex" \ "(use \`make latexpdf' here to do that automatically)." latexpdf: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through pdflatex..." $(MAKE) -C $(BUILDDIR)/latex all-pdf @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." latexpdfja: $(SPHINXBUILD) -b latex $(ALLSPHINXOPTS) $(BUILDDIR)/latex @echo "Running LaTeX files through platex and dvipdfmx..." $(MAKE) -C $(BUILDDIR)/latex all-pdf-ja @echo "pdflatex finished; the PDF files are in $(BUILDDIR)/latex." text: $(SPHINXBUILD) -b text $(ALLSPHINXOPTS) $(BUILDDIR)/text @echo @echo "Build finished. The text files are in $(BUILDDIR)/text." man: $(SPHINXBUILD) -b man $(ALLSPHINXOPTS) $(BUILDDIR)/man @echo @echo "Build finished. The manual pages are in $(BUILDDIR)/man." texinfo: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo @echo "Build finished. The Texinfo files are in $(BUILDDIR)/texinfo." @echo "Run \`make' in that directory to run these through makeinfo" \ "(use \`make info' here to do that automatically)." info: $(SPHINXBUILD) -b texinfo $(ALLSPHINXOPTS) $(BUILDDIR)/texinfo @echo "Running Texinfo files through makeinfo..." make -C $(BUILDDIR)/texinfo info @echo "makeinfo finished; the Info files are in $(BUILDDIR)/texinfo." gettext: $(SPHINXBUILD) -b gettext $(I18NSPHINXOPTS) $(BUILDDIR)/locale @echo @echo "Build finished. The message catalogs are in $(BUILDDIR)/locale." changes: $(SPHINXBUILD) -b changes $(ALLSPHINXOPTS) $(BUILDDIR)/changes @echo @echo "The overview file is in $(BUILDDIR)/changes." linkcheck: $(SPHINXBUILD) -b linkcheck $(ALLSPHINXOPTS) $(BUILDDIR)/linkcheck @echo @echo "Link check complete; look for any errors in the above output " \ "or in $(BUILDDIR)/linkcheck/output.txt." doctest: $(SPHINXBUILD) -b doctest $(ALLSPHINXOPTS) $(BUILDDIR)/doctest @echo "Testing of doctests in the sources finished, look at the " \ "results in $(BUILDDIR)/doctest/output.txt." xml: $(SPHINXBUILD) -b xml $(ALLSPHINXOPTS) $(BUILDDIR)/xml @echo @echo "Build finished. The XML files are in $(BUILDDIR)/xml." pseudoxml: $(SPHINXBUILD) -b pseudoxml $(ALLSPHINXOPTS) $(BUILDDIR)/pseudoxml @echo @echo "Build finished. The pseudo-XML files are in $(BUILDDIR)/pseudoxml." python-sparkpost-1.3.7/docs/api.rst000066400000000000000000000002431341365572200173310ustar00rootroot00000000000000================= API Documentation ================= A complete API reference to the :data:`sparkpost` module. .. toctree:: :glob: :maxdepth: 1 api/* python-sparkpost-1.3.7/docs/api/000077500000000000000000000000001341365572200166005ustar00rootroot00000000000000python-sparkpost-1.3.7/docs/api/recipient_lists.rst000066400000000000000000000002251341365572200225310ustar00rootroot00000000000000.. module:: sparkpost.recipient_lists :mod:`sparkpost.recipient_lists` ================================ .. autoclass:: RecipientLists :members: python-sparkpost-1.3.7/docs/api/suppression_list.rst000066400000000000000000000002311341365572200227530ustar00rootroot00000000000000.. module:: sparkpost.suppression_list :mod:`sparkpost.suppression_list` ================================= .. autoclass:: SuppressionList :members: python-sparkpost-1.3.7/docs/api/templates.rst000066400000000000000000000002011341365572200213210ustar00rootroot00000000000000.. module:: sparkpost.templates :mod:`sparkpost.templates` ============================= .. autoclass:: Templates :members: python-sparkpost-1.3.7/docs/api/transmissions.rst000066400000000000000000000002161341365572200222450ustar00rootroot00000000000000.. module:: sparkpost.transmissions :mod:`sparkpost.transmissions` ============================== .. autoclass:: Transmissions :members: python-sparkpost-1.3.7/docs/conf.py000066400000000000000000000206551341365572200173360ustar00rootroot00000000000000# -*- coding: utf-8 -*- # # python-sparkpost documentation build configuration file, created by # sphinx-quickstart on Sun Feb 1 21:26:22 2015. # # This file is execfile()d with the current directory set to its # containing dir. # # Note that not all possible configuration values are present in this # autogenerated file. # # All configuration values have a default; values that are commented out # serve to show the default. import datetime import sys import os # If extensions (or modules to document with autodoc) are in another directory, # add these directories to sys.path here. If the directory is relative to the # documentation root, use os.path.abspath to make it absolute, like shown here. #sys.path.insert(0, os.path.abspath('.')) # -- General configuration ------------------------------------------------ # If your documentation needs a minimal Sphinx version, state it here. #needs_sphinx = '1.0' # Add any Sphinx extension module names here, as strings. They can be # extensions coming with Sphinx (named 'sphinx.ext.*') or your custom # ones. extensions = [ 'sphinx.ext.autodoc', ] # Load the source for autodoc sys.path.insert(0, os.path.abspath(os.path.join(os.getcwd(), '..'))) # Add any paths that contain templates here, relative to this directory. templates_path = ['_templates'] # The suffix of source filenames. source_suffix = '.rst' # The encoding of source files. #source_encoding = 'utf-8-sig' # The master toctree document. master_doc = 'index' # General information about the project. now = datetime.datetime.now() project = u'python-sparkpost' copyright = u'2014-{year}, Message Systems'.format(year=now.year) # The version info for the project you're documenting, acts as replacement for # |version| and |release|, also used in various other places throughout the # built documents. # # The short X.Y version. version = '1.3' # The full version, including alpha/beta/rc tags. release = '1.3.6' # The language for content autogenerated by Sphinx. Refer to documentation # for a list of supported languages. #language = None # There are two options for replacing |today|: either, you set today to some # non-false value, then it is used: #today = '' # Else, today_fmt is used as the format for a strftime call. #today_fmt = '%B %d, %Y' # List of patterns, relative to source directory, that match files and # directories to ignore when looking for source files. exclude_patterns = ['_build'] # The reST default role (used for this markup: `text`) to use for all # documents. #default_role = None # If true, '()' will be appended to :func: etc. cross-reference text. #add_function_parentheses = True # If true, the current module name will be prepended to all description # unit titles (such as .. function::). #add_module_names = True # If true, sectionauthor and moduleauthor directives will be shown in the # output. They are ignored by default. #show_authors = False # The name of the Pygments (syntax highlighting) style to use. pygments_style = 'sphinx' # A list of ignored prefixes for module index sorting. #modindex_common_prefix = [] # If true, keep warnings as "system message" paragraphs in the built documents. #keep_warnings = False # -- Options for HTML output ---------------------------------------------- on_rtd = os.environ.get('READTHEDOCS', None) == 'True' if not on_rtd: import sphinx_rtd_theme html_theme = 'sphinx_rtd_theme' html_theme_path = [sphinx_rtd_theme.get_html_theme_path()] # The theme to use for HTML and HTML Help pages. See the documentation for # a list of builtin themes. # Theme options are theme-specific and customize the look and feel of a theme # further. For a list of options available for each theme, see the # documentation. #html_theme_options = {} # Add any paths that contain custom themes here, relative to this directory. #html_theme_path = [] # The name for this set of Sphinx documents. If None, it defaults to # " v documentation". #html_title = None # A shorter title for the navigation bar. Default is the same as html_title. #html_short_title = None # The name of an image file (relative to this directory) to place at the top # of the sidebar. #html_logo = None # The name of an image file (within the static path) to use as favicon of the # docs. This file should be a Windows icon file (.ico) being 16x16 or 32x32 # pixels large. #html_favicon = None # Add any paths that contain custom static files (such as style sheets) here, # relative to this directory. They are copied after the builtin static files, # so a file named "default.css" will overwrite the builtin "default.css". html_static_path = ['_static'] # Add any extra paths that contain custom files (such as robots.txt or # .htaccess) here, relative to this directory. These files are copied # directly to the root of the documentation. #html_extra_path = [] # If not '', a 'Last updated on:' timestamp is inserted at every page bottom, # using the given strftime format. #html_last_updated_fmt = '%b %d, %Y' # If true, SmartyPants will be used to convert quotes and dashes to # typographically correct entities. #html_use_smartypants = True # Custom sidebar templates, maps document names to template names. #html_sidebars = {} # Additional templates that should be rendered to pages, maps page names to # template names. #html_additional_pages = {} # If false, no module index is generated. #html_domain_indices = True # If false, no index is generated. #html_use_index = True # If true, the index is split into individual pages for each letter. #html_split_index = False # If true, links to the reST sources are added to the pages. #html_show_sourcelink = True # If true, "Created using Sphinx" is shown in the HTML footer. Default is True. #html_show_sphinx = True # If true, "(C) Copyright ..." is shown in the HTML footer. Default is True. #html_show_copyright = True # If true, an OpenSearch description file will be output, and all pages will # contain a tag referring to it. The value of this option must be the # base URL from which the finished HTML is served. #html_use_opensearch = '' # This is the file name suffix for HTML files (e.g. ".xhtml"). #html_file_suffix = None # Output file base name for HTML help builder. htmlhelp_basename = 'python-sparkpostdoc' # -- Options for LaTeX output --------------------------------------------- latex_elements = { # The paper size ('letterpaper' or 'a4paper'). #'papersize': 'letterpaper', # The font size ('10pt', '11pt' or '12pt'). #'pointsize': '10pt', # Additional stuff for the LaTeX preamble. #'preamble': '', } # Grouping the document tree into LaTeX files. List of tuples # (source start file, target name, title, # author, documentclass [howto, manual, or own class]). latex_documents = [ ('index', 'python-sparkpost.tex', u'python-sparkpost Documentation', u'Message Systems', 'manual'), ] # The name of an image file (relative to this directory) to place at the top of # the title page. #latex_logo = None # For "manual" documents, if this is true, then toplevel headings are parts, # not chapters. #latex_use_parts = False # If true, show page references after internal links. #latex_show_pagerefs = False # If true, show URL addresses after external links. #latex_show_urls = False # Documents to append as an appendix to all manuals. #latex_appendices = [] # If false, no module index is generated. #latex_domain_indices = True # -- Options for manual page output --------------------------------------- # One entry per manual page. List of tuples # (source start file, name, description, authors, manual section). man_pages = [ ('index', 'python-sparkpost', u'python-sparkpost Documentation', [u'Message Systems'], 1) ] # If true, show URL addresses after external links. #man_show_urls = False # -- Options for Texinfo output ------------------------------------------- # Grouping the document tree into Texinfo files. List of tuples # (source start file, target name, title, author, # dir menu entry, description, category) texinfo_documents = [ ('index', 'python-sparkpost', u'python-sparkpost Documentation', u'Message Systems', 'python-sparkpost', 'One line description of project.', 'Miscellaneous'), ] # Documents to append as an appendix to all manuals. #texinfo_appendices = [] # If false, no module index is generated. #texinfo_domain_indices = True # How to display URL addresses: 'footnote', 'no', or 'inline'. #texinfo_show_urls = 'footnote' # If true, do not generate a @detailmenu in the "Top" node's menu. #texinfo_no_detailmenu = False python-sparkpost-1.3.7/docs/django/000077500000000000000000000000001341365572200172715ustar00rootroot00000000000000python-sparkpost-1.3.7/docs/django/backend.rst000066400000000000000000000052371341365572200214210ustar00rootroot00000000000000Django Email Backend ==================== The SparkPost python library comes with an email backend for Django. Configure Django ---------------- To configure Django to use SparkPost, put the following configuration in `settings.py` file. .. code-block:: python SPARKPOST_API_KEY = 'API_KEY' EMAIL_BACKEND = 'sparkpost.django.email_backend.SparkPostEmailBackend' Replace *API_KEY* with an actual API key. You can also use `SPARKPOST_OPTIONS` to set options that will apply to every transmission. For example: .. code-block:: python SPARKPOST_OPTIONS = { 'track_opens': False, 'track_clicks': False, 'transactional': True, } Sending an email ---------------- Django is now configured to use the SparkPost email backend. You can now send mail using Django's `send_mail` method: .. code-block:: python from django.core.mail import send_mail send_mail( subject='Hello from SparkPost', message='Woo hoo! Sent from Django!', from_email='from@yourdomain.com', recipient_list=['to@example.com'], html_message='

Hello Rock stars!

', ) You can also use `EmailMessage` or `EmailMultiAlternatives` class directly. That will give you access to more specific fileds like `template`: .. code-block:: python email = EmailMessage( to=[ { "address": "to@example.com", "substitution_data": { "key": "value" } } ], from_email='test@from.com' ) email.template = 'template-id' email.send() Or cc, bcc, reply to, or attachments fields: .. code-block:: python from django.core.mail import EmailMultiAlternatives email = EmailMultiAlternatives( subject='hello from sparkpost', body='Woo hoo! Sent from Django!', from_email='from@yourdomain.com', to=['to@example.com'], cc=['ccone@example.com'], bcc=['bccone@example.com'], reply_to=['replyone@example.com'] ) email.attach_alternative('

Woo hoo! Sent from Django!

', 'text/html') email.attach('image.png', img_data, 'image/png') email.send() Supported version ----------------- SparkPost will support all versions of Django that are within extended support period. Refer to `Django Supported Versions`_. .. _Django Supported Versions: https://www.djangoproject.com/download/#supported-versions Additional documentation ------------------------ See our `Using SparkPost with Django`_ in support article. .. _Using SparkPost with Django: https://support.sparkpost.com/customer/en/portal/articles/2169630-using-sparkpost-with-django?b_id=7411 python-sparkpost-1.3.7/docs/index.rst000066400000000000000000000041111341365572200176650ustar00rootroot00000000000000SparkPost Python API client =========================== The super-mega-official Python package for using the SparkPost API. Installation ------------ Install from PyPI using `pip`_: .. code-block:: bash $ pip install sparkpost .. _pip: http://www.pip-installer.org/en/latest/ Authorization ------------- Go to `API & SMTP`_ in the SparkPost app and create an API key. We recommend using the ``SPARKPOST_API_KEY`` environment variable: .. code-block:: python from sparkpost import SparkPost sp = SparkPost() # uses environment variable Alternatively, you can pass the API key to the SparkPost class: .. code-block:: python from sparkpost import SparkPost sp = SparkPost('YOUR API KEY') For SparkPost EU and Enterprise accounts, pass in a second parameter to set the API host. .. code-block:: python from sparkpost import SparkPost sp = SparkPost('YOUR API KEY', 'https://api.eu.sparkpost.com/api') .. _API & SMTP: https://app.sparkpost.com/configuration/credentials Resources --------- The following resources are available in python-sparkpost: .. toctree:: :glob: :maxdepth: 1 resources/* API reference ------------- Auto-generated API reference for python-sparkpost: .. toctree:: :maxdepth: 2 api Using in Django --------------- Configure Django to use SparkPost email backend .. toctree:: :maxdepth: 2 django/backend Additional documentation ------------------------ The underlying SparkPost API is documented at the official `SparkPost API Reference`_. .. _SparkPost API Reference: https://www.sparkpost.com/api Contribute ---------- #. Check for open issues or open a fresh issue to start a discussion around a feature idea or a bug. #. Fork `the repository`_ on GitHub and make your changes in a branch on your fork #. Write a test which shows that the bug was fixed or that the feature works as expected. #. Send a pull request. Make sure to add yourself to AUTHORS_. .. _`the repository`: http://github.com/SparkPost/python-sparkpost .. _AUTHORS: https://github.com/SparkPost/python-sparkpost/blob/master/AUTHORS.rst python-sparkpost-1.3.7/docs/make.bat000066400000000000000000000151011341365572200174320ustar00rootroot00000000000000@ECHO OFF REM Command file for Sphinx documentation if "%SPHINXBUILD%" == "" ( set SPHINXBUILD=sphinx-build ) set BUILDDIR=_build set ALLSPHINXOPTS=-d %BUILDDIR%/doctrees %SPHINXOPTS% . set I18NSPHINXOPTS=%SPHINXOPTS% . if NOT "%PAPER%" == "" ( set ALLSPHINXOPTS=-D latex_paper_size=%PAPER% %ALLSPHINXOPTS% set I18NSPHINXOPTS=-D latex_paper_size=%PAPER% %I18NSPHINXOPTS% ) if "%1" == "" goto help if "%1" == "help" ( :help echo.Please use `make ^` where ^ is one of echo. html to make standalone HTML files echo. dirhtml to make HTML files named index.html in directories echo. singlehtml to make a single large HTML file echo. pickle to make pickle files echo. json to make JSON files echo. htmlhelp to make HTML files and a HTML help project echo. qthelp to make HTML files and a qthelp project echo. devhelp to make HTML files and a Devhelp project echo. epub to make an epub echo. latex to make LaTeX files, you can set PAPER=a4 or PAPER=letter echo. text to make text files echo. man to make manual pages echo. texinfo to make Texinfo files echo. gettext to make PO message catalogs echo. changes to make an overview over all changed/added/deprecated items echo. xml to make Docutils-native XML files echo. pseudoxml to make pseudoxml-XML files for display purposes echo. linkcheck to check all external links for integrity echo. doctest to run all doctests embedded in the documentation if enabled goto end ) if "%1" == "clean" ( for /d %%i in (%BUILDDIR%\*) do rmdir /q /s %%i del /q /s %BUILDDIR%\* goto end ) %SPHINXBUILD% 2> nul if errorlevel 9009 ( echo. echo.The 'sphinx-build' command was not found. Make sure you have Sphinx echo.installed, then set the SPHINXBUILD environment variable to point echo.to the full path of the 'sphinx-build' executable. Alternatively you echo.may add the Sphinx directory to PATH. echo. echo.If you don't have Sphinx installed, grab it from echo.http://sphinx-doc.org/ exit /b 1 ) if "%1" == "html" ( %SPHINXBUILD% -b html %ALLSPHINXOPTS% %BUILDDIR%/html if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/html. goto end ) if "%1" == "dirhtml" ( %SPHINXBUILD% -b dirhtml %ALLSPHINXOPTS% %BUILDDIR%/dirhtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/dirhtml. goto end ) if "%1" == "singlehtml" ( %SPHINXBUILD% -b singlehtml %ALLSPHINXOPTS% %BUILDDIR%/singlehtml if errorlevel 1 exit /b 1 echo. echo.Build finished. The HTML pages are in %BUILDDIR%/singlehtml. goto end ) if "%1" == "pickle" ( %SPHINXBUILD% -b pickle %ALLSPHINXOPTS% %BUILDDIR%/pickle if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the pickle files. goto end ) if "%1" == "json" ( %SPHINXBUILD% -b json %ALLSPHINXOPTS% %BUILDDIR%/json if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can process the JSON files. goto end ) if "%1" == "htmlhelp" ( %SPHINXBUILD% -b htmlhelp %ALLSPHINXOPTS% %BUILDDIR%/htmlhelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run HTML Help Workshop with the ^ .hhp project file in %BUILDDIR%/htmlhelp. goto end ) if "%1" == "qthelp" ( %SPHINXBUILD% -b qthelp %ALLSPHINXOPTS% %BUILDDIR%/qthelp if errorlevel 1 exit /b 1 echo. echo.Build finished; now you can run "qcollectiongenerator" with the ^ .qhcp project file in %BUILDDIR%/qthelp, like this: echo.^> qcollectiongenerator %BUILDDIR%\qthelp\python-sparkpost.qhcp echo.To view the help file: echo.^> assistant -collectionFile %BUILDDIR%\qthelp\python-sparkpost.ghc goto end ) if "%1" == "devhelp" ( %SPHINXBUILD% -b devhelp %ALLSPHINXOPTS% %BUILDDIR%/devhelp if errorlevel 1 exit /b 1 echo. echo.Build finished. goto end ) if "%1" == "epub" ( %SPHINXBUILD% -b epub %ALLSPHINXOPTS% %BUILDDIR%/epub if errorlevel 1 exit /b 1 echo. echo.Build finished. The epub file is in %BUILDDIR%/epub. goto end ) if "%1" == "latex" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex if errorlevel 1 exit /b 1 echo. echo.Build finished; the LaTeX files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdf" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "latexpdfja" ( %SPHINXBUILD% -b latex %ALLSPHINXOPTS% %BUILDDIR%/latex cd %BUILDDIR%/latex make all-pdf-ja cd %BUILDDIR%/.. echo. echo.Build finished; the PDF files are in %BUILDDIR%/latex. goto end ) if "%1" == "text" ( %SPHINXBUILD% -b text %ALLSPHINXOPTS% %BUILDDIR%/text if errorlevel 1 exit /b 1 echo. echo.Build finished. The text files are in %BUILDDIR%/text. goto end ) if "%1" == "man" ( %SPHINXBUILD% -b man %ALLSPHINXOPTS% %BUILDDIR%/man if errorlevel 1 exit /b 1 echo. echo.Build finished. The manual pages are in %BUILDDIR%/man. goto end ) if "%1" == "texinfo" ( %SPHINXBUILD% -b texinfo %ALLSPHINXOPTS% %BUILDDIR%/texinfo if errorlevel 1 exit /b 1 echo. echo.Build finished. The Texinfo files are in %BUILDDIR%/texinfo. goto end ) if "%1" == "gettext" ( %SPHINXBUILD% -b gettext %I18NSPHINXOPTS% %BUILDDIR%/locale if errorlevel 1 exit /b 1 echo. echo.Build finished. The message catalogs are in %BUILDDIR%/locale. goto end ) if "%1" == "changes" ( %SPHINXBUILD% -b changes %ALLSPHINXOPTS% %BUILDDIR%/changes if errorlevel 1 exit /b 1 echo. echo.The overview file is in %BUILDDIR%/changes. goto end ) if "%1" == "linkcheck" ( %SPHINXBUILD% -b linkcheck %ALLSPHINXOPTS% %BUILDDIR%/linkcheck if errorlevel 1 exit /b 1 echo. echo.Link check complete; look for any errors in the above output ^ or in %BUILDDIR%/linkcheck/output.txt. goto end ) if "%1" == "doctest" ( %SPHINXBUILD% -b doctest %ALLSPHINXOPTS% %BUILDDIR%/doctest if errorlevel 1 exit /b 1 echo. echo.Testing of doctests in the sources finished, look at the ^ results in %BUILDDIR%/doctest/output.txt. goto end ) if "%1" == "xml" ( %SPHINXBUILD% -b xml %ALLSPHINXOPTS% %BUILDDIR%/xml if errorlevel 1 exit /b 1 echo. echo.Build finished. The XML files are in %BUILDDIR%/xml. goto end ) if "%1" == "pseudoxml" ( %SPHINXBUILD% -b pseudoxml %ALLSPHINXOPTS% %BUILDDIR%/pseudoxml if errorlevel 1 exit /b 1 echo. echo.Build finished. The pseudo-XML files are in %BUILDDIR%/pseudoxml. goto end ) :end python-sparkpost-1.3.7/docs/requirements.txt000066400000000000000000000000301341365572200213040ustar00rootroot00000000000000sphinx sphinx-rtd-theme python-sparkpost-1.3.7/docs/resources/000077500000000000000000000000001341365572200200415ustar00rootroot00000000000000python-sparkpost-1.3.7/docs/resources/metrics.rst000066400000000000000000000010421341365572200222360ustar00rootroot00000000000000Metrics ======= Retrieve a list of campaigns ---------------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.metrics.campaigns.list() Retrieve a list of domains -------------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.metrics.domains.list() Additional documentation ------------------------ See the `SparkPost Metrics API Reference`_. .. _SparkPost Metrics API Reference: https://www.sparkpost.com/api#/reference/metrics python-sparkpost-1.3.7/docs/resources/recipient_lists.rst000066400000000000000000000033621341365572200237770ustar00rootroot00000000000000Recipient Lists =============== Let's use the underlying `recipient_lists API`_ to create a recipient list: .. code-block:: python from sparkpost import SparkPost sp = SparkPost() response = sp.recipient_lists.create( id='UNIQUE_TEST_ID', name='Test Recipient list', recipients=[ { 'address': { 'email': 'test1@test.com' } }, { 'address': { 'email': 'test2@test.com' } }, { 'address': { 'email': 'test3@test.com' } } ] ) print(response) # outputs {u'total_accepted_recipients': 3, u'id': u'UNIQUE_TEST_ID', u'total_rejected_recipients': 0, u'name':'Test Recipient list'} .. _recipient_lists API: https://www.sparkpost.com/api#/reference/recipient-lists Retrieve a recipient list ------------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.recipient_lists.get('my-list-id') List all recipient lists ------------------------ .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.recipient_lists.list() API reference ------------- :doc:`/api/recipient_lists` Further examples ---------------- See the `python-sparkpost recipient_lists examples`_. .. _python-sparkpost recipient_lists examples: https://github.com/SparkPost/python-sparkpost/tree/master/examples/recipient_lists Additional documentation ------------------------ See the `SparkPost Recipient Lists API Reference`_. .. _SparkPost Recipient Lists API Reference: https://www.sparkpost.com/api#/reference/recipient_lists python-sparkpost-1.3.7/docs/resources/suppression_list.rst000066400000000000000000000026351341365572200242260ustar00rootroot00000000000000Suppression List ================ Let's use the underlying `suppression_list API`_ to create a suppression entry: .. code-block:: python from sparkpost import SparkPost sp = SparkPost() response = sp.suppression_list.create({ "email": "test@test.com" "transactional": False, "non_transactional": True, "description": "User requested to not receive any non-transactional emails." }) print(response) # outputs {u'message': u'Recipient successfully created'} .. _suppression_list API: https://www.sparkpost.com/api#/reference/suppression-list Get a suppression entry ----------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.suppression_list.get('test@test.com') List suppression entries ------------------------ .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.suppression_list.list() API reference ------------- :doc:`/api/suppression_list` Further examples ---------------- See the `python-sparkpost suppression_list examples`_. .. _python-sparkpost suppression_list examples: https://github.com/SparkPost/python-sparkpost/tree/master/examples/suppression_list Additional documentation ------------------------ See the `SparkPost Suppression List API Reference`_. .. _SparkPost Suppression List API Reference: https://www.sparkpost.com/api#/reference/suppression-list python-sparkpost-1.3.7/docs/resources/templates.rst000066400000000000000000000024121341365572200225700ustar00rootroot00000000000000Templates ============= Let's use the underlying `templates API`_ to create a template: .. code-block:: python from sparkpost import SparkPost sp = SparkPost() response = sp.templates.create( id='TEST_ID', name='Test Template', from_email='test@test.com', subject='Test email template!', html='This is a test email template!' ) print(response) # outputs {u'id': u'TEST_ID'} .. _templates API: https://www.sparkpost.com/api#/reference/templates Retrieve a template ----------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.templates.get('my-template-id') List all templates ---------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.templates.list() API reference ------------- :doc:`/api/templates` Further examples ---------------- See the `python-sparkpost templates examples`_. .. _python-sparkpost templates examples: https://github.com/SparkPost/python-sparkpost/tree/master/examples/templates Additional documentation ------------------------ See the `SparkPost Templates API Reference`_. .. _SparkPost Templates API Reference: https://www.sparkpost.com/api#/reference/templates python-sparkpost-1.3.7/docs/resources/transmissions.rst000066400000000000000000000103661341365572200235150ustar00rootroot00000000000000Transmissions ============= Here at SparkPost, our messages are known as transmissions. Let's use the underlying `transmissions API`_ to send a friendly test message: .. code-block:: python from sparkpost import SparkPost sp = SparkPost() response = sp.transmissions.send( recipients=['someone@somedomain.com'], html='

Hello world

', from_email='test@sparkpostbox.com', subject='Hello from python-sparkpost', track_opens=True, track_clicks=True ) print(response) # outputs {u'total_accepted_recipients': 1, u'id': u'47960765679942446', u'total_rejected_recipients': 0} .. _transmissions API: https://www.sparkpost.com/api#/reference/transmissions Send a transmission ------------------- Using inline templates and/or recipients **************************************** .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.send( recipients=['someone@somedomain.com'], text="Hello world", html='

Hello world

', from_email='test@sparkpostbox.com', subject='Hello from python-sparkpost', track_opens=True, track_clicks=True ) Including cc, bcc ***************** .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.send( recipients=['someone@somedomain.com'], cc=['carboncopy@somedomain.com'], bcc=['blindcarboncopy@somedomain.com'], text="Hello world", html='

Hello world

', from_email='test@sparkpostbox.com', subject='Hello from python-sparkpost', track_opens=True, track_clicks=True ) Sending an attachment ********************* .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.send( recipients=['someone@somedomain.com'], text="Hello world", html='

Hello world

', from_email='test@sparkpostbox.com', subject='Hello from python-sparkpost', track_opens=True, track_clicks=True, attachments=[ { "name": "test.txt", "type": "text/plain", "filename": "/home/sparkpost/a-file.txt" } ] ) Using substitution data *********************** .. note:: Substitution data can be specified at the template, transmission and recipient levels. The order of precedence is as follows: recipient overrides transmission overrides template. .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.send( recipients=['someone@somedomain.com'], text="Hello {{name}}", html='

Hello {{name}}

', from_email='test@sparkpostbox.com', subject='Hello from python-sparkpost', track_opens=True, track_clicks=True, substitution_data={ 'name': 'Sparky' } ) Using a stored template *********************** .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.send( recipients=['someone@somedomain.com'], template='my-template-id' ) Using a stored recipient list ***************************** .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.send( recipient_list='my-recipient-list', template='my-template-id' ) Retrieve a transmission ----------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.get('my-transmission-id') List all transmissions ---------------------- .. code-block:: python from sparkpost import SparkPost sp = SparkPost() sp.transmissions.list() API reference ------------- :doc:`/api/transmissions` Further examples ---------------- See the `python-sparkpost transmissions examples`_. .. _python-sparkpost transmissions examples: https://github.com/SparkPost/python-sparkpost/tree/master/examples/transmissions Additional documentation ------------------------ See the `SparkPost Transmissions API Reference`_. .. _SparkPost Transmissions API Reference: https://www.sparkpost.com/api#/reference/transmissions python-sparkpost-1.3.7/examples/000077500000000000000000000000001341365572200167155ustar00rootroot00000000000000python-sparkpost-1.3.7/examples/base_resource.py000066400000000000000000000006151341365572200221120ustar00rootroot00000000000000import os from sparkpost.base import Resource class Webhooks(Resource): key = "webhooks" def list(self, **kwargs): results = self.request('GET', self.uri, **kwargs) return results api_key = os.environ.get('SPARKPOST_API_KEY', None) webhooks = Webhooks('https://api.sparkpost.com/api/v1', api_key) # returns a list of webhooks for your account print(webhooks.list()) python-sparkpost-1.3.7/examples/recipient_lists/000077500000000000000000000000001341365572200221155ustar00rootroot00000000000000python-sparkpost-1.3.7/examples/recipient_lists/create_recipient_list.py000066400000000000000000000007531341365572200270340ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() response = sp.recipient_lists.create( id='UNIQUE_TEST_ID', name='Test Recipient list', recipients=[ { 'address': { 'email': 'test1@test.com' } }, { 'address': { 'email': 'test2@test.com' } }, { 'address': { 'email': 'test3@test.com' } } ] ) print(response) python-sparkpost-1.3.7/examples/recipient_lists/delete_recipient_list.py000066400000000000000000000001561341365572200270300ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.recipient_lists.delete('list_id') print(result) python-sparkpost-1.3.7/examples/recipient_lists/get_recipient_list.py000066400000000000000000000001731341365572200263440ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipient_list = sp.recipient_lists.get('list_id') print(recipient_list) python-sparkpost-1.3.7/examples/recipient_lists/get_recipient_list_with_recipients.py000066400000000000000000000002011341365572200316140ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipient_list = sp.recipient_lists.get('list_id', True) print(recipient_list) python-sparkpost-1.3.7/examples/recipient_lists/list_recipient_lists.py000066400000000000000000000001651341365572200267240ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipient_lists = sp.recipient_lists.list() print(recipient_lists) python-sparkpost-1.3.7/examples/recipient_lists/update_recipient_list.py000066400000000000000000000007521341365572200270520ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() response = sp.recipient_lists.update( 'EXISTING_TEST_ID', name='Test Recipient list', recipients=[ { 'address': { 'email': 'test1@test.com' } }, { 'address': { 'email': 'test2@test.com' } }, { 'address': { 'email': 'test3@test.com' } } ] ) print(response) python-sparkpost-1.3.7/examples/suppression_list/000077500000000000000000000000001341365572200223425ustar00rootroot00000000000000python-sparkpost-1.3.7/examples/suppression_list/create_suppression_entries_bulk.py000066400000000000000000000011341341365572200313760ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipients = [ { 'email': 'test1@test.com', 'transactional': False, 'non_transactional': True, 'description': 'Test description 1' }, { 'email': 'test2@test.com', 'transactional': True, 'non_transactional': True, 'description': 'Test description 2' }, { 'email': 'test3@test.com', 'transactional': True, 'non_transactional': False, 'description': 'Test description 3' } ] result = sp.suppression_list.create(recipients) print(result) python-sparkpost-1.3.7/examples/suppression_list/create_suppression_entry.py000066400000000000000000000003501341365572200300500ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.suppression_list.create({ 'email': 'test@test.com', 'transactional': False, 'non_transactional': True, 'description': 'Test description' }) print(result) python-sparkpost-1.3.7/examples/suppression_list/delete_suppression_entry.py000066400000000000000000000001651341365572200300530ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.suppression_list.delete('test@test.com') print(result) python-sparkpost-1.3.7/examples/suppression_list/get_suppression_entry.py000066400000000000000000000001601341365572200273630ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() entry = sp.suppression_list.get('test@test.com') print(entry) python-sparkpost-1.3.7/examples/suppression_list/list_suppression_entries.py000066400000000000000000000003051341365572200300700ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() results = sp.suppression_list.list( from_date='2015-05-07T00:00:00+0000', to_date='2015-05-07T23:59:59+0000', limit=5 ) print(results) python-sparkpost-1.3.7/examples/suppression_list/update_suppression_entries_bulk.py000066400000000000000000000011341341365572200314150ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipients = [ { 'email': 'test1@test.com', 'transactional': False, 'non_transactional': True, 'description': 'Test description 1' }, { 'email': 'test2@test.com', 'transactional': True, 'non_transactional': True, 'description': 'Test description 2' }, { 'email': 'test3@test.com', 'transactional': True, 'non_transactional': False, 'description': 'Test description 3' } ] result = sp.suppression_list.update(recipients) print(result) python-sparkpost-1.3.7/examples/suppression_list/update_suppression_enty.py000066400000000000000000000003501341365572200277050ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.suppression_list.update({ 'email': 'test@test.com', 'transactional': False, 'non_transactional': True, 'description': 'Test description' }) print(result) python-sparkpost-1.3.7/examples/templates/000077500000000000000000000000001341365572200207135ustar00rootroot00000000000000python-sparkpost-1.3.7/examples/templates/create_template.py000066400000000000000000000004071341365572200244240ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() response = sp.templates.create( id='TEST_ID', name='Test Template', from_email='test@test.com', subject='Test email template!', html='This is a test email template!' ) print(response) python-sparkpost-1.3.7/examples/templates/delete_template.py000066400000000000000000000001541341365572200244220ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.templates.delete('template_id') print(result) python-sparkpost-1.3.7/examples/templates/get_template.py000066400000000000000000000001551341365572200237400ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() template = sp.templates.get('template_id') print(template) python-sparkpost-1.3.7/examples/templates/list_templates.py000066400000000000000000000001531341365572200243150ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() template_list = sp.templates.list() print(template_list) python-sparkpost-1.3.7/examples/templates/preview_draft_template.py000066400000000000000000000003011341365572200260130ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() sub_data = { 'first_name': 'John', 'last_name': 'Doe' } template = sp.templates.preview('template_id', sub_data, True) print(template) python-sparkpost-1.3.7/examples/templates/preview_template.py000066400000000000000000000002731341365572200246430ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() sub_data = { 'first_name': 'John', 'last_name': 'Doe' } template = sp.templates.preview('template_id', sub_data) print(template) python-sparkpost-1.3.7/examples/templates/update_template.py000066400000000000000000000004231341365572200244410ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() response = sp.templates.update( 'TEST_ID', name='Test Template', from_email='test@test.com', subject='Updated Test email template!', html='This is a test email template! Updated!' ) print(response) python-sparkpost-1.3.7/examples/transmissions/000077500000000000000000000000001341365572200216315ustar00rootroot00000000000000python-sparkpost-1.3.7/examples/transmissions/a-file.txt000066400000000000000000000000711341365572200235250ustar00rootroot00000000000000Woo hoo! This attachment was added via python-sparkpost. python-sparkpost-1.3.7/examples/transmissions/find_transmission.py000066400000000000000000000001751341365572200257370ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() transmission = sp.transmissions.get('transmission_id') print(transmission) python-sparkpost-1.3.7/examples/transmissions/handle_exception.py000066400000000000000000000021251341365572200255140ustar00rootroot00000000000000""" This script demonstrates how to use the SparkPostAPIException class. This particular example will yield output similar to the following: $ python send_transmission_exception.py 400 {u'errors': [{u'message': u'Invalid domain', u'code': u'7001', u'description': u'Unconfigured Sending Domain '}]} ['Invalid domain Code: 7001 Description: Unconfigured Sending Domain \n'] """ from sparkpost import SparkPost from sparkpost.exceptions import SparkPostAPIException sp = SparkPost() try: response = sp.transmissions.send( recipients=['john.doe@example.com'], text='Hello there', from_email='Testing ', subject='Testing python-sparkpost exceptions' ) except SparkPostAPIException as err: # http response status code print(err.status) # python requests library response object # http://docs.python-requests.org/en/master/api/#requests.Response print(err.response.json()) # list of formatted errors print(err.errors) python-sparkpost-1.3.7/examples/transmissions/schedule_transmission.py000066400000000000000000000015071341365572200266130ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() response = sp.transmissions.send( recipients=[ 'postmaster@example.com', 'you@me.com', { 'address': { 'email': 'john.doe@example.com', 'name': 'John Doe' } } ], html='

Hello world {{name}}

', text='Hello world {{name}}', from_email='test@sparkpostbox.com', subject='Example Script', description='contrived example', custom_headers={ 'X-CUSTOM-HEADER': 'foo bar' }, track_opens=True, track_clicks=True, start_time='2015-11-06T09:10:00-05:00', campaign='python-sparkpost example', metadata={ 'key': 'value', 'arbitrary': 'values' }, substitution_data={ 'name': 'Example User' } ) print(response) python-sparkpost-1.3.7/examples/transmissions/send_transmission.py000066400000000000000000000022321341365572200257440ustar00rootroot00000000000000import os from sparkpost import SparkPost parent_dir = os.path.dirname(os.path.realpath(__file__)) attachment_path = os.path.abspath(os.path.join(parent_dir, "a-file.txt")) sp = SparkPost() response = sp.transmissions.send( recipients=[ 'postmaster@example.com', 'you@me.com', { 'address': { 'email': 'john.doe@example.com', 'name': 'John Doe' } } ], cc=['carboncopy@example.com'], bcc=['blindcarboncopy@example.com'], html='

Hello {{name}}

', text='Hello {{name}}', from_email='Test User ', subject='Example Script', description='contrived example', custom_headers={ 'X-CUSTOM-HEADER': 'foo bar' }, track_opens=True, track_clicks=True, attachments=[ { "name": "test.txt", "type": "text/plain", "filename": attachment_path } ], campaign='sdk example', metadata={ 'key': 'value', 'arbitrary': 'values' }, substitution_data={ 'name': 'Example User' }, reply_to='no-reply@sparkpostmail.com' ) python-sparkpost-1.3.7/examples/transmissions/transmission_stored_list.py000066400000000000000000000010721341365572200273470ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() response = sp.transmissions.send( recipient_list='my_list', html='

Hello world {{name}}

', text='Hello world {{name}}', from_email='test@sparkpostbox.com', subject='Example Script', description='contrived example', custom_headers={ 'X-CUSTOM-HEADER': 'foo bar' }, campaign='sdk example', metadata={ 'key': 'value', 'arbitrary': 'values' }, substitution_data={ 'name': 'Example User' }, reply_to='no-reply@sparkpostmail.com' ) python-sparkpost-1.3.7/examples/transmissions/transmission_stored_template.py000066400000000000000000000002611341365572200302060ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() response = sp.transmissions.send( recipients=['you@me.com'], template='my-template-id', use_draft_template=True ) python-sparkpost-1.3.7/setup.cfg000066400000000000000000000000321341365572200167130ustar00rootroot00000000000000[bdist_wheel] universal=1 python-sparkpost-1.3.7/setup.py000066400000000000000000000016771341365572200166240ustar00rootroot00000000000000from codecs import open from setuptools import setup, find_packages with open('README.rst', 'r', 'utf-8') as f: readme = f.read() setup( name='sparkpost', version='1.3.7', author='SparkPost', author_email='developers@sparkpost.com', packages=find_packages(), url='https://github.com/SparkPost/python-sparkpost', license='Apache 2.0', description='SparkPost Python API client', long_description=readme, install_requires=['requests>=2.20.1'], classifiers=[ 'Development Status :: 5 - Production/Stable', 'Intended Audience :: Developers', 'Topic :: Communications :: Email', 'License :: OSI Approved :: Apache Software License', 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', ], ) python-sparkpost-1.3.7/sparkpost/000077500000000000000000000000001341365572200171255ustar00rootroot00000000000000python-sparkpost-1.3.7/sparkpost/__init__.py000066400000000000000000000033011341365572200212330ustar00rootroot00000000000000import os from .base import RequestsTransport from .exceptions import SparkPostException from .metrics import Metrics from .recipient_lists import RecipientLists from .suppression_list import SuppressionList from .templates import Templates from .transmissions import Transmissions __version__ = '1.3.6' class SparkPost(object): TRANSPORT_CLASS = RequestsTransport def __init__(self, api_key=None, base_uri='https://api.sparkpost.com', version='1'): "Set up the SparkPost API client" if not api_key: api_key = self.get_api_key() if not api_key: raise SparkPostException("No API key. Improve message.") self.base_uri = base_uri + '/api/v' + version self.api_key = api_key self.metrics = Metrics(self.base_uri, self.api_key, self.TRANSPORT_CLASS) self.recipient_lists = RecipientLists(self.base_uri, self.api_key, self.TRANSPORT_CLASS) self.suppression_list = SuppressionList(self.base_uri, self.api_key, self.TRANSPORT_CLASS) self.templates = Templates(self.base_uri, self.api_key, self.TRANSPORT_CLASS) self.transmissions = Transmissions(self.base_uri, self.api_key, self.TRANSPORT_CLASS) # Keeping self.transmission for backwards compatibility. # Will be removed in a future release. self.transmission = self.transmissions def get_api_key(self): "Get API key from environment variable" return os.environ.get('SPARKPOST_API_KEY', None) python-sparkpost-1.3.7/sparkpost/base.py000066400000000000000000000030231341365572200204070ustar00rootroot00000000000000import sparkpost from .exceptions import SparkPostAPIException class RequestsTransport(object): def __init__(self): import requests self.sess = requests.Session() def request(self, method, uri, headers, **kwargs): response = self.sess.request(method, uri, headers=headers, **kwargs) if response.status_code == 204: return True if not response.ok: raise SparkPostAPIException(response) if 'results' in response.json(): return response.json()['results'] return response.json() class Resource(object): key = "" def __init__(self, base_uri, api_key, transport_class=RequestsTransport): self.base_uri = base_uri self.api_key = api_key self.transport = transport_class() @property def uri(self): return "%s/%s" % (self.base_uri, self.key) def request(self, method, uri, **kwargs): headers = { 'User-Agent': 'python-sparkpost/' + sparkpost.__version__, 'Content-Type': 'application/json', 'Authorization': self.api_key } response = self.transport.request(method, uri, headers=headers, **kwargs) return response def get(self): raise NotImplementedError def list(self): raise NotImplementedError def create(self): raise NotImplementedError def update(self): raise NotImplementedError def delete(self): raise NotImplementedError python-sparkpost-1.3.7/sparkpost/django/000077500000000000000000000000001341365572200203675ustar00rootroot00000000000000python-sparkpost-1.3.7/sparkpost/django/__init__.py000066400000000000000000000000001341365572200224660ustar00rootroot00000000000000python-sparkpost-1.3.7/sparkpost/django/email_backend.py000066400000000000000000000022731341365572200235030ustar00rootroot00000000000000from django.conf import settings from django.core.mail.backends.base import BaseEmailBackend from sparkpost import SparkPost from .message import SparkPostMessage class SparkPostEmailBackend(BaseEmailBackend): """ SparkPost wrapper for Django email backend """ def __init__(self, fail_silently=False, **kwargs): super(SparkPostEmailBackend, self)\ .__init__(fail_silently=fail_silently, **kwargs) sp_api_key = getattr(settings, 'SPARKPOST_API_KEY', None) self.client = SparkPost(sp_api_key) def send_messages(self, email_messages): """ Send emails, returns integer representing number of successful emails """ success = 0 for message in email_messages: try: response = self._send(SparkPostMessage(message)) success += response['total_accepted_recipients'] except Exception: if not self.fail_silently: raise return success def _send(self, message): params = getattr(settings, 'SPARKPOST_OPTIONS', {}).copy() params.update(message) return self.client.transmissions.send(**params) python-sparkpost-1.3.7/sparkpost/django/exceptions.py000066400000000000000000000001341341365572200231200ustar00rootroot00000000000000class UnsupportedContent(Exception): pass class UnsupportedParam(Exception): pass python-sparkpost-1.3.7/sparkpost/django/message.py000066400000000000000000000067321341365572200223750ustar00rootroot00000000000000import mimetypes from base64 import b64encode from django.core.mail import EmailMultiAlternatives from django.core.mail.message import DEFAULT_ATTACHMENT_MIME_TYPE from django.conf import settings from .exceptions import UnsupportedContent class SparkPostMessage(dict): """ Takes a Django EmailMessage and formats it for use with the SparkPost API. The dictionary returned would be formatted like this: { 'recipients': ['recipient@example.com'], 'from_email': 'from@example.com', 'text': 'Hello world', 'html': '

Hello world

', 'subject': 'Hello from the SparkPost Django email backend' } """ def __init__(self, message): formatted = dict() if message.to: formatted['recipients'] = message.to if message.from_email: formatted['from_email'] = message.from_email if message.subject: formatted['subject'] = message.subject if hasattr(message, 'template'): formatted['template'] = message.template elif message.content_subtype == 'html': formatted['html'] = message.body else: formatted['text'] = message.body if message.cc: formatted['cc'] = message.cc if message.bcc: formatted['bcc'] = message.bcc if hasattr(message, 'reply_to') and message.reply_to: formatted['reply_to'] = ','.join(message.reply_to) if isinstance(message, EmailMultiAlternatives): for alternative in message.alternatives: if alternative[1] == 'text/html': formatted['html'] = alternative[0] else: raise UnsupportedContent( 'Content type %s is not supported' % alternative[1] ) if message.attachments: formatted['attachments'] = [] str_encoding = settings.DEFAULT_CHARSET for attachment in message.attachments: filename, content, mimetype = attachment if mimetype is None: mimetype, _ = mimetypes.guess_type(filename) if mimetype is None: mimetype = DEFAULT_ATTACHMENT_MIME_TYPE try: if isinstance(content, unicode): content = content.encode(str_encoding) except NameError: if isinstance(content, str): content = content.encode(str_encoding) base64_encoded_content = b64encode(content) formatted['attachments'].append({ 'name': filename, 'data': base64_encoded_content.decode('ascii'), 'type': mimetype }) if hasattr(message, 'substitution_data'): formatted['substitution_data'] = message.substitution_data if hasattr(message, 'campaign'): formatted['campaign'] = message.campaign if message.extra_headers: formatted['custom_headers'] = message.extra_headers if 'X-MSYS-API' in message.extra_headers: import json msys_api = json.loads(message.extra_headers['X-MSYS-API']) if msys_api and msys_api.get('options', {}).get('transactional', False): # noqa: E501 formatted['transactional'] = True super(SparkPostMessage, self).__init__(formatted) python-sparkpost-1.3.7/sparkpost/exceptions.py000066400000000000000000000022301341365572200216550ustar00rootroot00000000000000class SparkPostException(Exception): pass class SparkPostAPIException(SparkPostException): "Handle 4xx and 5xx errors from the SparkPost API" def __init__(self, response, *args, **kwargs): # noinspection PyBroadException try: errors = response.json()['errors'] error_template = "{message} Code: {code} Description: {desc} \n" errors = [error_template.format(message=e.get('message', ''), code=e.get('code', 'none'), desc=e.get('description', 'none')) for e in errors] # TODO: select exception to catch here except: # noqa: E722 errors = [response.text or ""] self.status = response.status_code self.response = response self.errors = errors message = """Call to {uri} returned {status_code}, errors: {errors} """.format( uri=response.url, status_code=response.status_code, errors='\n'.join(errors) ) super(SparkPostAPIException, self).__init__(message, *args, **kwargs) python-sparkpost-1.3.7/sparkpost/metrics.py000066400000000000000000000013341341365572200211460ustar00rootroot00000000000000from .base import Resource, RequestsTransport class Metrics(object): "Wrapper for sub-resources" def __init__(self, base_uri, api_key, transport_class=RequestsTransport): self.base_uri = "%s/%s" % (base_uri, 'metrics') self.campaigns = Campaigns(self.base_uri, api_key, transport_class) self.domains = Domains(self.base_uri, api_key, transport_class) class Campaigns(Resource): key = 'campaigns' def list(self, **kwargs): results = self.request('GET', self.uri, **kwargs) return results['campaigns'] class Domains(Resource): key = 'domains' def list(self, **kwargs): results = self.request('GET', self.uri, **kwargs) return results['domains'] python-sparkpost-1.3.7/sparkpost/recipient_lists.py000066400000000000000000000071271341365572200227060ustar00rootroot00000000000000import json from .base import Resource class RecipientLists(Resource): """ RecipientLists class used to create, update, delete, list and get recipient lists. For detailed request and response formats, see the `Recipient Lists API documentation `_. """ key = 'recipient-lists' def _translate_keys(self, **kwargs): model = {} if 'id' in kwargs: model['id'] = kwargs.get('id') model['name'] = kwargs.get('name') model['description'] = kwargs.get('description') model['attributes'] = kwargs.get('attributes') model['recipients'] = kwargs.get('recipients') return model def create(self, **kwargs): """ Create a recipient list based on the supplied parameters :param str id: ID used to reference the recipient list :param str name: Editable display name :param str description: Detailed description of the recipient list :param dict attributes: Arbitrary metadata related to the list :param list recipients: Array of recipient dicts :returns: a ``dict`` with the ID, name, and number of accepted and rejected recipients :raises: :exc:`SparkPostAPIException` if API call fails """ payload = self._translate_keys(**kwargs) results = self.request('POST', self.uri, data=json.dumps(payload)) return results def update(self, list_id, **kwargs): """ Update a recipient list by ID based on the supplied parameters :param str list_id: ID of the recipient list you want to update :param str name: Editable display name :param str description: Detailed description of the recipient list :param dict attributes: Arbitrary metadata related to the list :param list recipients: Array of recipient dicts :returns: a ``dict`` with the ID, name, and number of accepted and rejected recipients :raises: :exc:`SparkPostAPIException` if API call fails """ uri = "%s/%s" % (self.uri, list_id) payload = self._translate_keys(**kwargs) results = self.request('PUT', uri, data=json.dumps(payload)) return results def delete(self, list_id): """ Delete a recipient list by ID :param str list_id: ID of the recipient list you want to delete :returns: empty ``dict`` :raises: :exc:`SparkPostAPIException` if recipient list is not found or if recipient list is in use """ uri = "%s/%s" % (self.uri, list_id) results = self.request('DELETE', uri) return results def get(self, list_id, show_recipients=None): """ Get a recipient list by ID :param str list_id: ID of the recipient list you want to retrieve :param bool show_recipients: If True, returns attributes for all recipients :returns: the requested recipient list if found :raises: :exc:`SparkPostAPIException` if recipient list is not found """ uri = "%s/%s" % (self.uri, list_id) params = {} if show_recipients is not None: params['show_recipients'] = str(show_recipients).lower() results = self.request('GET', uri, params=params) return results def list(self): """ Get a list of your recipient lists :returns: list of recipient lists :raises: :exc:`SparkPostAPIException` if API call fails """ results = self.request('GET', self.uri) return results python-sparkpost-1.3.7/sparkpost/suppression_list.py000066400000000000000000000067171341365572200231370ustar00rootroot00000000000000import json from .base import Resource class SuppressionList(Resource): """ SuppressionList class used to search, get and modify suppression status. For detailed request and response formats, see the `Suppresion List API documentation `_. """ key = 'suppression-list' def list(self, **kwargs): """ List supression list entries based on the supplied parameters :param datetime from_date: DateTime to start listing :param datetime to_date: DateTime to end listing :param list types: Types of entries to return :param int limit: Maximum number of entries to return :returns: a ``list`` of entries :raises: :exc:`SparkPostAPIException` if API call fails """ key_map = { 'from_date': 'from', 'to_date': 'to', 'types': 'types', 'sources': 'sources', 'limit': 'limit' } params = dict([(key_map[i], kwargs[i]) for i in list(key_map.keys()) if i in kwargs]) results = self.request('GET', self.uri, params=params) return results def get(self, email): """ Retrieve a suppression list entry for a specific recipient by email :param str email: Email of the recipient whose status you want to check_status :returns: a suppression list entry :raises: :exc:`SparkPostAPIException` if API call fails """ uri = "%s/%s" % (self.uri, email) results = self.request('GET', uri) return results def _upsert(self, status): uri = self.uri if isinstance(status, dict): # single upsert, update uri and remove email property uri = "%s/%s" % (self.uri, status.pop("email", None)) else: status = {"recipients": status} results = self.request('PUT', uri, data=json.dumps(status)) return results def create(self, entry): """ Create a suppression list entry. :param dict|list status: If dict it is a single entry to create ``{ 'email': 'test@test.com', 'transactional': True, 'non_transactional': True, 'description': 'Test description' }``, if list it is multiple entries to create :returns: a ``dict`` with a message :raises: :exc:`SparkPostAPIException` if API call fails """ return self._upsert(entry) def update(self, entry): """ Update a suppression list entry. :param dict|list status: If dict it is a single entry to update ``{ 'email': 'test@test.com', 'transactional': True, 'non_transactional': True, 'description': 'Test description' }``, if list it is multiple entries to update :returns: a ``dict`` with a message :raises: :exc:`SparkPostAPIException` if API call fails """ return self._upsert(entry) def delete(self, email): """ Delete the suppression status for a specific recipient by email :param str email: Email of the recipient whose status you want to remove :returns: TODO :raises: :exc:`SparkPostAPIException` if API call fails """ uri = "%s/%s" % (self.uri, email) results = self.request('DELETE', uri) return results python-sparkpost-1.3.7/sparkpost/templates.py000066400000000000000000000160771341365572200215100ustar00rootroot00000000000000import json from .base import Resource class Templates(Resource): """ Templates class used to create, update, delete, list and get templates. For detailed request and response formats, see the `Templates API documentation `_. """ key = 'templates' def _translate_keys(self, **kwargs): model = { 'content': {}, 'options': {} } if 'id' in kwargs: model['id'] = kwargs.get('id') model['name'] = kwargs.get('name') model['description'] = kwargs.get('description') model['published'] = kwargs.get('published') model['options']['open_tracking'] = kwargs.get('track_opens') model['options']['click_tracking'] = kwargs.get('track_clicks') model['options']['transactional'] = kwargs.get('is_transactional') model['content']['html'] = kwargs.get('html') model['content']['text'] = kwargs.get('text') model['content']['subject'] = kwargs.get('subject') model['content']['from'] = kwargs.get('from_email') model['content']['reply_to'] = kwargs.get('reply_to') model['content']['headers'] = kwargs.get('custom_headers') return model def create(self, **kwargs): """ Create a template based on the supplied parameters :param str id: ID used to reference the template :param str name: Editable display name :param str description: Detailed description of the template :param bool published: Defaults to False. Whether the template is a published or draft version :param bool track_opens: Defaults to transmission level setting. Used to track opens of transmission :param bool track_clicks: Defaults to transmission level setting. Used to track clicks of transmission :param bool is_transactional: Defaults to transmission level setting. Distinguishes between transactional and non-transactional messages for unsubscribe and suppression purposes :param str html: HTML part of template :param str text: Text part of template :param str subject: Subject of template :param str from_email: Friendly from of template, domain must be a verified sending domain to your account or template create will fail :param str reply_to: Reply to of template :param dict custom_headers: Used to set any headers associated with template :returns: a ``dict`` with the ID :raises: :exc:`SparkPostAPIException` if template uses an unverified sending domain or there's a syntax error in the content """ payload = self._translate_keys(**kwargs) results = self.request('POST', self.uri, data=json.dumps(payload)) return results def update(self, template_id, **kwargs): """ Update a template by ID based on the supplied parameters :param str template_id: ID of the template you want to retrieve :param str name: Editable display name :param str description: Detailed description of the template :param bool published: Defaults to False. Whether the template is a published or draft version :param bool track_opens: Defaults to transmission level setting. Used to track opens of transmission :param bool track_clicks: Defaults to transmission level setting. Used to track clicks of transmission :param bool is_transactional: Defaults to transmission level setting. Distinguishes between transactional and non-transactional messages for unsubscribe and suppression purposes :param str html: HTML part of template :param str text: Text part of template :param str subject: Subject of template :param str from_email: Friendly from of template, domain must be a verified sending domain to your account or template create will fail :param str reply_to: Reply to of template :param dict custom_headers: Used to set any headers associated with template :returns: TODO :raises: :exc:`SparkPostAPIException` if template is not found """ uri = "%s/%s" % (self.uri, template_id) payload = self._translate_keys(**kwargs) results = self.request('PUT', uri, data=json.dumps(payload)) return results def delete(self, template_id): """ Delete a template by ID :param str template_id: ID of the template you want to delete :returns: TODO :raises: :exc:`SparkPostAPIException` if template is not found or if template is in use """ uri = "%s/%s" % (self.uri, template_id) results = self.request('DELETE', uri) return results def get(self, template_id, draft=None): """ Get a template by ID :param str template_id: ID of the template you want to retrieve :param bool draft: Defaults to None. If True, returns the most recent draft template. If False, returns the most recent published template. If None, returns the most recent template version regardless of draft or published. :returns: the requested template if found :raises: :exc:`SparkPostAPIException` if template is not found """ uri = "%s/%s" % (self.uri, template_id) params = {} if draft is not None: params['draft'] = str(draft).lower() results = self.request('GET', uri, params=params) return results def list(self): """ Get a list of your templates :returns: list of templates :raises: :exc:`SparkPostAPIException` if API call fails """ results = self.request('GET', self.uri) return results def preview(self, template_id, substitution_data, draft=None): """ Get a preivew of your template by ID with the provided substitution_data :param str template_id: ID of the template you want to retrieve :param dict substitution_data: data to be substituted in the template content :param bool draft: Defaults to None. If True, previews the most recent draft template. If False, previews the most recent published template. If None, previews the most recent template version regardless of draft or published. :returns: the requested template if found with content expanded using substitution data provided :raises: :exc:`SparkPostAPIException` if API call fails """ uri = "%s/%s/preview" % (self.uri, template_id) params = {} if draft is not None: params['draft'] = str(draft).lower() data = json.dumps({'substitution_data': substitution_data}) results = self.request('POST', uri, params=params, data=data) return results python-sparkpost-1.3.7/sparkpost/tornado/000077500000000000000000000000001341365572200205735ustar00rootroot00000000000000python-sparkpost-1.3.7/sparkpost/tornado/__init__.py000066400000000000000000000011301341365572200226770ustar00rootroot00000000000000import sparkpost from .exceptions import SparkPostAPIException from .base import TornadoTransport from .transmissions import Transmissions __all__ = ["SparkPost", "TornadoTransport", "SparkPostAPIException", "Transmissions"] class SparkPost(sparkpost.SparkPost): TRANSPORT_CLASS = TornadoTransport def __init__(self, *args, **kwargs): super(SparkPost, self).__init__(*args, **kwargs) self.transmissions = Transmissions(self.base_uri, self.api_key, self.TRANSPORT_CLASS) self.transmission = self.transmissions python-sparkpost-1.3.7/sparkpost/tornado/base.py000066400000000000000000000022051341365572200220560ustar00rootroot00000000000000import json from tornado import gen from tornado.httpclient import AsyncHTTPClient, HTTPError from .exceptions import SparkPostAPIException class TornadoTransport(object): @gen.coroutine def request(self, method, uri, headers, **kwargs): if "data" in kwargs: kwargs["body"] = kwargs.pop("data") client = AsyncHTTPClient() try: response = yield client.fetch(uri, method=method, headers=headers, **kwargs) except HTTPError as ex: raise SparkPostAPIException(ex.response) if response.code == 204: raise gen.Return(True) if response.code == 200: result = None # noinspection PyBroadException try: result = json.loads(response.body.decode("utf-8")) # TODO: select exception to catch here except: # noqa: E722 pass if result: if 'results' in result: raise gen.Return(result['results']) raise gen.Return(result) raise SparkPostAPIException(response) python-sparkpost-1.3.7/sparkpost/tornado/exceptions.py000066400000000000000000000022221341365572200233240ustar00rootroot00000000000000import json from ..exceptions import SparkPostAPIException as RequestsSparkPostAPIException class SparkPostAPIException(RequestsSparkPostAPIException): def __init__(self, response, *args, **kwargs): errors = None # noinspection PyBroadException try: data = json.loads(response.body.decode("utf-8")) if data: errors = data['errors'] errors = [e['message'] + ': ' + e.get('description', '') for e in errors] # TODO: select exception to catch here except: # noqa: E722 pass if not errors: errors = [response.body.decode("utf-8") or ""] self.status = response.code self.response = response self.errors = errors message = """Call to {uri} returned {status_code}, errors: {errors} """.format( uri=response.effective_url, status_code=response.code, errors='\n'.join(errors) ) super(RequestsSparkPostAPIException, self).__init__(message, *args, **kwargs) python-sparkpost-1.3.7/sparkpost/tornado/transmissions.py000066400000000000000000000004401341365572200240570ustar00rootroot00000000000000from .utils import wrap_future from ..transmissions import Transmissions as SyncTransmissions class Transmissions(SyncTransmissions): def get(self, transmission_id): results = self._fetch_get(transmission_id) return wrap_future(results, lambda f: f["transmission"]) python-sparkpost-1.3.7/sparkpost/tornado/utils.py000066400000000000000000000005151341365572200223060ustar00rootroot00000000000000from tornado.concurrent import Future def wrap_future(future, convert): wrapper = Future() def handle_future(future): try: wrapper.set_result(convert(future.result())) except Exception as ex: wrapper.set_exception(ex) future.add_done_callback(handle_future) return wrapper python-sparkpost-1.3.7/sparkpost/transmissions.py000066400000000000000000000300631341365572200224150ustar00rootroot00000000000000import base64 import copy import json import warnings from email.utils import parseaddr from .base import Resource from .exceptions import SparkPostException try: string_types = basestring except NameError: string_types = str # Python 3 doesn't have basestring class Transmissions(Resource): """ Transmission class used to send, list and get transmissions. For detailed request and response formats, see the `Transmissions API documentation `_. """ key = 'transmissions' def _translate_keys(self, **kwargs): model = { 'content': {}, 'options': {}, 'recipients': {} } model['description'] = kwargs.get('description') model['return_path'] = kwargs.get('return_path') model['campaign_id'] = kwargs.get('campaign') model['metadata'] = kwargs.get('metadata') model['substitution_data'] = kwargs.get('substitution_data') model['options']['start_time'] = kwargs.get('start_time') model['options']['open_tracking'] = kwargs.get('track_opens') model['options']['click_tracking'] = kwargs.get('track_clicks') model['options']['transactional'] = kwargs.get('transactional') model['options']['sandbox'] = kwargs.get('use_sandbox') model['options']['skip_suppression'] = kwargs.get('skip_suppression') model['options']['ip_pool'] = kwargs.get('ip_pool') model['options']['inline_css'] = kwargs.get('inline_css') rfc822 = kwargs.get('email_rfc822') if rfc822: model['content']['email_rfc822'] = rfc822 else: model['content']['headers'] = kwargs.get('custom_headers', {}) model['content']['use_draft_template'] = \ kwargs.get('use_draft_template', False) model['content']['reply_to'] = kwargs.get('reply_to') model['content']['subject'] = kwargs.get('subject') from_email = kwargs.get('from_email') if isinstance(from_email, string_types): from_email = self._parse_address(from_email) model['content']['from'] = from_email model['content']['html'] = kwargs.get('html') model['content']['text'] = kwargs.get('text') model['content']['template_id'] = kwargs.get('template') attachments = kwargs.get('attachments', []) model['content']['attachments'] = self._extract_attachments( attachments) if 'inline_images' in kwargs: inline_images = kwargs['inline_images'] model['content']['inline_images'] = self._extract_attachments( inline_images) recipient_list = kwargs.get('recipient_list') if recipient_list: model['recipients']['list_id'] = recipient_list else: recipients = kwargs.get('recipients', []) recipients = self._extract_recipients(recipients) cc = kwargs.get('cc') bcc = kwargs.get('bcc') if cc: model['content']['headers']['CC'] = ','.join(cc) cc_copies = self._format_copies(recipients, cc) recipients = recipients + cc_copies if bcc: bcc_copies = self._format_copies(recipients, bcc) recipients = recipients + bcc_copies model['recipients'] = recipients return model def _format_copies(self, recipients, copies): formatted_copies = [] if len(recipients) > 0: formatted_copies = self._extract_recipients(copies) main_recipient = copy.deepcopy(recipients[0]) main_recipient.pop('address') for recipient in formatted_copies: recipient['address'].update({ 'header_to': self._format_header_to(recipients[0]) }) recipient.update(**main_recipient) return formatted_copies def _format_header_to(self, recipient): if 'name' in recipient['address']: return '"{name}" <{email}>'.format( name=recipient['address']['name'], email=recipient['address']['email'] ) return recipient['address']['email'] def _extract_attachments(self, attachments): formatted_attachments = [] for attachment in attachments: formatted_attachment = {} formatted_attachment['type'] = attachment.get('type') formatted_attachment['name'] = attachment.get('name') if 'filename' in attachment: formatted_attachment['data'] = self._get_base64_from_file( attachment['filename']) else: formatted_attachment['data'] = attachment.get('data') formatted_attachments.append(formatted_attachment) return formatted_attachments def _get_base64_from_file(self, filename): with open(filename, "rb") as a_file: encoded_string = base64.b64encode(a_file.read()).decode("ascii") return encoded_string def _parse_address(self, address): name, email = parseaddr(address) parsed_address = { 'email': email } if name: parsed_address['name'] = name return parsed_address def _extract_recipients(self, recipients): if not (isinstance(recipients, (list, dict))): raise SparkPostException('recipients must be a list or dict') formatted_recipients = [] for recip in recipients: if isinstance(recip, string_types): formatted_recipients.append({ 'address': self._parse_address(recip) }) else: formatted_recipients.append(recip) return formatted_recipients def send(self, **kwargs): """ Send a transmission based on the supplied parameters :param list|dict recipients: List of email addresses, or a dict: ``{'address': {'name': 'Kyla', 'email': 'hello@example.com' }}`` :param str recipient_list: ID of recipient list. If this is set, the `recipients` param will be ignored :param cc: List of email addresses to send carbon copy to :param bcc: List of email addresses to send blind carbon copy to :param str template: ID of template to be used. Setting a template overrides the HTML and text params :param bool use_draft_template: Defaults to False. Set to true if you want to send a template that is a draft :param str html: HTML part of transmission :param str text: Text part of transmission :param str subject: Subject of transmission :param str from_email: Email that the transmission comes from. The domain must be a verified sending domain belonging to your account or the transmission will fail. You can pass a from email or both from name and from email - `testing@example.com` or `Test Email ` will both work. :param str reply_to: Reply to of transmission :param str return_path: Email address to use for envelope FROM. The domain part of the return_path address must be a CNAME-verified sending domain. :param str description: Description of transmission :param str campaign: Campaign of transmission :param dict metadata: Any data you want to send along with transmission, used in WebHooks :param dict substitution_data: Corresponds to substitutions in html/text content. See `substitutions reference `_. :param attachments: List of dicts. For example: .. code-block:: python dict( type='application/pdf', name='document.pdf', data='base64 encoded string' ) Replace `data` with `filename` if you want the library to perform the base64 conversion. For example: .. code-block:: python dict( type='application/pdf', name='document.pdf', filename='/full/path/to/document.pdf' ) :param inline_images: List of dicts. For example: .. code-block:: python dict( type='image/png', name='imageCID', data='base64 encoded string' ) Replace `data` with `filename` if you want the library to perform the base64 conversion. For example: .. code-block:: python dict( type='image/png', name='imageCID', filename='/full/path/to/image.png' ) :param str start_time: Delay generation of messages until this datetime. Format YYYY-MM-DDTHH:MM:SS+-HH:MM. Example: '2015-02-11T08:00:00-04:00'. :param bool track_opens: Defaults to True. Used to track opens of transmission :param bool track_clicks: Defaults to True. Used to track clicks of transmission :param bool use_sandbox: Flag must be set to use sandbox domain instead of verified sending domain. Limited to a lifetime of 50 transmissions with this domain :param bool transactional: Whether message is transactional or non-transactional for unsubscribe and suppression purposes :param bool skip_suppression: Whether or not to ignore customer suppression rules, for this transmission only. Only applicable if your configuration supports this parameter. (Enterprise only) :param str ip_pool: The ID of an IP pool associated with your account :param bool inline_css: Whether or not to perform CSS inlining :param dict custom_headers: Used to set any headers associated with transmission. See `header notes `_ :returns: a ``dict`` with the transmission ID and number of accepted and rejected recipients :raises: :exc:`SparkPostAPIException` if transmission cannot be sent """ payload = self._translate_keys(**kwargs) data = json.dumps(payload) results = self.request('POST', self.uri, data=data) return results def _fetch_get(self, transmission_id): uri = "%s/%s" % (self.uri, transmission_id) results = self.request('GET', uri) return results def get(self, transmission_id): """ Get a transmission by ID :param str transmission_id: ID of the transmission you want to retrieve :returns: the requested transmission, if found :raises: :exc:`SparkPostAPIException` if transmission is not found """ results = self._fetch_get(transmission_id) return results['transmission'] def list(self, **kwargs): """ Get a list of your transmissions. This method is deprecated. :param campaign_id: ID of the campaign used by the transmissions :param template_id: ID of the template used by the transmissions :returns: list of transmissions :raises: :exc:`SparkPostAPIException` if API call fails """ warn_msg = 'This method is deprecated.' warnings.warn(warn_msg, DeprecationWarning) return self.request('GET', self.uri, params=kwargs) def delete(self, transmission_id): """ Delete a transmission by ID :param str transmission_id: ID of the transmission you want to delete :returns: {} if transmission is deleted :raises: :exc:`SparkPostAPIException` if transmission is not found or Canceled """ uri = "%s/%s" % (self.uri, transmission_id) results = self.request('DELETE', uri) return results python-sparkpost-1.3.7/test-requirements.txt000066400000000000000000000001251341365572200213360ustar00rootroot00000000000000flake8 pytest==2.8.7 pytest-cov==1.8.1 requests==2.20.1 responses==0.3.0 mock==2.0.0 python-sparkpost-1.3.7/test/000077500000000000000000000000001341365572200160565ustar00rootroot00000000000000python-sparkpost-1.3.7/test/__init__.py000066400000000000000000000000001341365572200201550ustar00rootroot00000000000000python-sparkpost-1.3.7/test/assets/000077500000000000000000000000001341365572200173605ustar00rootroot00000000000000python-sparkpost-1.3.7/test/assets/sparkpostdev.png000066400000000000000000000146261341365572200226240ustar00rootroot00000000000000PNG  IHDR\]IDATx^=lי`z] 6U-a$:j-AE"P1 #€$ N BM ٭6qKfj#=25w3<@>?~ӟ]G{#<MxЛ7@oބ z&<MxЛ7@oބ z&<MxЛ7@oބ z&<MxЛ7@oބ z&<[kWT/?X{ fTӣ?˗־k?iEx3{{Z{k.\X?ݼyGS|>_;:::筭7>Ok믴|'W766ȟ׮\tOAx)m=Xw|p<|Ox@KHVc)ZbtшX1n4<ŋ߂#4yMS3ggi&kB!fYvw TIxbǣGO?Psa=zG0uz88XnޣGln= !t 1Z{: bg!F4>ӜE2u*!'-p`ƒ["Gcs'GxX]5DO늾 Vo-1V HOxev7C]&ku} b|x]UE1FEC$O#>Cjz!8𠷸$ΛLƒ^nΫ*ښ+A/Ullꃮ4+`✮iWkC;wCƒN,z{U>o qͫ<b%#<8W,߀$Jlm<!<*ow|xh&l6~해Ƀ)eynF)Gh&# EQTI@vwߛ7<*2IۘSI8q8NUJ!<*2ՖU(F#:]N.G./2 Kx*XsiEC0*J*Ue\£`=iizku-(Xy赧ܜV:Rwʗ =~o{+(\귿Ql%3? $yPuCxT"zЩ톓 I?<޽(H.xЙF/'&!<*˗ߘY\r {`£2T!ޘ>C{8\ڢ̃_&_{Z9 ~b(# 򆹎yCNJtcpDՑSEj V}rUʪN£B9VA-Uuʱ Va(v֪jܽYUXcF“r X]eUGŢm>O>sGDpF|=DEGrn;a|S6UghbC`.NICxT.OxwnJ$5B#̨K?ϿяFe|⋿fWo7xpv?v!sh47:ם;o Ӊrĩv%K#Oݸ69Yb1$&Hello There

' return { 'total_accepted_recipients': 0, 'total_rejected_recipients': 0 } with mock.patch.object(Transmissions, 'send') as mock_send: mock_send.side_effect = new_send send_mail( 'test subject', 'hello there', 'from@example.com', ['to@example.com'], html_message='

Hello There

' ) def test_send_plain_mail_after_html_mail(): SPARKPOST_OPTIONS = { 'track_opens': False, 'track_clicks': False, 'transactional': True, } reconfigure_settings(SPARKPOST_OPTIONS=SPARKPOST_OPTIONS) def new_send(**kwargs): assert kwargs['text'] == 'hello there' assert kwargs['html'] == '

Hello There

' return { 'total_accepted_recipients': 0, 'total_rejected_recipients': 0 } def new_send_text_only(**kwargs): assert kwargs['text'] == 'hello there again in text only' assert "html" not in kwargs return { 'total_accepted_recipients': 0, 'total_rejected_recipients': 0 } with mock.patch.object(Transmissions, 'send') as mock_send: mock_send.side_effect = new_send send_mail( 'test subject', 'hello there', 'from@example.com', ['to@example.com'], html_message='

Hello There

' ) mock_send.side_effect = new_send_text_only send_mail( 'test subject 2', 'hello there again in text only', 'from@example.com', ['to@example.com'], ) def test_unsupported_content_types(): params = get_params() with pytest.raises(UnsupportedContent): mail = EmailMultiAlternatives( params['subject'], 'plain text', params['from_email'], params['recipient_list']) mail.attach_alternative('non-plain content', 'text/alien') mail.send() def test_settings_options(): SPARKPOST_OPTIONS = { 'track_opens': False, 'track_clicks': False, 'transactional': True, } reconfigure_settings(SPARKPOST_OPTIONS=SPARKPOST_OPTIONS) with mock.patch.object(Transmissions, 'send'): mailer(get_params()) expected_kargs = get_params().copy() expected_kargs["text"] = expected_kargs["message"] expected_kargs["recipients"] = expected_kargs["recipient_list"] del expected_kargs["message"] del expected_kargs["recipient_list"] expected_kargs.update(SPARKPOST_OPTIONS) Transmissions.send.assert_called_with(**expected_kargs) python-sparkpost-1.3.7/test/django/test_message.py000066400000000000000000000144631341365572200223650ustar00rootroot00000000000000from django.conf import settings from django.core.mail import EmailMultiAlternatives from django.core.mail.message import EmailMessage from django.utils.functional import empty from sparkpost.django.message import SparkPostMessage from .utils import at_least_version def reconfigure_settings(**new_settings): old_settings = settings._wrapped settings._wrapped = empty settings.configure(default_settings=old_settings, **new_settings) reconfigure_settings( DEFAULT_CHARSET='utf-8' ) base_options = dict( subject='Test', body='Testing', from_email='test@from.com', to=['recipient@example.com'] ) def message(**options): options.update(base_options) email_message = EmailMessage(**options) return SparkPostMessage(email_message) def multipart_message(**options): options.update(base_options) email_message = EmailMultiAlternatives(**options) email_message.attach_alternative('

Testing

', 'text/html') return SparkPostMessage(email_message) base_expected = dict( recipients=['recipient@example.com'], from_email='test@from.com', subject='Test', text='Testing' ) def test_minimal(): assert message() == base_expected def test_multipart(): expected = dict( html='

Testing

' ) expected.update(base_expected) assert multipart_message() == expected def test_cc_bcc(): expected = dict( cc=['ccone@example.com'], bcc=['bccone@example.com'] ) expected.update(base_expected) options = dict(cc=['ccone@example.com'], bcc=['bccone@example.com']) assert message(**options) == expected def test_attachment(): email_message = EmailMessage(**base_options) email_message.attach('file.txt', 'test content', 'text/plain') actual = SparkPostMessage(email_message) expected = dict( attachments=[ { 'name': 'file.txt', 'data': 'dGVzdCBjb250ZW50', 'type': 'text/plain' } ] ) expected.update(base_expected) assert actual == expected def test_attachment_unicode(): email_message = EmailMessage(**base_options) email_message.attach('file.txt', u'test content', 'text/plain') actual = SparkPostMessage(email_message) expected = dict( attachments=[ { 'name': 'file.txt', 'data': 'dGVzdCBjb250ZW50', 'type': 'text/plain' } ] ) expected.update(base_expected) assert actual == expected def test_attachment_guess_mimetype(): email_message = EmailMessage(**base_options) email_message.attach('file.txt', 'test content') actual = SparkPostMessage(email_message) expected = dict( attachments=[ { 'name': 'file.txt', 'data': 'dGVzdCBjb250ZW50', 'type': 'text/plain' } ] ) expected.update(base_expected) assert actual == expected def test_attachment_guess_mimetype_fallback(): email_message = EmailMessage(**base_options) email_message.attach('file', 'test content') actual = SparkPostMessage(email_message) expected = dict( attachments=[ { 'name': 'file', 'data': 'dGVzdCBjb250ZW50', 'type': 'application/octet-stream' } ] ) expected.update(base_expected) assert actual == expected def test_content_subtype(): email_message = EmailMessage( to=['to@example.com'], from_email='test@from.com', body='

Testing

' ) email_message.content_subtype = 'html' actual = SparkPostMessage(email_message) expected = dict( recipients=['to@example.com'], from_email='test@from.com', html='

Testing

' ) assert actual == expected def test_template(): email_message = EmailMessage( to=['to@example.com'], from_email='test@from.com' ) email_message.template = 'template-id' actual = SparkPostMessage(email_message) expected = dict( recipients=['to@example.com'], from_email='test@from.com', template='template-id' ) assert actual == expected def test_campaign(): email_message = EmailMessage(**base_options) email_message.campaign = 'campaign-id' actual = SparkPostMessage(email_message) expected = dict( campaign='campaign-id' ) expected.update(base_expected) assert actual == expected def test_substitution_data(): email_message = EmailMessage( to=[ { "address": "to@example.com", "substitution_data": { "key": "value" } } ], from_email='test@from.com' ) email_message.template = 'template-id' email_message.substitution_data = {"key2": "value2"} actual = SparkPostMessage(email_message) expected = dict( recipients=[ { "address": "to@example.com", "substitution_data": { "key": "value" } } ], from_email='test@from.com', template='template-id', substitution_data={"key2": "value2"} ) assert actual == expected if at_least_version('1.8'): def test_reply_to(): expected = dict( reply_to='replyone@example.com,replytwo@example.com' ) expected.update(base_expected) assert message(reply_to=['replyone@example.com', 'replytwo@example.com']) == expected def test_extra_headers(): email_message = EmailMessage(**base_options) email_message.extra_headers['FOO'] = 'bar' actual = SparkPostMessage(email_message) expected = dict( custom_headers={'FOO': 'bar'}, ) expected.update(base_expected) assert actual == expected def test_transactional(): email_message = EmailMessage(**base_options) import json msys_api = json.dumps({'options': {'transactional': True}}) email_message.extra_headers['X-MSYS-API'] = msys_api actual = SparkPostMessage(email_message) expected = dict( custom_headers={'X-MSYS-API': msys_api}, transactional=True, ) expected.update(base_expected) assert actual == expected python-sparkpost-1.3.7/test/django/utils.py000066400000000000000000000002561341365572200210350ustar00rootroot00000000000000from distutils.version import StrictVersion from django import get_version def at_least_version(version): return StrictVersion(get_version()) > StrictVersion(version) python-sparkpost-1.3.7/test/test_base.py000066400000000000000000000061451341365572200204070ustar00rootroot00000000000000import pytest import responses from sparkpost.base import Resource from sparkpost.exceptions import SparkPostAPIException fake_base_uri = 'https://fake-base.com' fake_api_key = 'fake-api-key' fake_resource_key = 'fake-resource-key' fake_uri = "%s/%s" % (fake_base_uri, fake_resource_key) def create_resource(): resource = Resource(fake_base_uri, fake_api_key) resource.key = fake_resource_key return resource def test_uri(): resource = create_resource() assert resource.uri == fake_uri @responses.activate def test_success_request(): responses.add( responses.GET, fake_uri, status=200, content_type='application/json', body='{}' ) resource = create_resource() results = resource.request('GET', resource.uri) assert results == {} @responses.activate def test_success_request_with_results(): responses.add( responses.GET, fake_uri, status=200, content_type='application/json', body='{"results": []}' ) resource = create_resource() results = resource.request('GET', resource.uri) assert results == [] @responses.activate def test_fail_request(): responses.add( responses.GET, fake_uri, status=500, content_type='application/json', body='{"errors": [{"message": "failure", "description": "desc"}]}' ) resource = create_resource() with pytest.raises(SparkPostAPIException): resource.request('GET', resource.uri) @responses.activate def test_fail_wrongjson_request(): responses.add( responses.GET, fake_uri, status=500, content_type='application/json', body='{"errors": ["Error!"]}' ) resource = create_resource() with pytest.raises(SparkPostAPIException): resource.request('GET', resource.uri) @responses.activate def test_fail_nojson_request(): responses.add( responses.GET, fake_uri, status=500, content_type='application/json', body='{"errors": ' ) resource = create_resource() with pytest.raises(SparkPostAPIException): resource.request('GET', resource.uri) @responses.activate def test_fail_no_errors(): responses.add( responses.GET, fake_uri, status=500, content_type='application/json', body='no errors' ) resource = create_resource() with pytest.raises(SparkPostAPIException): resource.request('GET', resource.uri) def test_fail_get(): resource = create_resource() with pytest.raises(NotImplementedError): resource.get() def test_fail_list(): resource = create_resource() with pytest.raises(NotImplementedError): resource.list() def test_fail_create(): resource = create_resource() with pytest.raises(NotImplementedError): resource.create() def test_fail_update(): resource = create_resource() with pytest.raises(NotImplementedError): resource.update() def test_fail_delete(): resource = create_resource() with pytest.raises(NotImplementedError): resource.delete() python-sparkpost-1.3.7/test/test_init.py000066400000000000000000000002741341365572200204350ustar00rootroot00000000000000import pytest from sparkpost import SparkPost from sparkpost.exceptions import SparkPostException def test_no_api_key(): with pytest.raises(SparkPostException): SparkPost() python-sparkpost-1.3.7/test/test_metrics.py000066400000000000000000000032751341365572200211440ustar00rootroot00000000000000import pytest import responses from sparkpost import SparkPost from sparkpost.exceptions import SparkPostAPIException @responses.activate def test_success_campaigns(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/metrics/campaigns', status=200, content_type='application/json', body='{"results": {"campaigns": []}}' ) sp = SparkPost('fake-key') results = sp.metrics.campaigns.list() assert results == [] @responses.activate def test_fail_campaigns(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/metrics/campaigns', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.metrics.campaigns.list() @responses.activate def test_success_domains(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/metrics/domains', status=200, content_type='application/json', body='{"results": {"domains": []}}' ) sp = SparkPost('fake-key') results = sp.metrics.domains.list() assert results == [] @responses.activate def test_fail_domains(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/metrics/domains', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.metrics.domains.list() python-sparkpost-1.3.7/test/test_recipient_lists.py000066400000000000000000000103561341365572200226740ustar00rootroot00000000000000import pytest import responses from sparkpost import SparkPost from sparkpost import RecipientLists from sparkpost.exceptions import SparkPostAPIException def test_translate_keys_with_id(): t = RecipientLists('uri', 'key') results = t._translate_keys(id='test_id') assert results['id'] == 'test_id' @responses.activate def test_success_create(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/recipient-lists', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.recipient_lists.create() assert results == 'yay' @responses.activate def test_fail_create(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/recipient-lists', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.recipient_lists.create() @responses.activate def test_success_update(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/recipient-lists/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.recipient_lists.update('foobar', name='foobar') assert results == 'yay' @responses.activate def test_fail_update(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/recipient-lists/foobar', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.recipient_lists.update('foobar', name='foobar') @responses.activate def test_success_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/recipient-lists/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.recipient_lists.delete('foobar') assert results == 'yay' @responses.activate def test_fail_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/recipient-lists/foobar', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.recipient_lists.delete('foobar') @responses.activate def test_success_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/recipient-lists/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.recipient_lists.get('foobar') assert results == "yay" @responses.activate def test_success_get_with_recipients(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/recipient-lists/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.recipient_lists.get('foobar', True) assert results == "yay" @responses.activate def test_fail_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/recipient-lists/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.recipient_lists.get('foobar') @responses.activate def test_success_list(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/recipient-lists', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') response = sp.recipient_lists.list() assert response == "yay" python-sparkpost-1.3.7/test/test_suppression_list.py000066400000000000000000000107071341365572200231210ustar00rootroot00000000000000import pytest import responses from sparkpost import SparkPost from sparkpost.exceptions import SparkPostAPIException @responses.activate def test_success_list(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/suppression-list', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.suppression_list.list() assert results == 'yay' @responses.activate def test_fail_list(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/suppression-list', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.suppression_list.list() @responses.activate def test_success_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=200, content_type='application/json', body='{"results": []}' ) sp = SparkPost('fake-key') results = sp.suppression_list.get('foobar') assert results == [] @responses.activate def test_fail_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.suppression_list.get('foobar') @responses.activate def test_success_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=204, content_type='application/json' ) sp = SparkPost('fake-key') results = sp.suppression_list.delete('foobar') assert results is True @responses.activate def test_fail_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.suppression_list.delete('foobar') @responses.activate def test_success_upsert(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.suppression_list._upsert({"email": "foobar"}) assert results == 'yay' @responses.activate def test_success_create(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.suppression_list.create({"email": "foobar"}) assert results == 'yay' @responses.activate def test_success_update(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.suppression_list.update({"email": "foobar"}) assert results == 'yay' @responses.activate def test_success_upsert_bulk(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/suppression-list', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.suppression_list._upsert([ {"email": "foobar1"}, {"email": "foobar2"} ]) assert results == 'yay' @responses.activate def test_fail_upsert(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/suppression-list/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.suppression_list._upsert({"email": "foobar"}) python-sparkpost-1.3.7/test/test_templates.py000066400000000000000000000132261341365572200214710ustar00rootroot00000000000000try: from urllib.parse import urlparse # TODO: select exception to catch here except: # noqa: E722 from urlparse import urlparse import pytest import responses from sparkpost import SparkPost from sparkpost import Templates from sparkpost.exceptions import SparkPostAPIException def test_translate_keys_with_id(): t = Templates('uri', 'key') results = t._translate_keys(id='test_id') assert results['id'] == 'test_id' @responses.activate def test_success_create(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/templates', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.templates.create() assert results == 'yay' @responses.activate def test_fail_create(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/templates', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.templates.create() @responses.activate def test_success_update(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/templates/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.templates.update('foobar', name='foobar') assert results == 'yay' @responses.activate def test_fail_update(): responses.add( responses.PUT, 'https://api.sparkpost.com/api/v1/templates/foobar', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.templates.update('foobar', name='foobar') @responses.activate def test_success_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/templates/foobar', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.templates.delete('foobar') assert results == 'yay' @responses.activate def test_fail_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/templates/foobar', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.templates.delete('foobar') @responses.activate def test_success_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/templates/foobar', status=200, content_type='application/json', body='{"results": {}}' ) sp = SparkPost('fake-key') results = sp.templates.get('foobar') assert results == {} @responses.activate def test_success_get_with_is_draft(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/templates/foobar?draft=true', match_querystring=True, status=200, content_type='application/json', body='{"results": {}}' ) sp = SparkPost('fake-key') results = sp.templates.get('foobar', True) assert results == {} @responses.activate def test_fail_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/templates/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.templates.get('foobar') @responses.activate def test_success_list(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/templates', status=200, content_type='application/json', body='{"results": []}' ) sp = SparkPost('fake-key') response = sp.templates.list() assert response == [] @responses.activate def test_success_preview(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/templates/foobar/preview', status=200, content_type='application/json', body='{"results": {}}' ) sp = SparkPost('fake-key') results = sp.templates.preview('foobar', {}) assert responses.calls[0].request.body == '{"substitution_data": {}}' assert results == {} @responses.activate def test_success_preview_with_draft(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/templates/foobar/preview?draft=true', match_querystring=True, status=200, content_type='application/json', body='{"results": {}}' ) sp = SparkPost('fake-key') results = sp.templates.preview('foobar', {}, True) parsed = urlparse(responses.calls[0].request.url) assert parsed.query == 'draft=true' assert results == {} @responses.activate def test_fail_preview(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/templates/foobar/preview', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.templates.preview('foobar', {}) python-sparkpost-1.3.7/test/test_transmissions.py000066400000000000000000000302541341365572200224070ustar00rootroot00000000000000import base64 import json import os import tempfile import warnings import pytest import responses import six from mock import patch from sparkpost import SparkPost from sparkpost import Transmissions from sparkpost.exceptions import SparkPostAPIException, SparkPostException def test_translate_keys_with_list(): t = Transmissions('uri', 'key') results = t._translate_keys(recipient_list='test') assert results['content']['use_draft_template'] is False assert isinstance(results['content']['headers'], dict) assert results['recipients'] == {'list_id': 'test'} def test_translate_keys_with_recips(): t = Transmissions('uri', 'key') results = t._translate_keys(recipients=['test', {'key': 'value'}, 'foobar']) assert results['recipients'] == [{'address': {'email': 'test'}}, {'key': 'value'}, {'address': {'email': 'foobar'}}] def test_exceptions_for_recipients(): t = Transmissions('uri', 'key') with pytest.raises(SparkPostException): t._translate_keys(recipients='test') def test_translate_keys_with_unicode_recips(): t = Transmissions('uri', 'key') results = t._translate_keys(recipients=[u'unicode_email@example.com', 'str_email@example.com']) assert results['recipients'] == [ {'address': {'email': 'unicode_email@example.com'}}, {'address': {'email': 'str_email@example.com'}} ] def test_translate_keys_for_email_parsing(): t = Transmissions('uri', 'key') results = t._translate_keys(recipients=['hansel@example.com', 'Gretel ']) assert results['recipients'] == [ {'address': {'email': 'hansel@example.com'}}, {'address': {'name': 'Gretel', 'email': 'gretel@example.com'}} ] def test_translate_keys_for_from_email(): t = Transmissions('uri', 'key') results = t._translate_keys(from_email='Testing ') assert results['content']['from'] == { 'name': 'Testing', 'email': 'testing@example.com' } def test_format_header_to(): t = Transmissions('uri', 'key') formatted = t._format_header_to(recipient={ 'address': {'email': 'primary@example.com'} }) assert formatted == 'primary@example.com' formatted = t._format_header_to(recipient={ 'address': {'name': 'Testing', 'email': 'primary@example.com'} }) assert formatted == '"Testing" ' def test_cc_with_sub_data(): t = Transmissions('uri', 'key') results = t._translate_keys( recipients=[{ 'address': {'email': 'primary@example.com'}, 'substitution_data': {'fake': 'data'} }], cc=['ccone@example.com'] ) assert results['recipients'] == [ { 'address': {'email': 'primary@example.com'}, 'substitution_data': {'fake': 'data'} }, { 'address': { 'email': 'ccone@example.com', 'header_to': 'primary@example.com' }, 'substitution_data': {'fake': 'data'} } ] def test_translate_keys_with_cc(): t = Transmissions('uri', 'key') results = t._translate_keys(recipients=['primary@example.com'], cc=['ccone@example.com']) assert results['recipients'] == [ {'address': {'email': 'primary@example.com'}}, {'address': {'email': 'ccone@example.com', 'header_to': 'primary@example.com'}}, ] assert results['content']['headers'] == { 'CC': 'ccone@example.com' } def test_translate_keys_with_multiple_cc(): t = Transmissions('uri', 'key') results = t._translate_keys(recipients=['primary@example.com'], cc=['ccone@example.com', 'cctwo@example.com']) assert results['recipients'] == [ {'address': {'email': 'primary@example.com'}}, {'address': {'email': 'ccone@example.com', 'header_to': 'primary@example.com'}}, {'address': {'email': 'cctwo@example.com', 'header_to': 'primary@example.com'}}, ] assert results['content']['headers'] == { 'CC': 'ccone@example.com,cctwo@example.com' } def test_translate_keys_with_bcc(): t = Transmissions('uri', 'key') results = t._translate_keys(recipients=['primary@example.com'], bcc=['bccone@example.com']) assert results['recipients'] == [ {'address': {'email': 'primary@example.com'}}, {'address': {'email': 'bccone@example.com', 'header_to': 'primary@example.com'}}, ] def test_translate_keys_with_inline_css(): t = Transmissions('uri', 'key') results = t._translate_keys(inline_css=True) assert results['options'].get('inline_css') is True def test_translate_keys_with_email_rfc822(): t = Transmissions('uri', 'key') # Build data using implicit cat, as max line length is enforced eml = ( 'To: wilma \n', 'From: fred \n', 'Subject: Bedrock declaration\n', 'MIME-Version: 1.0\n', 'Content-Type: text/plain; charset=utf-8; format=flowed\n', 'Content-Transfer-Encoding: 7bit\nContent-Language: en-GB\n', '\n', 'When in the Course of human events we yell yabba dabba doo.', ) # Just a selection. Don't test attribs with irregular naming non_rfc822_attrs = { 'headers': 'foo', 'reply_to': 'foo', 'subject': 'foo', 'html': 'foo', 'text': 'foo', } # Demonstrate that email_rfc822 overrides other content attributes test_content = {'email_rfc822': eml} test_content.update(non_rfc822_attrs) results = t._translate_keys(**test_content) for i in non_rfc822_attrs: assert results['content'].get(i) is None assert results['content'].get('email_rfc822') is not None @responses.activate def test_success_send(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = sp.transmission.send() assert results == 'yay' @responses.activate def test_success_send_with_attachments(): try: # Let's compare unicode for Python 2 / 3 compatibility test_content = six.u("Hello \nWorld\n") (_, temp_file_path) = tempfile.mkstemp() with open(temp_file_path, "w") as temp_file: temp_file.write(test_content) responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') attachment = { "name": "test.txt", "type": "text/plain", "filename": temp_file_path } results = sp.transmission.send(attachments=[attachment]) request_params = json.loads(responses.calls[0].request.body) content = base64.b64decode( request_params["content"]["attachments"][0]["data"]) # Let's compare unicode for Python 2 / 3 compatibility assert test_content == content.decode("ascii") assert results == 'yay' attachment = { "name": "test.txt", "type": "text/plain", "data": base64.b64encode( test_content.encode("ascii")).decode("ascii") } results = sp.transmission.send(attachments=[attachment]) request_params = json.loads(responses.calls[1].request.body) content = base64.b64decode( request_params["content"]["attachments"][0]["data"]) # Let's compare unicode for Python 2 / 3 compatibility assert test_content == content.decode("ascii") assert results == 'yay' finally: os.unlink(temp_file_path) @responses.activate def test_success_send_with_inline_images(): current_dir = os.path.abspath(os.path.dirname(__file__)) image_path = os.path.join(current_dir, 'assets', 'sparkpostdev.png') with open(image_path, "rb") as image: encoded_image = base64.b64encode(image.read()).decode("ascii") responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') image_data = { "name": "sparkpostdev", "type": "image/png", "filename": image_path } results = sp.transmission.send(inline_images=[image_data]) request_params = json.loads(responses.calls[0].request.body) content = request_params["content"]["inline_images"][0]["data"] assert encoded_image == content assert results == 'yay' image_data = { "name": "sparkpostdev", "type": "image/png", "data": encoded_image } results = sp.transmission.send(inline_images=[image_data]) request_params = json.loads(responses.calls[1].request.body) content = request_params["content"]["inline_images"][0]["data"] assert content == encoded_image assert results == 'yay' @responses.activate def test_fail_send(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/transmissions', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.transmission.send() @responses.activate def test_success_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions/foobar', status=200, content_type='application/json', body='{"results": {"transmission": {}}}' ) sp = SparkPost('fake-key') results = sp.transmission.get('foobar') assert results == {} @responses.activate def test_fail_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.transmission.get('foobar') @responses.activate @patch.object(warnings, 'warn') def test_success_list(mock_warn): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results": []}' ) sp = SparkPost('fake-key') response = sp.transmission.list() assert mock_warn.called assert response == [] @responses.activate def test_success_list_with_params(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions?template_id=abcd', status=200, content_type='application/json', body='{"results": []}', match_querystring=True ) sp = SparkPost('fake-key') response = sp.transmission.list(template_id='abcd') assert response == [] @responses.activate def test_success_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/transmissions/foobar', status=200, content_type='application/json', body='{}' ) sp = SparkPost('fake-key') results = sp.transmission.delete('foobar') assert results == {} @responses.activate def test_fail_delete(): responses.add( responses.DELETE, 'https://api.sparkpost.com/api/v1/transmissions/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "resource not found", "description": "Resource not found:transmission id foobar"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') sp.transmission.delete('foobar') python-sparkpost-1.3.7/test/tornado/000077500000000000000000000000001341365572200175245ustar00rootroot00000000000000python-sparkpost-1.3.7/test/tornado/__init__.py000066400000000000000000000000001341365572200216230ustar00rootroot00000000000000python-sparkpost-1.3.7/test/tornado/test_tornado.py000066400000000000000000000124211341365572200226030ustar00rootroot00000000000000import base64 import json import os import tempfile import pytest import six from sparkpost.tornado import SparkPost, SparkPostAPIException from tornado import ioloop from .utils import AsyncClientMock responses = AsyncClientMock() @responses.activate def test_success_send(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') results = ioloop.IOLoop().run_sync(sp.transmission.send) assert results == 'yay' @responses.activate def test_success_send_with_attachments(): try: # Let's compare unicode for Python 2 / 3 compatibility test_content = six.u("Hello \nWorld\n") (_, temp_file_path) = tempfile.mkstemp() with open(temp_file_path, "w") as temp_file: temp_file.write(test_content) responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results": "yay"}' ) sp = SparkPost('fake-key') attachment = { "name": "test.txt", "type": "text/plain", "filename": temp_file_path } def send(): return sp.transmission.send(attachments=[attachment]) results = ioloop.IOLoop().run_sync(send) request_params = json.loads(responses.calls[0].request.body) content = base64.b64decode( request_params["content"]["attachments"][0]["data"]) # Let's compare unicode for Python 2 / 3 compatibility assert test_content == content.decode("ascii") assert results == 'yay' attachment = { "name": "test.txt", "type": "text/plain", "data": base64.b64encode( test_content.encode("ascii")).decode("ascii") } def send(): return sp.transmission.send(attachments=[attachment]) results = ioloop.IOLoop().run_sync(send) request_params = json.loads(responses.calls[1].request.body) content = base64.b64decode( request_params["content"]["attachments"][0]["data"]) # Let's compare unicode for Python 2 / 3 compatibility assert test_content == content.decode("ascii") assert results == 'yay' finally: os.unlink(temp_file_path) @responses.activate def test_fail_send(): responses.add( responses.POST, 'https://api.sparkpost.com/api/v1/transmissions', status=500, content_type='application/json', body=""" {"errors": [{"message": "You failed", "description": "More Info"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') ioloop.IOLoop().run_sync(sp.transmission.send) @responses.activate def test_success_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions/foobar', status=200, content_type='application/json', body='{"results": {"transmission": {}}}' ) sp = SparkPost('fake-key') def send(): return sp.transmission.get('foobar') results = ioloop.IOLoop().run_sync(send, timeout=3) assert results == {} @responses.activate def test_fail_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions/foobar', status=404, content_type='application/json', body=""" {"errors": [{"message": "cant find", "description": "where you go"}]} """ ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') def send(): return sp.transmission.get('foobar') ioloop.IOLoop().run_sync(send, timeout=3) @responses.activate def test_nocontent_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions', status=204, content_type='application/json', body='' ) sp = SparkPost('fake-key') response = ioloop.IOLoop().run_sync(sp.transmission.list) assert response is True @responses.activate def test_brokenjson_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results":' ) with pytest.raises(SparkPostAPIException): sp = SparkPost('fake-key') ioloop.IOLoop().run_sync(sp.transmission.list) @responses.activate def test_noresults_get(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"ok": false}' ) sp = SparkPost('fake-key') response = ioloop.IOLoop().run_sync(sp.transmission.list) assert response == {"ok": False} @responses.activate def test_success_list(): responses.add( responses.GET, 'https://api.sparkpost.com/api/v1/transmissions', status=200, content_type='application/json', body='{"results": []}' ) sp = SparkPost('fake-key') response = ioloop.IOLoop().run_sync(sp.transmission.list) assert response == [] python-sparkpost-1.3.7/test/tornado/utils.py000066400000000000000000000032511341365572200212370ustar00rootroot00000000000000from __future__ import print_function from collections import namedtuple from responses import RequestsMock from tornado import ioloop from tornado.concurrent import Future from tornado.httpclient import HTTPRequest, HTTPResponse, HTTPError Request = namedtuple("Request", ["url", "method", "headers", "body"]) class ResponseGenerator(object): def get_adapter(self, url): return self def build_response(self, request, response): resp = HTTPResponse(request, response.status, headers=response.headers, effective_url=request.url, error=None, buffer="") resp._body = response.data f = Future() f.content = None if response.status < 200 or response.status >= 300: resp.error = HTTPError(response.status, response=resp) ioloop.IOLoop().current().add_callback(f.set_exception, resp.error) else: ioloop.IOLoop().current().add_callback(f.set_result, resp) return f class AsyncClientMock(RequestsMock): def start(self): import mock def unbound_on_send(client, request, callback=None, **kwargs): if not isinstance(request, HTTPRequest): request = Request(request, kwargs.get("method", "GET"), kwargs.get("headers", []), kwargs.get("body", "")) return self._on_request(ResponseGenerator(), request) self._patcher = mock.patch('tornado.httpclient.AsyncHTTPClient.fetch', unbound_on_send) self._patcher.start() def stop(self): self._patcher.stop() python-sparkpost-1.3.7/tox.ini000066400000000000000000000005611341365572200164140ustar00rootroot00000000000000[tox] envlist = {py27,py34}-django{17,18}, py35-django{18,19} [testenv] deps = -rtest-requirements.txt django17: Django>=1.7,<1.8 django18: Django>=1.8,<1.9 django19: Django>=1.9,<1.10 commands = py.test test/ [testenv:py35-django19] commands = flake8 sparkpost test py.test --cov-report term-missing --cov-report html --cov sparkpost test/