├── .editorconfig ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .style.yapf ├── HISTORY.rst ├── LICENSE ├── MANIFEST.in ├── README.rst ├── apitester ├── __init__.py └── oauth2_app.py ├── mautic ├── __init__.py ├── api.py ├── assets.py ├── campaigns.py ├── categories.py ├── companies.py ├── company_fields.py ├── contact_fields.py ├── contacts.py ├── data.py ├── dynamic_contents.py ├── emails.py ├── exceptions.py ├── files.py ├── forms.py ├── notes.py ├── notifications.py ├── pages.py ├── point_triggers.py ├── points.py ├── reports.py ├── roles.py ├── segments.py ├── smses.py ├── stages.py ├── stats.py ├── users.py └── utils.py ├── requirements_dev.txt ├── setup.py ├── tests ├── __init__.py └── test_mautic_python.py └── tox.ini /.editorconfig: -------------------------------------------------------------------------------- 1 | # http://editorconfig.org 2 | 3 | root = true 4 | 5 | [*] 6 | indent_style = space 7 | indent_size = 4 8 | trim_trailing_whitespace = true 9 | insert_final_newline = true 10 | charset = utf-8 11 | end_of_line = lf 12 | 13 | [*.bat] 14 | indent_style = tab 15 | end_of_line = crlf 16 | 17 | [LICENSE] 18 | insert_final_newline = false 19 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | * Mautic Python version: 2 | * Python version: 3 | * Operating System: 4 | 5 | ### Description 6 | 7 | Describe what you were trying to get done. 8 | Tell us what happened, what went wrong, and what you expected to happen. 9 | 10 | ### What I Did 11 | 12 | ``` 13 | Paste the command(s) you ran and the output. 14 | If there was a crash, please include the traceback here. 15 | ``` 16 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | env/ 12 | build/ 13 | develop-eggs/ 14 | dist/ 15 | downloads/ 16 | eggs/ 17 | .eggs/ 18 | lib/ 19 | lib64/ 20 | parts/ 21 | sdist/ 22 | var/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | 27 | # PyInstaller 28 | # Usually these files are written by a python script from a template 29 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 30 | *.manifest 31 | *.spec 32 | 33 | # Installer logs 34 | pip-log.txt 35 | pip-delete-this-directory.txt 36 | 37 | # Unit test / coverage reports 38 | htmlcov/ 39 | .tox/ 40 | .coverage 41 | .coverage.* 42 | .cache 43 | nosetests.xml 44 | coverage.xml 45 | *,cover 46 | .hypothesis/ 47 | 48 | # Translations 49 | *.mo 50 | *.pot 51 | 52 | # Django stuff: 53 | *.log 54 | 55 | # Sphinx documentation 56 | docs/_build/ 57 | 58 | # PyBuilder 59 | target/ 60 | 61 | # pyenv python configuration file 62 | .python-version 63 | 64 | # Pycharm 65 | .idea 66 | -------------------------------------------------------------------------------- /.style.yapf: -------------------------------------------------------------------------------- 1 | 2 | [style] 3 | # Align closing bracket with visual indentation. 4 | align_closing_bracket_with_visual_indent=True 5 | 6 | # Allow lambdas to be formatted on more than one line. 7 | allow_multiline_lambdas=False 8 | 9 | # Insert a blank line before a 'def' or 'class' immediately nested 10 | # within another 'def' or 'class'. For example: 11 | # 12 | # class Foo: 13 | # # <------ this blank line 14 | # def method(): 15 | # ... 16 | blank_line_before_nested_class_or_def=False 17 | 18 | # Do not split consecutive brackets. Only relevant when 19 | # dedent_closing_brackets is set. For example: 20 | # 21 | # call_func_that_takes_a_dict( 22 | # { 23 | # 'key1': 'value1', 24 | # 'key2': 'value2', 25 | # } 26 | # ) 27 | # 28 | # would reformat to: 29 | # 30 | # call_func_that_takes_a_dict({ 31 | # 'key1': 'value1', 32 | # 'key2': 'value2', 33 | # }) 34 | coalesce_brackets=False 35 | 36 | # The column limit. 37 | column_limit=79 38 | 39 | # Indent width used for line continuations. 40 | continuation_indent_width=4 41 | 42 | # Put closing brackets on a separate line, dedented, if the bracketed 43 | # expression can't fit in a single line. Applies to all kinds of brackets, 44 | # including function definitions and calls. For example: 45 | # 46 | # config = { 47 | # 'key1': 'value1', 48 | # 'key2': 'value2', 49 | # } # <--- this bracket is dedented and on a separate line 50 | # 51 | # time_series = self.remote_client.query_entity_counters( 52 | # entity='dev3246.region1', 53 | # key='dns.query_latency_tcp', 54 | # transform=Transformation.AVERAGE(window=timedelta(seconds=60)), 55 | # start_ts=now()-timedelta(days=3), 56 | # end_ts=now(), 57 | # ) # <--- this bracket is dedented and on a separate line 58 | dedent_closing_brackets=True 59 | 60 | # The regex for an i18n comment. The presence of this comment stops 61 | # reformatting of that line, because the comments are required to be 62 | # next to the string they translate. 63 | i18n_comment= 64 | 65 | # The i18n function call names. The presence of this function stops 66 | # reformattting on that line, because the string it has cannot be moved 67 | # away from the i18n comment. 68 | i18n_function_call= 69 | 70 | # Indent the dictionary value if it cannot fit on the same line as the 71 | # dictionary key. For example: 72 | # 73 | # config = { 74 | # 'key1': 75 | # 'value1', 76 | # 'key2': value1 + 77 | # value2, 78 | # } 79 | indent_dictionary_value=False 80 | 81 | # The number of columns to use for indentation. 82 | indent_width=4 83 | 84 | # Join short lines into one line. E.g., single line 'if' statements. 85 | join_multiple_lines=True 86 | 87 | # Use spaces around default or named assigns. 88 | spaces_around_default_or_named_assign=False 89 | 90 | # Use spaces around the power operator. 91 | spaces_around_power_operator=False 92 | 93 | # The number of spaces required before a trailing comment. 94 | spaces_before_comment=2 95 | 96 | # Insert a space between the ending comma and closing bracket of a list, 97 | # etc. 98 | space_between_ending_comma_and_closing_bracket=False 99 | 100 | # Split before arguments if the argument list is terminated by a 101 | # comma. 102 | split_arguments_when_comma_terminated=False 103 | 104 | # Set to True to prefer splitting before '&', '|' or '^' rather than 105 | # after. 106 | split_before_bitwise_operator=False 107 | 108 | # If an argument / parameter list is going to be split, then split before 109 | # the first argument. 110 | split_before_first_argument=False 111 | 112 | # Set to True to prefer splitting before 'and' or 'or' rather than 113 | # after. 114 | split_before_logical_operator=False 115 | 116 | # Split named assignments onto individual lines. 117 | split_before_named_assigns=True 118 | 119 | # The penalty for splitting right after the opening bracket. 120 | split_penalty_after_opening_bracket=30 121 | 122 | # The penalty for splitting the line after a unary operator. 123 | split_penalty_after_unary_operator=10000 124 | 125 | # The penalty for splitting right before an if expression. 126 | split_penalty_before_if_expr=0 127 | 128 | # The penalty of splitting the line around the '&', '|', and '^' 129 | # operators. 130 | split_penalty_bitwise_operator=300 131 | 132 | # The penalty for characters over the column limit. 133 | split_penalty_excess_character=2600 134 | 135 | # The penalty incurred by adding a line split to the unwrapped line. The 136 | # more line splits added the higher the penalty. 137 | split_penalty_for_added_line_split=30 138 | 139 | # The penalty of splitting a list of "import as" names. For example: 140 | # 141 | # from a_very_long_or_indented_module_name_yada_yad import (long_argument_1, 142 | # long_argument_2, 143 | # long_argument_3) 144 | # 145 | # would reformat to something like: 146 | # 147 | # from a_very_long_or_indented_module_name_yada_yad import ( 148 | # long_argument_1, long_argument_2, long_argument_3) 149 | split_penalty_import_names=0 150 | 151 | # The penalty of splitting the line around the 'and' and 'or' 152 | # operators. 153 | split_penalty_logical_operator=300 154 | 155 | # Use the Tab character for indentation. 156 | use_tabs=False 157 | -------------------------------------------------------------------------------- /HISTORY.rst: -------------------------------------------------------------------------------- 1 | ======= 2 | History 3 | ======= 4 | 5 | 0.1.0 (2016-12-07) 6 | ------------------ 7 | 8 | * First release. 9 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Divio AG 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. -------------------------------------------------------------------------------- /MANIFEST.in: -------------------------------------------------------------------------------- 1 | include HISTORY.rst LICENSE README.rst 2 | 3 | recursive-include tests * 4 | 5 | recursive-exclude * __pycache__ 6 | recursive-exclude * *.py[co] 7 | -------------------------------------------------------------------------------- /README.rst: -------------------------------------------------------------------------------- 1 | =============================== 2 | Mautic Python 3 | =============================== 4 | 5 | Python wrapper for Mautic API based on `requests-oauthlib `_ 6 | 7 | Installation 8 | ------------ 9 | 10 | Clone repo from GitHub_:: 11 | 12 | $ git clone https://github.com/divio/python-mautic.git 13 | 14 | Then install it by running:: 15 | 16 | $ python setup.py install 17 | 18 | 19 | Quickstart 20 | ---------- 21 | Put your Mautic API credentials in `apitester/oauth2_app.py` 22 | Run Flask app to get OAuth2 token:: 23 | 24 | $ python apitester/oauth2_app.py 25 | 26 | This way you'll have `creds.json` in temporary directory. Now you can start using Mautic API: 27 | 28 | .. code-block:: python 29 | 30 | >>> from python_mautic import MauticOauth2Client, Contacts 31 | >>> from python_mautic.utils import read_token_tempfile 32 | >>> token = read_token_tempfile() 33 | >>> mautic = MauticOauth2Client(base_url='', client_id='', token=token) 34 | >>> contacts = Contacts(client=mautic) 35 | >>> print(contacts.get_list()) 36 | -------------------------------------------------------------------------------- /apitester/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | -------------------------------------------------------------------------------- /apitester/oauth2_app.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from pprint import pformat 5 | import os 6 | 7 | from flask import Flask, request, redirect, session, url_for, jsonify 8 | from requests_oauthlib import OAuth2Session 9 | 10 | from python_mautic import MauticOauth2Client, Contacts 11 | from python_mautic.utils import update_token_tempfile 12 | 13 | app = Flask(__name__) 14 | 15 | client_id = '' # put here your Mautic API Public Key 16 | client_secret = '' # put here your Mautic API Secret Key 17 | redirect_uri = 'http://localhost:8000/callback' 18 | 19 | 20 | base_url = '' # put here base URL for your Mautic API. E.g. `https://.mautic.net` 21 | 22 | base_url = '{}/'.format(base_url.strip(' /')) 23 | authorization_base_url = base_url + 'oauth/v2/authorize' 24 | token_url = base_url + 'oauth/v2/token' 25 | refresh_url = token_url 26 | 27 | api_base_url = base_url + 'api/' 28 | 29 | extra = { 30 | 'client_id': client_id, 31 | 'client_secret': client_secret 32 | } 33 | 34 | 35 | @app.route("/") 36 | def index(): 37 | """Step 1: User Authorization. 38 | 39 | Redirect the user/resource owner to the OAuth provider using an URL with a few key OAuth parameters. 40 | """ 41 | mautic = OAuth2Session(client_id, redirect_uri=redirect_uri) 42 | authorization_url, state = mautic.authorization_url(authorization_base_url, grant_type='authorization_code') 43 | 44 | session['oauth_state'] = state 45 | return redirect(authorization_url) 46 | 47 | 48 | # Step 2: User authorization, this happens on the provider. 49 | @app.route("/callback", methods=['GET']) 50 | def callback(): 51 | """ Step 3: Retrieving an access token. 52 | 53 | The user has been redirected back from the provider to your registered 54 | callback URL. With this redirection comes an authorization code included 55 | in the redirect URL. We will use that to obtain an access token. 56 | """ 57 | 58 | mautic = OAuth2Session(client_id, redirect_uri=redirect_uri, 59 | state=session['oauth_state']) 60 | token = mautic.fetch_token(token_url, client_secret=client_secret, authorization_response=request.url) 61 | 62 | # We use the session as a simple DB for this example. 63 | session['oauth_token'] = token 64 | update_token_tempfile(token) # store token in /tmp/mautic_creds.json 65 | 66 | return redirect(url_for('.menu')) 67 | 68 | 69 | @app.route("/menu", methods=["GET"]) 70 | def menu(): 71 | """""" 72 | return """ 73 |

Congratulations, you have obtained an OAuth 2 token!

74 |

What would you like to do next?

75 | 78 | 79 |
 80 |     %s
 81 |     
82 | """ % pformat(session['oauth_token'], indent=4) 83 | 84 | 85 | @app.route("/contacts", methods=["GET"]) 86 | def contacts(): 87 | """Fetching a protected resource using an OAuth 2 token. 88 | """ 89 | mautic = MauticOauth2Client(base_url=base_url, 90 | client_id=client_id, 91 | client_secret=client_secret, 92 | token=session['oauth_token'], 93 | token_updater=update_token_tempfile) 94 | return jsonify(Contacts(client=mautic).get_list()) 95 | 96 | 97 | if __name__ == "__main__": 98 | app.config['DEBUG'] = True 99 | app.config['SECRET_KEY'] = 'not_to_be_used_in_production' 100 | app.config['SERVER_NAME'] = 'localhost:8000' 101 | 102 | os.environ['DEBUG'] = '1' 103 | os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' 104 | app.run(host='localhost', port=8000) 105 | -------------------------------------------------------------------------------- /mautic/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from .exceptions import ( 3 | ActionNotSupportedException, 4 | ContextNotFoundException, 5 | MauticException, 6 | UnexpectedResponseFormatException, 7 | ) 8 | from .api import MauticOauth2Client, MauticBasicAuthClient 9 | from .assets import Assets 10 | from .campaigns import Campaigns 11 | from .categories import Categories 12 | from .companies import Companies 13 | from .company_fields import CompanyFields 14 | from .contacts import Contacts 15 | from .contact_fields import ContactFields 16 | from .contacts import Contacts 17 | from .data import Data 18 | from .dynamic_contents import DynamicContents 19 | from .emails import Emails 20 | from .files import Files 21 | from .forms import Forms 22 | from .notes import Notes 23 | from .notifications import Notifications 24 | from .pages import Pages 25 | from .point_triggers import PointTriggers 26 | from .points import Points 27 | from .reports import Reports 28 | from .roles import Roles 29 | from .segments import Segments 30 | from .smses import Smses 31 | from .stages import Stages 32 | from .stats import Stats 33 | from .users import Users 34 | 35 | __version__ = '0.1.0' 36 | -------------------------------------------------------------------------------- /mautic/api.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | import requests 5 | from requests_oauthlib import OAuth2Session 6 | 7 | 8 | class MauticOauth2Client(object): 9 | def __init__( 10 | self, 11 | base_url, 12 | client_id, 13 | client_secret=None, 14 | scope=None, 15 | token=None, 16 | token_updater=None 17 | ): 18 | """ 19 | :param base_url: str Base URL for Mautic API E.g. `https://.mautic.net` 20 | :param client_id: str Mautic API Public Key 21 | :param client_secret: str Mautic API Secret Key - needed to autorefresh token 22 | :param scope: list|str 23 | :param token: dict with access token data 24 | :param token_updater: function used for token autorefresh. 25 | """ 26 | if scope and not isinstance(scope, (list, tuple)): 27 | scope = scope.split(',') 28 | self.base_url = base_url.strip(' /') 29 | 30 | self.access_token_url = base_url + '/oauth/v2/token' 31 | self.authorization_base_url = base_url + '/oauth/v2/authorize' 32 | 33 | if token_updater is not None and client_secret is not None: 34 | kwargs = { 35 | 'auto_refresh_url': self.access_token_url, 36 | 'auto_refresh_kwargs': { 37 | 'client_id': client_id, 38 | 'client_secret': client_secret 39 | }, 40 | 'token_updater': token_updater 41 | } 42 | else: 43 | kwargs = {} 44 | 45 | self.session = OAuth2Session( 46 | client_id, scope=scope, token=token, **kwargs 47 | ) 48 | 49 | 50 | class MauticBasicAuthClient(object): 51 | def __init__( 52 | self, 53 | base_url, 54 | username, 55 | password 56 | ): 57 | """ 58 | :param base_url: str Base URL for Mautic API E.g. `https://.mautic.net` 59 | :param username: str Mautic Username 60 | :param password: str Mautic Password 61 | """ 62 | 63 | self.base_url = base_url.strip(' /') 64 | 65 | self.session = requests.Session() 66 | self.session.auth = (username, password) 67 | 68 | 69 | class API(object): 70 | _endpoint = '' 71 | 72 | def __init__(self, client): 73 | self._client = client 74 | self.endpoint_url = '{base_url}/api/{endpoint}'.format( 75 | base_url=self._client.base_url, 76 | endpoint=self._endpoint.strip(' /') 77 | ) 78 | 79 | @staticmethod 80 | def process_response(response): 81 | if response.ok: 82 | return response.json() 83 | try: 84 | return response.json() 85 | except ValueError: 86 | # no json object could be decoded 87 | return response.content 88 | 89 | @staticmethod 90 | def action_not_supported(action): 91 | """ 92 | Returns a not supported error 93 | :param action: str 94 | :return: dict 95 | """ 96 | return { 97 | 'error': { 98 | 'code': 500, 99 | 'message': 100 | '{action} is not supported at this time'.format(action=action) 101 | } 102 | } 103 | 104 | def get(self, obj_id): 105 | """ 106 | Get a single item 107 | 108 | :param obj_id: int 109 | :return: dict|str 110 | """ 111 | response = self._client.session.get( 112 | '{url}/{id}'.format( 113 | url=self.endpoint_url, id=obj_id 114 | ) 115 | ) 116 | return self.process_response(response) 117 | 118 | def get_list( 119 | self, 120 | search='', 121 | start=0, 122 | limit=0, 123 | order_by='', 124 | order_by_dir='ASC', 125 | published_only=False, 126 | minimal=False 127 | ): 128 | """ 129 | Get a list of items 130 | 131 | :param search: str 132 | :param start: int 133 | :param limit: int 134 | :param order_by: str 135 | :param order_by_dir: str 136 | :param published_only: bool 137 | :param minimal: bool 138 | :return: dict|str 139 | """ 140 | 141 | parameters = {} 142 | args = ['search', 'start', 'limit', 'minimal'] 143 | for arg in args: 144 | if arg in locals() and locals()[arg]: 145 | parameters[arg] = locals()[arg] 146 | if order_by: 147 | parameters['orderBy'] = order_by 148 | if order_by_dir: 149 | parameters['orderByDir'] = order_by_dir 150 | if published_only: 151 | parameters['publishedOnly'] = 'true' 152 | response = self._client.session.get( 153 | self.endpoint_url, params=parameters 154 | ) 155 | return self.process_response(response) 156 | 157 | def get_published_list( 158 | self, search='', start=0, limit=0, order_by='', order_by_dir='ASC' 159 | ): 160 | """ 161 | Proxy function to get_list with published_only set to True 162 | :param search: str 163 | :param start: int 164 | :param limit: int 165 | :param order_by: str 166 | :param order_by_dir: str 167 | :return: dict|str 168 | """ 169 | return self.get_list( 170 | search=search, 171 | start=start, 172 | limit=limit, 173 | order_by=order_by, 174 | order_by_dir=order_by_dir, 175 | published_only=True 176 | ) 177 | 178 | def create(self, parameters): 179 | """ 180 | Create a new item (if supported) 181 | 182 | :param parameters: dict 183 | :return: dict|str 184 | """ 185 | response = self._client.session.post( 186 | '{url}/new'.format(url=self.endpoint_url), data=parameters 187 | ) 188 | return self.process_response(response) 189 | 190 | def edit(self, obj_id, parameters, create_if_not_exists=False): 191 | """ 192 | Edit an item with option to create if it doesn't exist 193 | 194 | :param obj_id: int 195 | :param create_if_not_exists: bool 196 | :param parameters: dict 197 | :return: dict|str 198 | """ 199 | if create_if_not_exists: 200 | response = self._client.session.put( 201 | '{url}/{id}/edit'.format( 202 | url=self.endpoint_url, id=obj_id 203 | ), 204 | data=parameters 205 | ) 206 | else: 207 | response = self._client.session.patch( 208 | '{url}/{id}/edit'.format( 209 | url=self.endpoint_url, id=obj_id 210 | ), 211 | data=parameters 212 | ) 213 | return self.process_response(response) 214 | 215 | def delete(self, obj_id): 216 | """ 217 | Delete an item 218 | 219 | :param obj_id: int 220 | :return: dict|str 221 | """ 222 | response = self._client.session.delete( 223 | '{url}/{id}/delete'.format( 224 | url=self.endpoint_url, id=obj_id 225 | ) 226 | ) 227 | return self.process_response(response) 228 | -------------------------------------------------------------------------------- /mautic/assets.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Assets(API): 8 | _endpoint = 'assets' 9 | -------------------------------------------------------------------------------- /mautic/campaigns.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Campaigns(API): 8 | _endpoint = 'campaigns' 9 | 10 | def add_contact(self, obj_id, contact_id): 11 | """ 12 | Add a contact to the campaign 13 | 14 | :param obj_id: int Campaign ID 15 | :param contact_id: int Contact ID 16 | :return: dict|str 17 | """ 18 | response = self._client.session.post( 19 | '{url}/{id}/contact/add/{contact_id}'.format( 20 | url=self.endpoint_url, id=obj_id, contact_id=contact_id 21 | ) 22 | ) 23 | return self.process_response(response) 24 | 25 | def remove_contact(self, obj_id, contact_id): 26 | """ 27 | Remove a contact from the campaign 28 | 29 | :param obj_id: int Campaign ID 30 | :param contact_id: int Contact ID 31 | :return: dict|str 32 | """ 33 | response = self._client.session.post( 34 | '{url}/{id}/contact/remove/{contact_id}'.format( 35 | url=self.endpoint_url, id=obj_id, contact_id=contact_id 36 | ) 37 | ) 38 | return self.process_response(response) 39 | -------------------------------------------------------------------------------- /mautic/categories.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Categories(API): 8 | _endpoint = 'categories' 9 | -------------------------------------------------------------------------------- /mautic/companies.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Companies(API): 8 | _endpoint = 'companies' 9 | 10 | def add_contact(self, obj_id, contact_id): 11 | """ 12 | Add a contact to the company 13 | 14 | :param obj_id: int Company ID 15 | :param contact_id: int Contact ID 16 | :return: dict|str 17 | """ 18 | response = self._client.session.post( 19 | '{url}/{id}/contact/add/{contact_id}'.format( 20 | url=self.endpoint_url, id=obj_id, contact_id=contact_id 21 | ) 22 | ) 23 | return self.process_response(response) 24 | 25 | def remove_contact(self, obj_id, contact_id): 26 | """ 27 | Remove a contact from the company 28 | 29 | :param obj_id: int Campaign ID 30 | :param contact_id: int Contact ID 31 | :return: dict|str 32 | """ 33 | response = self._client.session.post( 34 | '{url}/{id}/contact/remove/{contact_id}'.format( 35 | url=self.endpoint_url, id=obj_id, contact_id=contact_id 36 | ) 37 | ) 38 | return self.process_response(response) 39 | -------------------------------------------------------------------------------- /mautic/company_fields.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class CompanyFields(API): 8 | _endpoint = 'fields/company' 9 | -------------------------------------------------------------------------------- /mautic/contact_fields.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class ContactFields(API): 8 | _endpoint = 'fields/contact' 9 | -------------------------------------------------------------------------------- /mautic/contacts.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Contacts(API): 8 | # Contact unsubscribed themselves. 9 | UNSUBSCRIBED = 1 10 | # Contact was unsubscribed due to an unsuccessful send. 11 | BOUNCED = 2 12 | # Contact was manually unsubscribed by user. 13 | MANUAL = 3 14 | 15 | _endpoint = 'contacts' 16 | 17 | def get_owners(self): 18 | """ 19 | Get a list of users available as contact owners 20 | 21 | :return: dict|str 22 | """ 23 | response = self._client.session.get( 24 | '{url}/list/owners'.format(url=self.endpoint_url) 25 | ) 26 | return self.process_response(response) 27 | 28 | def get_field_list(self): 29 | """ 30 | Get a list of custom fields 31 | 32 | :return: dict|str 33 | """ 34 | response = self._client.session.get( 35 | '{url}/list/fields'.format(url=self.endpoint_url) 36 | ) 37 | return self.process_response(response) 38 | 39 | def get_segments(self): 40 | """ 41 | Get a list of contact segments 42 | 43 | :return: dict|str 44 | """ 45 | response = self._client.session.get( 46 | '{url}/list/segments'.format(url=self.endpoint_url) 47 | ) 48 | return self.process_response(response) 49 | 50 | def get_events( 51 | self, 52 | obj_id, 53 | search='', 54 | include_events=None, 55 | exclude_events=None, 56 | order_by='', 57 | order_by_dir='ASC', 58 | page=1 59 | ): 60 | """ 61 | Get a list of a contact's engagement events 62 | 63 | :param obj_id: int Contact ID 64 | :param search: str 65 | :param include_events: list|tuple 66 | :param exclude_events: list|tuple 67 | :param order_by: str 68 | :param order_by_dir: str 69 | :param page: int 70 | :return: dict|str 71 | """ 72 | if include_events is None: 73 | include_events = [] 74 | if exclude_events is None: 75 | exclude_events = [] 76 | 77 | parameters = { 78 | 'search': search, 79 | 'includeEvents': include_events, 80 | 'excludeEvents': exclude_events, 81 | 'orderBy': order_by, 82 | 'orderByDir': order_by_dir, 83 | 'page': page 84 | } 85 | response = self._client.session.get( 86 | '{url}/{id}/events'.format( 87 | url=self.endpoint_url, id=obj_id 88 | ), 89 | params=parameters 90 | ) 91 | return self.process_response(response) 92 | 93 | def get_contact_notes( 94 | self, 95 | obj_id, 96 | search='', 97 | start=0, 98 | limit=0, 99 | order_by='', 100 | order_by_dir='ASC' 101 | ): 102 | """ 103 | Get a list of a contact's notes 104 | 105 | :param obj_id: int Contact ID 106 | :param search: str 107 | :param start: int 108 | :param limit: int 109 | :param order_by: str 110 | :param order_by_dir: str 111 | :return: dict|str 112 | """ 113 | 114 | parameters = { 115 | 'search': search, 116 | 'start': start, 117 | 'limit': limit, 118 | 'orderBy': order_by, 119 | 'orderByDir': order_by_dir, 120 | } 121 | response = self._client.session.get( 122 | '{url}/{id}/notes'.format( 123 | url=self.endpoint_url, id=obj_id 124 | ), 125 | params=parameters 126 | ) 127 | return self.process_response(response) 128 | 129 | def get_contact_segments(self, obj_id): 130 | """ 131 | Get a segment of smart segments the contact is in 132 | 133 | :param obj_id: int 134 | :return: dict|str 135 | """ 136 | 137 | response = self._client.session.get( 138 | '{url}/{id}/segments'.format( 139 | url=self.endpoint_url, id=obj_id 140 | ) 141 | ) 142 | return self.process_response(response) 143 | 144 | def get_contact_campaigns(self, obj_id): 145 | """ 146 | Get a segment of campaigns the contact is in 147 | 148 | :param obj_id: int 149 | :return: dict|str 150 | """ 151 | 152 | response = self._client.session.get( 153 | '{url}/{id}/campaigns'.format( 154 | url=self.endpoint_url, id=obj_id 155 | ) 156 | ) 157 | return self.process_response(response) 158 | 159 | def add_points(self, obj_id, points, **kwargs): 160 | """ 161 | Add the points to a contact 162 | 163 | :param obj_id: int 164 | :param points: int 165 | :param kwargs: dict 'eventname' and 'actionname' 166 | :return: dict|str 167 | """ 168 | 169 | response = self._client.session.post( 170 | '{url}/{id}/points/plus/{points}'.format( 171 | url=self.endpoint_url, id=obj_id, points=points 172 | ), 173 | data=kwargs 174 | ) 175 | return self.process_response(response) 176 | 177 | def subtract_points(self, obj_id, points, **kwargs): 178 | """ 179 | Subtract points from a contact 180 | 181 | :param obj_id: int 182 | :param points: int 183 | :param kwargs: dict 'eventname' and 'actionname' 184 | :return: dict|str 185 | """ 186 | 187 | response = self._client.session.post( 188 | '{url}/{id}/points/minus/{points}'.format( 189 | url=self.endpoint_url, id=obj_id, points=points 190 | ), 191 | data=kwargs 192 | ) 193 | return self.process_response(response) 194 | 195 | def add_dnc( 196 | self, 197 | obj_id, 198 | channel='email', 199 | reason=MANUAL, 200 | channel_id=None, 201 | comments='via API' 202 | ): 203 | """ 204 | Adds Do Not Contact 205 | 206 | :param obj_id: int 207 | :param channel: str 208 | :param reason: str 209 | :param channel_id: int 210 | :param comments: str 211 | :return: dict|str 212 | """ 213 | data = { 214 | 'reason': reason, 215 | 'channelId': channel_id, 216 | 'comments': comments 217 | } 218 | response = self._client.session.post( 219 | '{url}/{id}/dnc/add/{channel}'.format( 220 | url=self.endpoint_url, id=obj_id, channel=channel 221 | ), 222 | data=data 223 | ) 224 | return self.process_response(response) 225 | 226 | def remove_dnc(self, obj_id, channel): 227 | """ 228 | Removes Do Not Contact 229 | 230 | :param obj_id: int 231 | :param channel: str 232 | :return: dict|str 233 | """ 234 | response = self._client.session.post( 235 | '{url}/{id}/dnc/remove/{channel}'.format( 236 | url=self.endpoint_url, id=obj_id, channel=channel 237 | ) 238 | ) 239 | return self.process_response(response) 240 | -------------------------------------------------------------------------------- /mautic/data.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Data(API): 8 | _endpoint = 'data' 9 | 10 | def get(self, data_type, options=None): 11 | """ 12 | Get a single item 13 | :param data_type: str 14 | :param options: dict 15 | :return: dict|str 16 | """ 17 | if options is None: 18 | options = {} 19 | response = self._client.session.get( 20 | '{url}/{type}'.format( 21 | url=self.endpoint_url, type=data_type 22 | ), 23 | params=options 24 | ) 25 | return self.process_response(response) 26 | -------------------------------------------------------------------------------- /mautic/dynamic_contents.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class DynamicContents(API): 8 | _endpoint = 'dynamiccontents' 9 | -------------------------------------------------------------------------------- /mautic/emails.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Emails(API): 8 | _endpoint = 'emails' 9 | 10 | def send(self, obj_id): 11 | """ 12 | Send email to the assigned lists 13 | 14 | :param obj_id: int 15 | :return: dict|str 16 | """ 17 | response = self._client.session.post( 18 | '{url}/{id}/send'.format( 19 | url=self.endpoint_url, id=obj_id 20 | ) 21 | ) 22 | return self.process_response(response) 23 | 24 | def send_to_contact(self, obj_id, contact_id): 25 | """ 26 | Send email to a specific contact 27 | 28 | :param obj_id: int 29 | :param contact_id: int 30 | :return: dict|str 31 | """ 32 | response = self._client.session.post( 33 | '{url}/{id}/send/contact/{contact_id}'.format( 34 | url=self.endpoint_url, id=obj_id, contact_id=contact_id 35 | ) 36 | ) 37 | return self.process_response(response) 38 | -------------------------------------------------------------------------------- /mautic/exceptions.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | 5 | class MauticException(Exception): 6 | """ 7 | Base class for Mautic exceptions. 8 | """ 9 | code = 500 10 | default_message = 'Mautic error occurred.' 11 | 12 | def __init__(self, message=None): 13 | if message is not None: 14 | self.message = message 15 | else: 16 | self.message = self.default_message 17 | 18 | def __repr__(self): 19 | return '{} {}'.format( 20 | type(self).__name__, 21 | self.message, 22 | ) 23 | 24 | def __str__(self): 25 | return self.__repr__() 26 | 27 | 28 | class ActionNotSupportedException(MauticException): 29 | """ 30 | Exception representing an unsupported action 31 | """ 32 | 33 | default_message = 'Action is not supported at this time.' 34 | 35 | 36 | class ContextNotFoundException(MauticException): 37 | """ 38 | Exception representing a requested API context which was not found 39 | """ 40 | 41 | default_message = 'Context not found.' 42 | 43 | 44 | class IncorrectParametersReturnedException(MauticException): 45 | """ 46 | Exception representing an incorrect parameter set for an OAuth token request 47 | """ 48 | 49 | default_message = 'Incorrect parameters returned.' 50 | 51 | 52 | class UnexpectedResponseFormatException(MauticException): 53 | """ 54 | Exception representing an unexpected HTTP response 55 | """ 56 | 57 | default_message = 'The response returned is in an unexpected format.' 58 | -------------------------------------------------------------------------------- /mautic/files.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Files(API): 8 | _endpoint = 'files/images' 9 | 10 | def set_folder(self, folder='assets'): 11 | """ 12 | Changes the file folder to look at 13 | 14 | :param folder: str [images, assets] 15 | :return: None 16 | """ 17 | folder = folder.replace('/', '.') 18 | self._endpoint = 'files/{folder}'.format(folder=folder) 19 | 20 | def edit(self, obj_id, parameters=None, create_if_not_exists=False): 21 | return self.action_not_supported('edit') 22 | -------------------------------------------------------------------------------- /mautic/forms.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Forms(API): 8 | _endpoint = 'forms' 9 | 10 | def delete_fields(self, form_id, field_ids): 11 | """ 12 | Remove fields from a form 13 | 14 | :param form_id: int 15 | :param field_ids: list|tuple 16 | :return: dict|str 17 | """ 18 | 19 | response = self._client.session.delete( 20 | '{url}/{form_id}/fields/delete'.format( 21 | url=self.endpoint_url, form_id=form_id 22 | ), 23 | params={'fields': field_ids} 24 | ) 25 | return self.process_response(response) 26 | 27 | def delete_actions(self, form_id, action_ids): 28 | """ 29 | Remove actions from a form 30 | 31 | :param form_id: int 32 | :param action_ids: list|tuple 33 | :return: dict|str 34 | """ 35 | 36 | response = self._client.session.delete( 37 | '{url}/{form_id}/actions/delete'.format( 38 | url=self.endpoint_url, form_id=form_id 39 | ), 40 | params={'actions': action_ids} 41 | ) 42 | return self.process_response(response) 43 | -------------------------------------------------------------------------------- /mautic/notes.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Notes(API): 8 | _endpoint = 'notes' 9 | -------------------------------------------------------------------------------- /mautic/notifications.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Notifications(API): 8 | _endpoint = 'notifications' 9 | -------------------------------------------------------------------------------- /mautic/pages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Pages(API): 8 | _endpoint = 'pages' 9 | -------------------------------------------------------------------------------- /mautic/point_triggers.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class PointTriggers(API): 8 | _endpoint = 'points/triggers' 9 | 10 | def delete_trigger_events(self, trigger_id, event_ids): 11 | """ 12 | Remove events from a point trigger 13 | 14 | :param trigger_id: int 15 | :param event_ids: list|tuple 16 | :return: dict|str 17 | """ 18 | 19 | response = self._client.session.delete( 20 | '{url}/{trigger_id}/events/delete'.format( 21 | url=self.endpoint_url, trigger_id=trigger_id 22 | ), 23 | params={'events': event_ids} 24 | ) 25 | return self.process_response(response) 26 | -------------------------------------------------------------------------------- /mautic/points.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Points(API): 8 | _endpoint = 'points' 9 | 10 | def get_point_action_types(self): 11 | response = self._client.session.get( 12 | '{url}/actions/types'.format(url=self.endpoint_url) 13 | ) 14 | return self.process_response(response) 15 | -------------------------------------------------------------------------------- /mautic/reports.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Reports(API): 8 | _endpoint = 'reports' 9 | 10 | def create(self, parameters): 11 | return self.action_not_supported('create') 12 | 13 | def edit(self, obj_id, parameters, create_if_not_exists=False): 14 | return self.action_not_supported('edit') 15 | 16 | def delete(self, obj_id): 17 | return self.action_not_supported('delete') 18 | -------------------------------------------------------------------------------- /mautic/roles.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Roles(API): 8 | _endpoint = 'roles' 9 | -------------------------------------------------------------------------------- /mautic/segments.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Segments(API): 8 | _endpoint = 'segments' 9 | 10 | def add_contact(self, segment_id, contact_id): 11 | """ 12 | Add a contact to the segment 13 | 14 | :param segment_id: int Segment ID 15 | :param contact_id: int Contact ID 16 | :return: dict|str 17 | """ 18 | 19 | response = self._client.session.post( 20 | '{url}/{segment_id}/contact/add/{contact_id}'.format( 21 | url=self.endpoint_url, 22 | segment_id=segment_id, 23 | contact_id=contact_id 24 | ) 25 | ) 26 | return self.process_response(response) 27 | -------------------------------------------------------------------------------- /mautic/smses.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Smses(API): 8 | _endpoint = 'smses' 9 | -------------------------------------------------------------------------------- /mautic/stages.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Stages(API): 8 | _endpoint = 'stages' 9 | 10 | def add_contact(self, obj_id, contact_id): 11 | """ 12 | Add a contact to the stage 13 | 14 | :param obj_id: int Stage ID 15 | :param contact_id: int Contact ID 16 | :return: dict|str 17 | """ 18 | response = self._client.session.post( 19 | '{url}/{id}/contact/add/{contact_id}'.format( 20 | url=self.endpoint_url, id=obj_id, contact_id=contact_id 21 | ) 22 | ) 23 | return self.process_response(response) 24 | 25 | def remove_contact(self, obj_id, contact_id): 26 | """ 27 | Remove a contact from the stage 28 | 29 | :param obj_id: int Stage ID 30 | :param contact_id: int Contact ID 31 | :return: dict|str 32 | """ 33 | response = self._client.session.post( 34 | '{url}/{id}/contact/remove/{contact_id}'.format( 35 | url=self.endpoint_url, id=obj_id, contact_id=contact_id 36 | ) 37 | ) 38 | return self.process_response(response) 39 | -------------------------------------------------------------------------------- /mautic/stats.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Stats(API): 8 | _endpoint = 'stats' 9 | 10 | def get(self, table='', start=0, limit=0, order=None, where=None): 11 | """ 12 | Get a list of stat items 13 | 14 | :param table: str database table name 15 | :param start: int 16 | :param limit: int 17 | :param order: list|tuple 18 | :param where: list|tuple 19 | :return: 20 | """ 21 | parameters = {} 22 | 23 | args = ['start', 'limit', 'order', 'where'] 24 | for arg in args: 25 | if arg in locals() and locals()[arg]: 26 | parameters[arg] = locals()[arg] 27 | 28 | response = self._client.session.get( 29 | '{url}/{table}'.format( 30 | url=self.endpoint_url, table=table 31 | ), 32 | params=parameters 33 | ) 34 | return self.process_response(response) 35 | 36 | def delete(self, obj_id): 37 | return self.action_not_supported('delete') 38 | 39 | def get_list( 40 | self, 41 | search='', 42 | start=0, 43 | limit=0, 44 | order_by='', 45 | order_by_dir='ASC', 46 | published_only=False, 47 | minimal=False 48 | ): 49 | return self.action_not_supported('get_list') 50 | 51 | def create(self, parameters): 52 | return self.action_not_supported('create') 53 | 54 | def get_published_list( 55 | self, search='', start=0, limit=0, order_by='', order_by_dir='ASC' 56 | ): 57 | return self.action_not_supported('get_published_list') 58 | 59 | def edit(self, obj_id, parameters, create_if_not_exists=False): 60 | return self.action_not_supported('edit') 61 | -------------------------------------------------------------------------------- /mautic/users.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | from .api import API 5 | 6 | 7 | class Users(API): 8 | _endpoint = 'users' 9 | 10 | def get_self(self): 11 | """ 12 | Get your (API) user 13 | 14 | :return: dict|str 15 | """ 16 | response = self._client.session.get( 17 | '{url}/self'.format(url=self.endpoint_url) 18 | ) 19 | return self.process_response(response) 20 | 21 | def check_permission(self, obj_id, permissions): 22 | """ 23 | Get list of permissions for a user 24 | 25 | :param obj_id: int 26 | :param permissions: str|list|tuple 27 | :return: dict|str 28 | """ 29 | response = self._client.session.post( 30 | '{url}/{id}/permissioncheck'.format( 31 | url=self.endpoint_url, id=obj_id 32 | ), 33 | data={'permissions': permissions} 34 | ) 35 | return self.process_response(response) 36 | -------------------------------------------------------------------------------- /mautic/utils.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | from __future__ import unicode_literals, absolute_import 3 | 4 | import json 5 | import os 6 | from tempfile import gettempdir 7 | 8 | tmp = os.path.join(gettempdir(), 'mautic_creds.json') 9 | 10 | 11 | def read_token_tempfile(): 12 | """ 13 | Example of function for getting stored token 14 | :return: token dict 15 | """ 16 | with open(tmp, 'r') as f: 17 | return json.loads(f.read()) 18 | 19 | 20 | def update_token_tempfile(token): 21 | """ 22 | Example of function for token update 23 | """ 24 | with open(tmp, 'w') as f: 25 | f.write(json.dumps(token, indent=4)) 26 | -------------------------------------------------------------------------------- /requirements_dev.txt: -------------------------------------------------------------------------------- 1 | flake8 2 | tox 3 | coverage 4 | Sphinx 5 | Flask 6 | yapf 7 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | import re 4 | 5 | from setuptools import setup, find_packages 6 | 7 | 8 | with open('mautic/__init__.py', 'r') as fd: 9 | version = re.search( 10 | r'^__version__\s*=\s*[\'"]([^\'"]*)[\'"]', 11 | fd.read(), re.MULTILINE 12 | ).group(1) 13 | 14 | with open('README.rst', 'r') as readme_file: 15 | readme = readme_file.read() 16 | 17 | with open('HISTORY.rst', 'r') as history_file: 18 | history = history_file.read() 19 | 20 | requirements = [ 21 | 'requests-oauthlib', 22 | ] 23 | 24 | test_requirements = [] 25 | 26 | setup( 27 | name='mautic', 28 | version=version, 29 | description='Python wrapper for Mautic API', 30 | long_description=readme + '\n\n' + history, 31 | author='Divio AG', 32 | author_email='info@divio.com', 33 | url='https://github.com/divio/python-mautic', 34 | packages=find_packages(), 35 | include_package_data=True, 36 | install_requires=requirements, 37 | license='LICENSE', 38 | zip_safe=False, 39 | classifiers=[ 40 | 'Development Status :: 2 - Pre-Alpha', 41 | 'Intended Audience :: Developers', 42 | 'License :: OSI Approved :: MIT License', 43 | 'Natural Language :: English', 44 | 'Programming Language :: Python :: 2', 45 | 'Programming Language :: Python :: 2.6', 46 | 'Programming Language :: Python :: 2.7', 47 | 'Programming Language :: Python :: 3', 48 | 'Programming Language :: Python :: 3.3', 49 | 'Programming Language :: Python :: 3.4', 50 | 'Programming Language :: Python :: 3.5', 51 | ], 52 | test_suite='tests', 53 | tests_require=test_requirements 54 | ) 55 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | -------------------------------------------------------------------------------- /tests/test_mautic_python.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # -*- coding: utf-8 -*- 3 | -------------------------------------------------------------------------------- /tox.ini: -------------------------------------------------------------------------------- 1 | [tox] 2 | envlist = py26, py27, py33, py34, py35, flake8 3 | 4 | [testenv:flake8] 5 | basepython=python 6 | deps=flake8 7 | commands=flake8 python_mautic 8 | 9 | [testenv] 10 | setenv = 11 | PYTHONPATH = {toxinidir}:{toxinidir}/python_mautic 12 | deps = 13 | -r{toxinidir}/requirements_dev.txt 14 | commands = 15 | pip install -U pip 16 | py.test --basetemp={envtmpdir} 17 | 18 | 19 | ; If you want to make tox run the tests with the same versions, create a 20 | ; requirements.txt with the pinned versions and uncomment the following lines: 21 | ; deps = 22 | ; -r{toxinidir}/requirements.txt 23 | --------------------------------------------------------------------------------