pax_global_header00006660000000000000000000000064130576207210014515gustar00rootroot0000000000000052 comment=6e9259546e0952bc11d9fe4fb474d3b9b1eac9b7 python-sparkpost-1.3.5/000077500000000000000000000000001305762072100150705ustar00rootroot00000000000000python-sparkpost-1.3.5/.editorconfig000066400000000000000000000002331305762072100175430ustar00rootroot00000000000000root = 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.5/.gitignore000066400000000000000000000013531305762072100170620ustar00rootroot00000000000000# 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.5/.travis.yml000066400000000000000000000013561305762072100172060ustar00rootroot00000000000000language: 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.5/AUTHORS.rst000066400000000000000000000020311305762072100167430ustar00rootroot00000000000000Core contributors ----------------- - Bob Evans `@bizob2828 `_ - Aydrian Howard `@aydrian `_ - Rich Leland `@richleland `_ Patches and suggestions ----------------------- - 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 `_ - ADD YOURSELF HERE (and link to your github page) python-sparkpost-1.3.5/CHANGELOG.md000066400000000000000000000161631305762072100167100ustar00rootroot00000000000000# 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.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.5...HEAD [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.5/CONTRIBUTING.md000066400000000000000000000055171305762072100173310ustar00rootroot00000000000000# 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.5/LICENSE000066400000000000000000000010711305762072100160740ustar00rootroot00000000000000Copyright 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.5/MANIFEST.in000066400000000000000000000000471305762072100166270ustar00rootroot00000000000000include AUTHORS.rst README.rst LICENSE python-sparkpost-1.3.5/Makefile000066400000000000000000000017341305762072100165350ustar00rootroot00000000000000.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.5/README.rst000066400000000000000000000106531305762072100165640ustar00rootroot00000000000000.. 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/sign-up?src=Dev-Website&sfdcid=70160000000pqBb .. _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 .. image:: http://slack.sparkpost.com/badge.svg :target: http://slack.sparkpost.com :alt: Slack Community 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/ .. _pip: http://www.pip-installer.org/en/latest/ 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') .. _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( 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. Documentation ------------- * Documentation for `python-sparkpost`_ * `SparkPost API Reference`_ .. _python-sparkpost: https://python-sparkpost.readthedocs.io/ .. _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.5/dev-requirements.txt000066400000000000000000000001031305762072100211220ustar00rootroot00000000000000-r test-requirements.txt wheel twine Django>=1.7,<1.10 tornado>=3.2python-sparkpost-1.3.5/docs/000077500000000000000000000000001305762072100160205ustar00rootroot00000000000000python-sparkpost-1.3.5/docs/Makefile000066400000000000000000000152221305762072100174620ustar00rootroot00000000000000# 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.5/docs/api.rst000066400000000000000000000002431305762072100173220ustar00rootroot00000000000000================= API Documentation ================= A complete API reference to the :data:`sparkpost` module. .. toctree:: :glob: :maxdepth: 1 api/* python-sparkpost-1.3.5/docs/api/000077500000000000000000000000001305762072100165715ustar00rootroot00000000000000python-sparkpost-1.3.5/docs/api/recipient_lists.rst000066400000000000000000000002251305762072100225220ustar00rootroot00000000000000.. module:: sparkpost.recipient_lists :mod:`sparkpost.recipient_lists` ================================ .. autoclass:: RecipientLists :members: python-sparkpost-1.3.5/docs/api/suppression_list.rst000066400000000000000000000002311305762072100227440ustar00rootroot00000000000000.. module:: sparkpost.suppression_list :mod:`sparkpost.suppression_list` ================================= .. autoclass:: SuppressionList :members: python-sparkpost-1.3.5/docs/api/templates.rst000066400000000000000000000002011305762072100213120ustar00rootroot00000000000000.. module:: sparkpost.templates :mod:`sparkpost.templates` ============================= .. autoclass:: Templates :members: python-sparkpost-1.3.5/docs/api/transmissions.rst000066400000000000000000000002161305762072100222360ustar00rootroot00000000000000.. module:: sparkpost.transmissions :mod:`sparkpost.transmissions` ============================== .. autoclass:: Transmissions :members: python-sparkpost-1.3.5/docs/conf.py000066400000000000000000000206551305762072100173270ustar00rootroot00000000000000# -*- 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.5' # 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.5/docs/django/000077500000000000000000000000001305762072100172625ustar00rootroot00000000000000python-sparkpost-1.3.5/docs/django/backend.rst000066400000000000000000000052371305762072100214120ustar00rootroot00000000000000Django 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.5/docs/index.rst000066400000000000000000000035521305762072100176660ustar00rootroot00000000000000SparkPost 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') .. _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.5/docs/make.bat000066400000000000000000000151011305762072100174230ustar00rootroot00000000000000@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.5/docs/requirements.txt000066400000000000000000000000301305762072100212750ustar00rootroot00000000000000sphinx sphinx-rtd-theme python-sparkpost-1.3.5/docs/resources/000077500000000000000000000000001305762072100200325ustar00rootroot00000000000000python-sparkpost-1.3.5/docs/resources/metrics.rst000066400000000000000000000010421305762072100222270ustar00rootroot00000000000000Metrics ======= 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.5/docs/resources/recipient_lists.rst000066400000000000000000000033621305762072100237700ustar00rootroot00000000000000Recipient 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.5/docs/resources/suppression_list.rst000066400000000000000000000026351305762072100242170ustar00rootroot00000000000000Suppression 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.5/docs/resources/templates.rst000066400000000000000000000024121305762072100225610ustar00rootroot00000000000000Templates ============= 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.5/docs/resources/transmissions.rst000066400000000000000000000103661305762072100235060ustar00rootroot00000000000000Transmissions ============= 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.5/examples/000077500000000000000000000000001305762072100167065ustar00rootroot00000000000000python-sparkpost-1.3.5/examples/base_resource.py000066400000000000000000000006151305762072100221030ustar00rootroot00000000000000import 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.5/examples/recipient_lists/000077500000000000000000000000001305762072100221065ustar00rootroot00000000000000python-sparkpost-1.3.5/examples/recipient_lists/create_recipient_list.py000066400000000000000000000007531305762072100270250ustar00rootroot00000000000000from 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.5/examples/recipient_lists/delete_recipient_list.py000066400000000000000000000001561305762072100270210ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.recipient_lists.delete('list_id') print(result) python-sparkpost-1.3.5/examples/recipient_lists/get_recipient_list.py000066400000000000000000000001731305762072100263350ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipient_list = sp.recipient_lists.get('list_id') print(recipient_list) python-sparkpost-1.3.5/examples/recipient_lists/get_recipient_list_with_recipients.py000066400000000000000000000002011305762072100316050ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipient_list = sp.recipient_lists.get('list_id', True) print(recipient_list) python-sparkpost-1.3.5/examples/recipient_lists/list_recipient_lists.py000066400000000000000000000001651305762072100267150ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() recipient_lists = sp.recipient_lists.list() print(recipient_lists) python-sparkpost-1.3.5/examples/recipient_lists/update_recipient_list.py000066400000000000000000000007521305762072100270430ustar00rootroot00000000000000from 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.5/examples/suppression_list/000077500000000000000000000000001305762072100223335ustar00rootroot00000000000000python-sparkpost-1.3.5/examples/suppression_list/create_suppression_entries_bulk.py000066400000000000000000000011341305762072100313670ustar00rootroot00000000000000from 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.5/examples/suppression_list/create_suppression_entry.py000066400000000000000000000003501305762072100300410ustar00rootroot00000000000000from 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.5/examples/suppression_list/delete_suppression_entry.py000066400000000000000000000001651305762072100300440ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.suppression_list.delete('test@test.com') print(result) python-sparkpost-1.3.5/examples/suppression_list/get_suppression_entry.py000066400000000000000000000001601305762072100273540ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() entry = sp.suppression_list.get('test@test.com') print(entry) python-sparkpost-1.3.5/examples/suppression_list/list_suppression_entries.py000066400000000000000000000003051305762072100300610ustar00rootroot00000000000000from 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.5/examples/suppression_list/update_suppression_entries_bulk.py000066400000000000000000000011341305762072100314060ustar00rootroot00000000000000from 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.5/examples/suppression_list/update_suppression_enty.py000066400000000000000000000003501305762072100276760ustar00rootroot00000000000000from 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.5/examples/templates/000077500000000000000000000000001305762072100207045ustar00rootroot00000000000000python-sparkpost-1.3.5/examples/templates/create_template.py000066400000000000000000000004071305762072100244150ustar00rootroot00000000000000from 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.5/examples/templates/delete_template.py000066400000000000000000000001541305762072100244130ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() result = sp.templates.delete('template_id') print(result) python-sparkpost-1.3.5/examples/templates/get_template.py000066400000000000000000000001551305762072100237310ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() template = sp.templates.get('template_id') print(template) python-sparkpost-1.3.5/examples/templates/list_templates.py000066400000000000000000000001531305762072100243060ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() template_list = sp.templates.list() print(template_list) python-sparkpost-1.3.5/examples/templates/preview_draft_template.py000066400000000000000000000003011305762072100260040ustar00rootroot00000000000000from 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.5/examples/templates/preview_template.py000066400000000000000000000002731305762072100246340ustar00rootroot00000000000000from 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.5/examples/templates/update_template.py000066400000000000000000000004231305762072100244320ustar00rootroot00000000000000from 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.5/examples/transmissions/000077500000000000000000000000001305762072100216225ustar00rootroot00000000000000python-sparkpost-1.3.5/examples/transmissions/a-file.txt000066400000000000000000000000711305762072100235160ustar00rootroot00000000000000Woo hoo! This attachment was added via python-sparkpost. python-sparkpost-1.3.5/examples/transmissions/find_transmission.py000066400000000000000000000001751305762072100257300ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() transmission = sp.transmissions.get('transmission_id') print(transmission) python-sparkpost-1.3.5/examples/transmissions/get_transmissions.py000066400000000000000000000001671305762072100257530ustar00rootroot00000000000000from sparkpost import SparkPost sp = SparkPost() transmission_list = sp.transmissions.list() print(transmission_list) python-sparkpost-1.3.5/examples/transmissions/handle_exception.py000066400000000000000000000021251305762072100255050ustar00rootroot00000000000000""" 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.5/examples/transmissions/schedule_transmission.py000066400000000000000000000015071305762072100266040ustar00rootroot00000000000000from 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.5/examples/transmissions/send_transmission.py000066400000000000000000000022321305762072100257350ustar00rootroot00000000000000import 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.5/examples/transmissions/transmission_stored_list.py000066400000000000000000000010721305762072100273400ustar00rootroot00000000000000from 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.5/examples/transmissions/transmission_stored_template.py000066400000000000000000000002611305762072100301770ustar00rootroot00000000000000from 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.5/setup.cfg000066400000000000000000000000321305762072100167040ustar00rootroot00000000000000[bdist_wheel] universal=1 python-sparkpost-1.3.5/setup.py000066400000000000000000000016761305762072100166140ustar00rootroot00000000000000from 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.5', 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.5.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.5/sparkpost/000077500000000000000000000000001305762072100171165ustar00rootroot00000000000000python-sparkpost-1.3.5/sparkpost/__init__.py000066400000000000000000000033011305762072100212240ustar00rootroot00000000000000import 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.5' 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.5/sparkpost/base.py000066400000000000000000000030231305762072100204000ustar00rootroot00000000000000import 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.5/sparkpost/django/000077500000000000000000000000001305762072100203605ustar00rootroot00000000000000python-sparkpost-1.3.5/sparkpost/django/__init__.py000066400000000000000000000000001305762072100224570ustar00rootroot00000000000000python-sparkpost-1.3.5/sparkpost/django/email_backend.py000066400000000000000000000022731305762072100234740ustar00rootroot00000000000000from 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.5/sparkpost/django/exceptions.py000066400000000000000000000001341305762072100231110ustar00rootroot00000000000000class UnsupportedContent(Exception): pass class UnsupportedParam(Exception): pass python-sparkpost-1.3.5/sparkpost/django/message.py000066400000000000000000000060751305762072100223660ustar00rootroot00000000000000import 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 super(SparkPostMessage, self).__init__(formatted) python-sparkpost-1.3.5/sparkpost/exceptions.py000066400000000000000000000020631305762072100216520ustar00rootroot00000000000000class SparkPostException(Exception): pass class SparkPostAPIException(SparkPostException): "Handle 4xx and 5xx errors from the SparkPost API" def __init__(self, response, *args, **kwargs): 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] except: 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.5/sparkpost/metrics.py000066400000000000000000000013341305762072100211370ustar00rootroot00000000000000from .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.5/sparkpost/recipient_lists.py000066400000000000000000000071271305762072100226770ustar00rootroot00000000000000import 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.5/sparkpost/suppression_list.py000066400000000000000000000067171305762072100231300ustar00rootroot00000000000000import 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.5/sparkpost/templates.py000066400000000000000000000160771305762072100215010ustar00rootroot00000000000000import 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.5/sparkpost/tornado/000077500000000000000000000000001305762072100205645ustar00rootroot00000000000000python-sparkpost-1.3.5/sparkpost/tornado/__init__.py000066400000000000000000000011301305762072100226700ustar00rootroot00000000000000import 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.5/sparkpost/tornado/base.py000066400000000000000000000020301305762072100220430ustar00rootroot00000000000000import 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 try: result = json.loads(response.body.decode("utf-8")) except: pass if result: if 'results' in result: raise gen.Return(result['results']) raise gen.Return(result) raise SparkPostAPIException(response) python-sparkpost-1.3.5/sparkpost/tornado/exceptions.py000066400000000000000000000020551305762072100233210ustar00rootroot00000000000000import json from ..exceptions import SparkPostAPIException as RequestsSparkPostAPIException class SparkPostAPIException(RequestsSparkPostAPIException): def __init__(self, response, *args, **kwargs): errors = None try: data = json.loads(response.body.decode("utf-8")) if data: errors = data['errors'] errors = [e['message'] + ': ' + e.get('description', '') for e in errors] except: 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.5/sparkpost/tornado/transmissions.py000066400000000000000000000004401305762072100240500ustar00rootroot00000000000000from .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.5/sparkpost/tornado/utils.py000066400000000000000000000005151305762072100222770ustar00rootroot00000000000000from 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.5/sparkpost/transmissions.py000066400000000000000000000271271305762072100224150ustar00rootroot00000000000000import 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', 'default@sparkpostmail.com') 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') 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') model['content']['headers'] = kwargs.get('custom_headers', {}) 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 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) 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: If list it is an list of email addresses, if dict ``{'address': {'name': 'Name', 'email': 'me' }}`` :param str recipient_list: ID of recipient list, if set recipients above 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. If set HTML or text will not be used :param bool use_draft_template: Default 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 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 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. (SparkPost Elite only) :param str ip_pool: The name of a dedicated 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 :returns: a ``dict`` with the ID and number of accepted and rejected recipients :raises: :exc:`SparkPostAPIException` if transmission cannot be sent """ payload = self._translate_keys(**kwargs) results = self.request('POST', self.uri, data=json.dumps(payload)) 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 :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 endpoint is deprecated. For details, ' 'check https://sparkpo.st/5qcj4.' 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.5/test-requirements.txt000066400000000000000000000001241305762072100213260ustar00rootroot00000000000000flake8 pytest==2.8.7 pytest-cov==1.8.1 requests==2.5.1 responses==0.3.0 mock==2.0.0 python-sparkpost-1.3.5/test/000077500000000000000000000000001305762072100160475ustar00rootroot00000000000000python-sparkpost-1.3.5/test/__init__.py000066400000000000000000000000001305762072100201460ustar00rootroot00000000000000python-sparkpost-1.3.5/test/assets/000077500000000000000000000000001305762072100173515ustar00rootroot00000000000000python-sparkpost-1.3.5/test/assets/sparkpostdev.png000066400000000000000000000146261305762072100226150ustar00rootroot00000000000000PNG  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.5/test/django/test_message.py000066400000000000000000000131311305762072100223450ustar00rootroot00000000000000from 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 python-sparkpost-1.3.5/test/django/utils.py000066400000000000000000000002561305762072100210260ustar00rootroot00000000000000from distutils.version import StrictVersion from django import get_version def at_least_version(version): return StrictVersion(get_version()) > StrictVersion(version) python-sparkpost-1.3.5/test/test_base.py000066400000000000000000000061451305762072100204000ustar00rootroot00000000000000import 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.5/test/test_init.py000066400000000000000000000002741305762072100204260ustar00rootroot00000000000000import pytest from sparkpost import SparkPost from sparkpost.exceptions import SparkPostException def test_no_api_key(): with pytest.raises(SparkPostException): SparkPost() python-sparkpost-1.3.5/test/test_metrics.py000066400000000000000000000032751305762072100211350ustar00rootroot00000000000000import 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.5/test/test_recipient_lists.py000066400000000000000000000103561305762072100226650ustar00rootroot00000000000000import 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.5/test/test_suppression_list.py000066400000000000000000000107071305762072100231120ustar00rootroot00000000000000import 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.5/test/test_templates.py000066400000000000000000000131411305762072100214560ustar00rootroot00000000000000try: from urllib.parse import urlparse except: 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.5/test/test_transmissions.py000066400000000000000000000261151305762072100224010ustar00rootroot00000000000000import 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['return_path'] == 'default@sparkpostmail.com' assert results['content']['use_draft_template'] is False 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 @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.5/test/tornado/000077500000000000000000000000001305762072100175155ustar00rootroot00000000000000python-sparkpost-1.3.5/test/tornado/__init__.py000066400000000000000000000000001305762072100216140ustar00rootroot00000000000000python-sparkpost-1.3.5/test/tornado/test_tornado.py000066400000000000000000000124211305762072100225740ustar00rootroot00000000000000import 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.5/test/tornado/utils.py000066400000000000000000000032511305762072100212300ustar00rootroot00000000000000from __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.5/tox.ini000066400000000000000000000005611305762072100164050ustar00rootroot00000000000000[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/