From 62420362f1545f60448720a724def33b54a837be Mon Sep 17 00:00:00 2001 From: Martin Majlis Date: Mon, 3 Jul 2017 07:10:13 +0200 Subject: [PATCH] Initial commit. --- .travis.yml | 21 ++ mailgunv3/__init__.py | 450 ++++++++++++++++++++++++++++++++++++++++++ requirements.txt | 1 + setup.py | 39 ++++ 4 files changed, 511 insertions(+) create mode 100644 .travis.yml create mode 100644 mailgunv3/__init__.py create mode 100644 requirements.txt create mode 100644 setup.py diff --git a/.travis.yml b/.travis.yml new file mode 100644 index 0000000..b280465 --- /dev/null +++ b/.travis.yml @@ -0,0 +1,21 @@ +language: python +python: +- '2.7' +- '3.3' +- '3.4' +- '3.5' +- '3.6' +install: pip install -r requirements.txt +script: +- touch foo +deploy: + provider: pypi + user: socialmind + password: + secure: jKUKWvonigyqCXUeTnEvpIASKKtB2yHLzb6QEkiTG6ssFIklYk0gqt2sjQonvNudbpUD27sMKZnCah8Gi8yoiAnkr78mSAngv2/sBgnULnMr+gIv3gygS2Ozny+wWvPBUvl5gI7prDLyeW9CNmrR/szHI6+9B6aito/T4tmTVizJsUMCU2IBl8lPriucd1Z2mXknCO3ROz9MJzejb+AMpTDSJExMDlOeZtfX3tvuVhM8XSZY/T3tulJgjT4Yic1mrvTyS6N0LGwgmLLfHonO4+Kcja+vc57UzohB95DtamlrVKpL+A8aCr/1khH5g3fLzQKiLwZQth+HaGpl+911SFfr3bzy6CZVzLsiknEU1YYP90vYCn1p4pzbG3PgxqrsyU259IYgedTTlwt0scnEbaZPYbUnTzRmvon4o6PcIFUe49ocuGaGQKZsRVIfxKSCN1ZyFkmjOLIBlz5T+kv1+9aTHT/FdkL6+bCZ55VGjpRL1IcDdv0q/KPN/MiI5+8AW1V0DV4FfqLFSxSojuJMeyx8S8jpHIE3wBVX7mLXbPdVaBZLGBIHwP8ex5GrB9Zb5k+CLumx1RSEEWeh5kP5ZIGURDSqIiA60EEaANWoJF1DGZlRIIGVtvljCaf70/t4PSZ4o3z4kMeq8P+XHZtbxKPAq8u3y+PfqylA1w8TYyk= + on: + tags: true + branch: master +after_sucess: +- CODECLIMATE_REPO_TOKEN=97962937bc5c67607343dbdd5517fbba0c6865da codeclimate-test-reporter + diff --git a/mailgunv3/__init__.py b/mailgunv3/__init__.py new file mode 100644 index 0000000..5954e51 --- /dev/null +++ b/mailgunv3/__init__.py @@ -0,0 +1,450 @@ +from __future__ import unicode_literals + +import base64 +import enum +import hashlib +import hmac +import json +import random +import requests + +#from mailgun3 import Mailgun +BASE_URL = 'https://api.mailgun.net/v3' + + +class MailGunV3(object): + def __init__(self, domain, private_key, public_key): + self.domain = domain + self.private_key = private_key + self.public_key = public_key + self.auth = ('api', private_key) + + def post(self, url, data, auth=None, files=None, include_domain=True): + print(repr(url)) + return requests.post(url, auth=auth or self.auth, data=data, files=files) + + def get(self, url, params=None, auth=None, include_domain=True): + return requests.get(url, auth=auth or self.auth, params=params) + + def put(self, url, params=None, auth=None, include_domain=True): + return requests.put(url, auth=auth or self.auth, params=params) + + def delete(self, url, params=None, auth=None, include_domain=True): + return requests.delete(url, auth=auth or self.auth, params=params) + + def mailinglist(self, email): + return MailingList(self, email) + + def send_message(self, from_email, to, cc=None, bcc=None, + subject=None, text=None, html=None, user_variables=None, + reply_to=None, headers=None, inlines=None, attachments=None, campaign_id=None, + tags=None): + # sanity checks + assert (text or html) + + data = { + 'from': from_email, + 'to': to, + 'cc': cc or [], + 'bcc': bcc or [], + 'subject': subject or '', + 'text': text or '', + 'html': html or '', + } + + if reply_to: + data['h:Reply-To'] = reply_to + + if headers: + for k, v in headers.items(): + data["h:%s" % k] = v + + if campaign_id: + data['o:campaign'] = campaign_id + + if tags: + data['o:tag'] = tags + + if user_variables: + for k, v in user_variables.items(): + data['v:%s' % k] = v + + files = [] + + if inlines: + for filename in inlines: + files.append(('inline', open(filename))) + + if attachments: + for filename, content_type, content in attachments: + files.append(('attachment', (filename, content, content_type))) + + return self.post(BASE_URL + '/' + self.domain + '/messages', data, files=files) + + +class Mailgun(object): + def __init__(self, domain, private_key, public_key): + self.private_key = private_key + self.public_key = public_key + self.auth = ('api', private_key) + self.base_url = '{0}/{1}'.format(BASE_URL, domain) + + def post(self, path, data, auth=None, files=None, include_domain=True): + url = self.base_url if include_domain else BASE_URL + return requests.post(url + path, auth=auth or self.auth, data=data, files=files) + + def get(self, path, params=None, auth=None, include_domain=True): + url = self.base_url if include_domain else BASE_URL + return requests.get(url + path, auth=auth or self.auth, params=params) + + def send_message(self, from_email, to, cc=None, bcc=None, + subject=None, text=None, html=None, user_variables=None, + reply_to=None, headers=None, inlines=None, attachments=None, campaign_id=None, + tags=None): + # sanity checks + assert (text or html) + + data = { + 'from': from_email, + 'to': to, + 'cc': cc or [], + 'bcc': bcc or [], + 'subject': subject or '', + 'text': text or '', + 'html': html or '', + } + + if reply_to: + data['h:Reply-To'] = reply_to + + if headers: + for k, v in headers.items(): + data["h:%s" % k] = v + + if campaign_id: + data['o:campaign'] = campaign_id + + if tags: + data['o:tag'] = tags + + if user_variables: + for k, v in user_variables.items(): + data['v:%s' % k] = v + + files = [] + + if inlines: + for filename in inlines: + files.append(('inline', open(filename))) + + if attachments: + for filename, content_type, content in attachments: + files.append(('attachment', (filename, content, content_type))) + + return self.post('/messages', data, files=files) + + def get_events(self, begin=None, end=None, ascending=None, limit=None, filters=None): + params = dict() + + if begin: + params['begin'] = begin + + if end: + params['end'] = end + + if ascending: + params['ascending'] = ascending + + if limit: + params['limit'] = limit + + if filters is None: + filters = dict() + + params.update(filters) + + return self.get('/events', params=params) + + def create_list(self, address, name=None, description=None, access_level=None): + data = {'address': address} + if name: + data['name'] = name + + if description: + data['description'] = description + + if access_level and access_level in ['readonly', 'members', 'everyone']: + data['access_level'] = access_level + + return self.post('/lists', data, include_domain=False) + + def add_list_member(self, list_name, address, name=None, params=None, + subscribed=True, upsert=False): + data = {'address': address} + if name: + data['name'] = name + + if params: + data['vars'] = json.dumps(params) if isinstance( + params, dict) else params + + if not subscribed: + data['subscribed'] = 'no' + + if upsert: + data['upsert'] = 'yes' + + return self.post('/lists/%s/members' % list_name, data, include_domain=False) + + def verify_authenticity(self, token, timestamp, signature): + return signature == hmac.new( + key=self.private_key, msg='{}{}'.format(timestamp, token), + digestmod=hashlib.sha256).hexdigest() + + def validate(self, address): + params = dict(address=address) + auth = ('api', self.public_key) + return self.get('/address/validate', params=params, auth=auth, include_domain=False) + + +class APIResponse(object): + def __init__(self): + self.status_code = 200 + self.status_msg = 'OK' + self.text = '' + self.json = '' + + def _set_error(self, status_code, status_msg): + self.status_code = status_code + self.status_msg = status_msg + + def _has_error(self): + return self.status_code != 200 + + def __repr__(self): + return repr({ + 'status_code': self.status_code, + 'status_msg': self.status_msg, + 'text': self.text, + 'json': self.json, + }) + + def _copy_state(self, api_response): + self.status_code = api_response.status_code + self.status_msg = api_response.status_msg + self.text = api_response.text + self.json = api_response.json + +# https://documentation.mailgun.com/api-mailinglists.html#mailing-lists + + +class MailingList(APIResponse): + def __init__(self, mailgun, address): + super().__init__() + self.mailgun = mailgun + self.address = address + + def get(self): + if self._has_error(): + return self + + res = self.mailgun.get(BASE_URL + '/lists/' + + self.address) + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during mailing list {} retrieval.'.format(self.address), + ) + return self + + def delete(self): + if self._has_error(): + return self + + res = self.mailgun.delete(BASE_URL + '/lists/' + + self.address) + + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during mailing list {} deletion.'.format(self.address), + ) + return self + + def create(self, name=None, description=None, access_level=None): + if self._has_error(): + return self + + data = {'address': self.address} + if name: + data['name'] = name + + if description: + data['description'] = description + + if access_level and access_level in ['readonly', 'members', 'everyone']: + data['access_level'] = access_level + + res = self.mailgun.post(BASE_URL + '/lists', data) + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during mailing list {} creation.'.format(self.address), + ) + return self + + def update(self, name=None, description=None, access_level=None): + if self._has_error(): + return self + + data = {'address': self.address} + if name: + data['name'] = name + + if description: + data['description'] = description + + if access_level and access_level in ['readonly', 'members', 'everyone']: + data['access_level'] = access_level + + res = self.mailgun.put(BASE_URL + '/lists/' + self.address, data) + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during mailing list {} update.'.format(self.address), + ) + return self + + def members(self): + if self._has_error(): + return self + + res = self.mailgun.get(BASE_URL + '/lists/' + + self.address + '/members/pages') + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during mailing list {} members.'.format(self.address), + ) + return self + + def member(self, address): + return MailingListMember(self.mailgun, self, address) + + +class MailingListMember(APIResponse): + def __init__(self, mailgun, mailinglist, address): + super().__init__() + self.mailgun = mailgun + self.mailinglist = mailinglist + self.address = address + self._copy_state(mailinglist) + + def create(self, name=None, params=None, subscribed=True, upsert=False): + if self._has_error(): + return self + + data = {'address': self.address} + if name: + data['name'] = name + + if params: + data['vars'] = json.dumps(params) if isinstance( + params, dict) else params + + if not subscribed: + data['subscribed'] = 'no' + + if upsert: + data['upsert'] = 'yes' + + res = self.mailgun.post(BASE_URL + '/lists/' + + self.mailinglist.address + '/members', data) + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during adding member {} into mailing list.'.format( + self.address, + self.mailinglist.address, + ), + ) + return self + + def update(self, name=None, params=None, subscribed=True): + if self._has_error(): + return self + + data = {} + if name: + data['name'] = name + + if params: + data['vars'] = json.dumps(params) if isinstance( + params, dict) else params + + if not subscribed: + data['subscribed'] = 'no' + + res = self.mailgun.put(BASE_URL + '/lists/' + + self.mailinglist.address + '/members/' + self.address, data) + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during updating member {} from mailing list.'.format( + self.address, + self.mailinglist.address, + ), + ) + return self + + def get(self): + if self._has_error(): + return self + + res = self.mailgun.get(BASE_URL + '/lists/' + + self.mailinglist.address + '/members/' + self.address) + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during retrieving member {} from mailing list.'.format( + self.address, + self.mailinglist.address, + ), + ) + return self + + def delete(self): + if self._has_error(): + return self + + res = self.mailgun.get(BASE_URL + '/lists/' + + self.mailinglist.address + '/members/' + self.address) + self.text = res.text + self.json = res.json + if (res.status_code != 200): + self._set_error( + res.status_code, + 'Error during retrieving member {} from mailing list.'.format( + self.address, + self.mailinglist.address, + ), + ) + return self + + diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..f229360 --- /dev/null +++ b/requirements.txt @@ -0,0 +1 @@ +requests diff --git a/setup.py b/setup.py new file mode 100644 index 0000000..82f8d05 --- /dev/null +++ b/setup.py @@ -0,0 +1,39 @@ +import os + +from setuptools import setup, find_packages + +#here = os.path.abspath(os.path.dirname(__file__)) +# with open(os.path.join(here, 'README.txt')) as f: +# README = f.read() +# with open(os.path.join(here, 'CHANGES.txt')) as f: +# CHANGES = f.read() + +requires = [ + 'requests', +] + +tests_require = [] + +setup( + name='pymailgun', + version='0.1', + description='Python wrapper for Mailgun REST API.', + #long_description=README + '\n\n' + CHANGES, + classifiers=[ + 'Programming Language :: Python', + ], + author='Martin Majlis', + author_email='martin@majlis.cz', + license='MIT', + url='https://github.com/martin-majlis/MailGunV3', + download_url='https://github.com/martin-majlis/MailGunV3/archive/master.tar.gz', + keywords='mailgun mail', + packages=find_packages(), + include_package_data=True, + zip_safe=False, + extras_require={ + 'testing': tests_require, + }, + install_requires=requires, + platforms='any', +)