diff --git a/.travis.yml b/.travis.yml index 5ad33bccf..91c45daab 100644 --- a/.travis.yml +++ b/.travis.yml @@ -2,7 +2,6 @@ language: python sudo: false cache: pip python: -- '2.6' - '2.7' - '3.4' - '3.5' @@ -11,7 +10,6 @@ env: global: - CC_TEST_REPORTER_ID=$TRAVIS_CODE_CLIMATE_TOKEN install: -- if [[ $TRAVIS_PYTHON_VERSION == 2.6* ]]; then pip install unittest2; fi - python setup.py install - pip install pyyaml - pip install flask @@ -30,7 +28,7 @@ before_script: - chmod +x ./cc-test-reporter - ./cc-test-reporter before-build script: -- if [[ $TRAVIS_PYTHON_VERSION == '2.6' ]]; then coverage run -m unittest2 discover; else coverage run -m unittest discover; fi +- coverage run -m unittest discover after_script: - codecov - ./cc-test-reporter after-build --exit-code $? diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index b38f1c1c4..c4aa2c4a5 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -70,7 +70,7 @@ You can use our Docker image to avoid setting up the development environment you ##### Prerequisites ##### -- Python 2.6 through 3.6 +- Python 2.7 and 3.4+ - [python_http_client](https://github.com/sendgrid/python-http-client) ##### Initial setup: ##### @@ -122,12 +122,6 @@ All test files are in the [`test`](https://github.com/sendgrid/sendgrid-python/t For the purposes of contributing to this repo, please update the [`test_sendgrid.py`](https://github.com/sendgrid/sendgrid-python/tree/master/test/test_sendgrid.py) file with unit tests as you modify the code. -For Python 2.6.*: - -`unit2 discover -v` - -For Python 2.7.* and up: - `python -m unittest discover -v` ### Testing Multiple Versions of Python @@ -149,7 +143,6 @@ You can install it by yourself in user dir by calling `source test/prism.sh`. Add ```eval "$(pyenv init -)"``` to your shell environment (.profile, .bashrc, etc) after installing tox, you only need to do this once. ``` -pyenv install 2.6.9 pyenv install 2.7.11 pyenv install 3.4.3 pyenv install 3.5.0 @@ -159,7 +152,7 @@ Make sure to change the current working directory to your local version of the r python setup.py install ``` ``` -pyenv local 3.5.0 3.4.3 2.7.11 2.6.9 +pyenv local 3.5.0 3.4.3 2.7.11 pyenv rehash ``` diff --git a/README.md b/README.md index dbcebafdc..ba6468f23 100644 --- a/README.md +++ b/README.md @@ -42,7 +42,7 @@ We appreciate your continued support, thank you! ## Prerequisites -- Python version 2.6, 2.7, 3.4, 3.5 or 3.6 +- Python version 2.7 and 3.4+ - The SendGrid service, starting at the [free level](https://sendgrid.com/free?source=sendgrid-python) ## Setup Environment Variables diff --git a/docker/Dockerfile b/docker/Dockerfile index e891e497c..798b494e0 100644 --- a/docker/Dockerfile +++ b/docker/Dockerfile @@ -1,5 +1,5 @@ FROM ubuntu:xenial -ENV PYTHON_VERSIONS='python2.6 python2.7 python3.4 python3.5 python3.6' \ +ENV PYTHON_VERSIONS='python2.7 python3.4 python3.5 python3.6' \ OAI_SPEC_URL="https://raw.githubusercontent.com/sendgrid/sendgrid-oai/master/oai_stoplight.json" # install testing versions of python, including old versions, from deadsnakes diff --git a/sendgrid/helpers/inbound/send.py b/sendgrid/helpers/inbound/send.py index 6de575aab..e3526eb7c 100644 --- a/sendgrid/helpers/inbound/send.py +++ b/sendgrid/helpers/inbound/send.py @@ -37,6 +37,7 @@ def url(self): """URL to send to.""" return self._url + def main(): config = Config() parser = argparse.ArgumentParser(description='Test data and optional host.') @@ -54,5 +55,6 @@ def main(): print(response.headers) print(response.body) + if __name__ == '__main__': - main() \ No newline at end of file + main() diff --git a/sendgrid/helpers/mail/content.py b/sendgrid/helpers/mail/content.py index cff8ac498..da4ed8027 100644 --- a/sendgrid/helpers/mail/content.py +++ b/sendgrid/helpers/mail/content.py @@ -1,5 +1,6 @@ from .validators import ValidateAPIKey + class Content(object): """Content to be included in your email. diff --git a/sendgrid/helpers/mail/exceptions.py b/sendgrid/helpers/mail/exceptions.py index ab4dd9c0c..1b5da92fc 100644 --- a/sendgrid/helpers/mail/exceptions.py +++ b/sendgrid/helpers/mail/exceptions.py @@ -2,6 +2,7 @@ # Various types of extensible SendGrid related exceptions ################################################################ + class SendGridException(Exception): """Wrapper/default SendGrid-related exception""" pass @@ -14,9 +15,8 @@ class APIKeyIncludedException(SendGridException): message -- explanation of the error """ - def __init__(self, - expression="Email body", + def __init__(self, + expression="Email body", message="SendGrid API Key detected"): self.expression = expression self.message = message - diff --git a/sendgrid/helpers/mail/from_email.py b/sendgrid/helpers/mail/from_email.py index c12eeb4ac..0f6f231ce 100644 --- a/sendgrid/helpers/mail/from_email.py +++ b/sendgrid/helpers/mail/from_email.py @@ -1,4 +1,5 @@ from .email import Email + class From(Email): - """A from email address with an optional name.""" \ No newline at end of file + """A from email address with an optional name.""" diff --git a/sendgrid/helpers/mail/html_content.py b/sendgrid/helpers/mail/html_content.py index 0c81f9ce6..71c018b4a 100644 --- a/sendgrid/helpers/mail/html_content.py +++ b/sendgrid/helpers/mail/html_content.py @@ -1,6 +1,7 @@ from .content import Content from .validators import ValidateAPIKey + class HtmlContent(Content): """HTML content to be included in your email.""" diff --git a/sendgrid/helpers/mail/mail.py b/sendgrid/helpers/mail/mail.py index b0ac616f7..f374e6ad1 100644 --- a/sendgrid/helpers/mail/mail.py +++ b/sendgrid/helpers/mail/mail.py @@ -2,6 +2,7 @@ from .personalization import Personalization from .header import Header + class Mail(object): """Creates the response body for v3/mail/send""" def __init__( @@ -26,9 +27,9 @@ def __init__( # Minimum required to send a single email if from_email: - self.from_email = from_email + self.from_email = from_email if subject: - self.subject = subject + self.subject = subject if to_email: personalization = Personalization() personalization.add_to(to_email) @@ -51,7 +52,7 @@ def _ensure_insert(self, new_items, insert_to): def _flatten_dicts(self, dicts): list_of_dicts = [d.get() for d in dicts or []] - return dict((k, v) for d in list_of_dicts for k, v in d.items()) + return {k: v for d in list_of_dicts for k, v in d.items()} def _get_or_none(self, from_obj): return from_obj.get() if from_obj is not None else None @@ -138,5 +139,5 @@ def get(self): 'reply_to': self._get_or_none(self.reply_to), } - return dict((key, value) for key, value in mail.items() - if value is not None and value != [] and value != {}) + return {key: value for key, value in mail.items() + if value is not None and value != [] and value != {}} diff --git a/sendgrid/helpers/mail/plain_text_content.py b/sendgrid/helpers/mail/plain_text_content.py index 9c55d12d9..1a3c00191 100644 --- a/sendgrid/helpers/mail/plain_text_content.py +++ b/sendgrid/helpers/mail/plain_text_content.py @@ -1,6 +1,7 @@ from .content import Content from .validators import ValidateAPIKey + class PlainTextContent(Content): """Plain text content to be included in your email. """ diff --git a/sendgrid/helpers/mail/to_email.py b/sendgrid/helpers/mail/to_email.py index 18ef8f725..e4f294f03 100644 --- a/sendgrid/helpers/mail/to_email.py +++ b/sendgrid/helpers/mail/to_email.py @@ -1,4 +1,5 @@ from .email import Email + class To(Email): - """A to email address with an optional name.""" \ No newline at end of file + """A to email address with an optional name.""" diff --git a/sendgrid/helpers/mail/validators.py b/sendgrid/helpers/mail/validators.py index b4a69f697..816ec71a4 100644 --- a/sendgrid/helpers/mail/validators.py +++ b/sendgrid/helpers/mail/validators.py @@ -3,6 +3,7 @@ # Various types of Validators ################################################################ + class ValidateAPIKey(object): """Validates content to ensure SendGrid API key is not present""" @@ -27,9 +28,8 @@ def __init__(self, regex_strings=None, use_default=True): default_regex_string = 'SG\.[0-9a-zA-Z]+\.[0-9a-zA-Z]+' self.regexes.add(re.compile(default_regex_string)) - def validate_message_dict(self, request_body): - """With the JSON dict that will be sent to SendGrid's API, + """With the JSON dict that will be sent to SendGrid's API, check the content for SendGrid API keys - throw exception if found Args: request_body (:obj:`dict`): message parameter that is @@ -44,9 +44,9 @@ def validate_message_dict(self, request_body): # Default param elif isinstance(request_body, dict): - + contents = request_body.get("content", list()) - + for content in contents: if content is not None: if (content.get("type") == "text/html" or @@ -54,7 +54,6 @@ def validate_message_dict(self, request_body): message_text = content.get("value", "") self.validate_message_text(message_text) - def validate_message_text(self, message_string): """With a message string, check to see if it contains a SendGrid API Key If a key is found, throw an exception @@ -68,4 +67,3 @@ def validate_message_text(self, message_string): for regex in self.regexes: if regex.match(message_string) is not None: raise APIKeyIncludedException() - diff --git a/sendgrid/sendgrid.py b/sendgrid/sendgrid.py index c044196a3..5be0a651f 100644 --- a/sendgrid/sendgrid.py +++ b/sendgrid/sendgrid.py @@ -69,7 +69,7 @@ def __init__( self.apikey = apikey or api_key or os.environ.get('SENDGRID_API_KEY') self.impersonate_subuser = impersonate_subuser self.host = host - self.useragent = 'sendgrid/{0};python'.format(__version__) + self.useragent = 'sendgrid/{};python'.format(__version__) self.version = __version__ self.client = python_http_client.Client(host=self.host, @@ -79,7 +79,7 @@ def __init__( @property def _default_headers(self): headers = { - "Authorization": 'Bearer {0}'.format(self.apikey), + "Authorization": 'Bearer {}'.format(self.apikey), "User-agent": self.useragent, "Accept": 'application/json' } diff --git a/setup.py b/setup.py index 014691b61..5ccaa10f5 100644 --- a/setup.py +++ b/setup.py @@ -1,4 +1,3 @@ -import sys import os from io import open from setuptools import setup, find_packages @@ -14,12 +13,9 @@ def getRequires(): deps = ['python_http_client>=3.0'] - if sys.version_info < (2, 7): - deps.append('unittest2') - elif (3, 0) <= sys.version_info < (3, 2): - deps.append('unittest2py3k') return deps + setup( name='sendgrid', version=str(__version__), @@ -32,11 +28,11 @@ def getRequires(): description='SendGrid library for Python', long_description=long_description, install_requires=getRequires(), + python_requires='>=2.7, !=3.0.*, !=3.1.*, !=3.2.*, !=3.3.*', classifiers=[ - 'Programming Language :: Python :: 2.6', + 'Programming Language :: Python :: 2', 'Programming Language :: Python :: 2.7', - 'Programming Language :: Python :: 3.2', - 'Programming Language :: Python :: 3.3', + 'Programming Language :: Python :: 3', 'Programming Language :: Python :: 3.4', 'Programming Language :: Python :: 3.5', 'Programming Language :: Python :: 3.6' diff --git a/test/test_app.py b/test/test_app.py index 1a8e4a698..56027d570 100644 --- a/test/test_app.py +++ b/test/test_app.py @@ -1,13 +1,9 @@ import os +import unittest from sendgrid.helpers.inbound.config import Config from sendgrid.helpers.inbound.app import app -try: - import unittest2 as unittest -except ImportError: - import unittest - class UnitTests(unittest.TestCase): @@ -23,4 +19,4 @@ def test_up_and_running(self): def test_used_port_true(self): if self.config.debug_mode: port = int(os.environ.get("PORT", self.config.port)) - self.assertEqual(port, self.config.port) \ No newline at end of file + self.assertEqual(port, self.config.port) diff --git a/test/test_config.py b/test/test_config.py index 301bacc92..715eb685d 100644 --- a/test/test_config.py +++ b/test/test_config.py @@ -1,10 +1,8 @@ import os +import unittest + import sendgrid.helpers.inbound.config from sendgrid.helpers.inbound.config import Config -try: - import unittest2 as unittest -except ImportError: - import unittest class UnitTests(unittest.TestCase): @@ -39,7 +37,7 @@ def test_initialization(self): self.assertTrue(host, self.config.host) self.assertTrue(port, self.config.port) for key in keys: - self.assertTrue(key in self.config.keys) + self.assertIn(key, self.config.keys) def test_init_environment(self): config_file = sendgrid.helpers.inbound.config.__file__ diff --git a/test/test_email.py b/test/test_email.py index 902c59d4e..8213a20c9 100644 --- a/test/test_email.py +++ b/test/test_email.py @@ -1,13 +1,8 @@ # -*- coding: utf-8 -*- -import json +import unittest from sendgrid.helpers.mail import (Email) -try: - import unittest2 as unittest -except ImportError: - import unittest - class TestEmailObject(unittest.TestCase): @@ -40,7 +35,7 @@ def test_add_rfc_function_finds_name_not_email(self): def test_add_rfc_email(self): name = "SomeName" address = "test@example.com" - name_address = "{0} <{1}>".format(name, address) + name_address = "{} <{}>".format(name, address) email = Email(name_address) self.assertEqual(email.name, name) self.assertEqual(email.email, "test@example.com") diff --git a/test/test_mail.py b/test/test_mail.py index 7721b5205..98af29a09 100644 --- a/test/test_mail.py +++ b/test/test_mail.py @@ -1,5 +1,6 @@ # -*- coding: utf-8 -*- import json +import unittest from sendgrid.helpers.mail import ( ASM, @@ -29,11 +30,6 @@ ValidateAPIKey ) -try: - import unittest2 as unittest -except ImportError: - import unittest - class UnitTests(unittest.TestCase): @@ -52,7 +48,7 @@ def test_sendgridAPIKey(self): personalization.add_to(Email("test@example.com")) mail.add_personalization(personalization) - #Try to include SendGrid API key + # Try to include SendGrid API key try: mail.add_content(Content("text/plain", "some SG.2123b1B.1212lBaC here")) mail.add_content( @@ -72,15 +68,14 @@ def test_sendgridAPIKey(self): '"subject": "Hello World from the SendGrid Python Library"}' ) - #Exception should be thrown + # Exception should be thrown except Exception as e: pass - #Exception not thrown + # Exception not thrown else: self.fail("Should have failed as SendGrid API key included") - def test_helloEmail(self): self.max_diff = None @@ -113,7 +108,7 @@ def test_helloEmail(self): '"subject": "Hello World from the SendGrid Python Library"}' ) - self.assertTrue(isinstance(str(mail), str)) + self.assertIsInstance(str(mail), str) def test_helloEmailAdditionalContent(self): """Tests bug found in Issue-451 with Content ordering causing a crash""" @@ -130,7 +125,7 @@ def test_helloEmailAdditionalContent(self): personalization = Personalization() personalization.add_to(Email("test@example.com")) mail.add_personalization(personalization) - + mail.add_content(Content("text/html", "some text here")) mail.add_content(Content("text/plain", "some text here")) @@ -146,7 +141,7 @@ def test_helloEmailAdditionalContent(self): '"subject": "Hello World from the SendGrid Python Library"}' ) - self.assertTrue(isinstance(str(mail), str)) + self.assertIsInstance(str(mail), str) def test_kitchenSink(self): self.max_diff = None diff --git a/test/test_parse.py b/test/test_parse.py index 897b67655..1c899bbbb 100644 --- a/test/test_parse.py +++ b/test/test_parse.py @@ -1,11 +1,8 @@ +import unittest + from sendgrid.helpers.inbound.config import Config from sendgrid.helpers.inbound.app import app -try: - import unittest2 as unittest -except ImportError: - import unittest - class UnitTests(unittest.TestCase): diff --git a/test/test_project.py b/test/test_project.py index a762474ec..a0b87908a 100644 --- a/test/test_project.py +++ b/test/test_project.py @@ -1,9 +1,6 @@ import os +import unittest -try: - import unittest2 as unittest -except ImportError: - import unittest class ProjectTests(unittest.TestCase): @@ -75,5 +72,6 @@ def test_usage(self): def test_use_cases(self): self.assertTrue(os.path.isfile('./use_cases/README.md')) + if __name__ == '__main__': unittest.main() diff --git a/test/test_send.py b/test/test_send.py index 16d496b85..360ea7a82 100644 --- a/test/test_send.py +++ b/test/test_send.py @@ -1,10 +1,7 @@ import argparse -from sendgrid.helpers.inbound import send +import unittest -try: - import unittest2 as unittest -except ImportError: - import unittest +from sendgrid.helpers.inbound import send try: import unittest.mock as mock diff --git a/test/test_sendgrid.py b/test/test_sendgrid.py index c545cbb2d..214eb2de9 100644 --- a/test/test_sendgrid.py +++ b/test/test_sendgrid.py @@ -1,15 +1,9 @@ import sendgrid from sendgrid.helpers.mail import * from sendgrid.version import __version__ -try: - import unittest2 as unittest -except ImportError: - import unittest import os -import subprocess -import sys -import time import datetime +import unittest host = "http://localhost:4010" @@ -19,13 +13,13 @@ class UnitTests(unittest.TestCase): @classmethod def setUpClass(cls): cls.host = host - cls.path = '{0}{1}'.format( + cls.path = '{}{}'.format( os.path.abspath( os.path.dirname(__file__)), '/..') cls.sg = sendgrid.SendGridAPIClient(host=host) cls.devnull = open(os.devnull, 'w') prism_cmd = None - + # try: # # check for prism in the PATH # if subprocess.call('prism version'.split(), stdout=cls.devnull) == 0: @@ -101,7 +95,7 @@ def test_impersonate_subuser_init(self): self.assertEqual(sg_impersonate.impersonate_subuser, temp_subuser) def test_useragent(self): - useragent = '{0}{1}{2}'.format('sendgrid/', __version__, ';python') + useragent = '{}{}{}'.format('sendgrid/', __version__, ';python') self.assertEqual(self.sg.useragent, useragent) def test_host(self): @@ -134,7 +128,7 @@ def test_reset_request_headers(self): self.assertNotIn('blah', self.sg.client.request_headers) self.assertNotIn('blah2x', self.sg.client.request_headers) - for k,v in self.sg._default_headers.items(): + for k, v in self.sg._default_headers.items(): self.assertEqual(v, self.sg.client.request_headers[k]) def test_hello_world(self): @@ -144,8 +138,20 @@ def test_hello_world(self): content = Content( "text/plain", "and easy to do anywhere, even with Python") mail = Mail(from_email, subject, to_email, content) - self.assertTrue(mail.get() == {'content': [{'type': 'text/plain', 'value': 'and easy to do anywhere, even with Python'}], 'personalizations': [ - {'to': [{'email': 'test@example.com'}]}], 'from': {'email': 'test@example.com'}, 'subject': 'Sending with SendGrid is Fun'}) + self.assertEqual( + mail.get(), + { + "content": [ + { + "type": "text/plain", + "value": "and easy to do anywhere, even with Python" + } + ], + "personalizations": [{"to": [{"email": "test@example.com"}]}], + "from": {"email": "test@example.com"}, + "subject": "Sending with SendGrid is Fun", + }, + ) def test_access_settings_activity_get(self): params = {'limit': 1} diff --git a/tox.ini b/tox.ini index 9336a97b8..2f35f4872 100644 --- a/tox.ini +++ b/tox.ini @@ -4,7 +4,7 @@ # and then run "tox" from this directory. [tox] -envlist = py26, py27, py34, py35, py36 +envlist = py27, py34, py35, py36 [testenv] commands = coverage erase @@ -14,15 +14,6 @@ deps = -rrequirements.txt coverage -[testenv:py26] -commands = coverage erase - coverage run {envbindir}/unit2 discover -v [] - coverage report -deps = unittest2 - mock - {[testenv]deps} -basepython = python2.6 - [testenv:py27] commands = {[testenv]commands} deps = {[testenv]deps} diff --git a/use_cases/aws.md b/use_cases/aws.md index 2ff04bd1f..9c30fd7ed 100644 --- a/use_cases/aws.md +++ b/use_cases/aws.md @@ -9,7 +9,7 @@ The neat thing is that CodeStar provides all of this in a pre-configured package Once this tutorial is complete, you'll have a basic web service for sending email that can be invoked via a link to your newly created API endpoint. ### Prerequisites -Python 2.6, 2.7, 3.4, or 3.5 are supported by the sendgrid Python library, however I was able to utilize 3.6 with no issue. +Python 2.7 and 3.4 or 3.5 are supported by the sendgrid Python library, however I was able to utilize 3.6 with no issue. Before starting this tutorial, you will need to have access to an AWS account in which you are allowed to provision resources. This tutorial also assumes you've already created a SendGrid account with free-tier access. Finally, it is highly recommended you utilize [virtualenv](https://virtualenv.pypa.io/en/stable/).