Code Monkey home page Code Monkey logo

createsend-python's Introduction

createsend

A Python library which implements the complete functionality of the Campaign Monitor API. Requires Python 2.7, 3.4, 3.5, 3.6, 3.7, 3.8, 3.9 or 3.10.

Installation

pip install createsend

Authenticating

The Campaign Monitor API supports authentication using either OAuth or an API key.

Using OAuth

Depending on the environment you are developing in, you may wish to use a Python OAuth library to get access tokens for your users. If you use Flask, you may like to refer to this example application, which uses the Flask-OAuth package to authenticate.

If you don't use an OAuth library, you will need to manually get access tokens for your users by following the instructions included in the Campaign Monitor API documentation. This package provides functionality to help you do this, as described below. There's also another Flask example application you may wish to reference, which doesn't depend on any OAuth libraries.

The first thing your application should do is redirect your user to the Campaign Monitor authorization URL where they will have the opportunity to approve your application to access their Campaign Monitor account. You can get this authorization URL by using the authorize_url() function, like so:

from createsend import *

cs = CreateSend()
authorize_url = cs.authorize_url(
  client_id='Client ID for your application',
  redirect_uri='Redirect URI for your application',
  scope='The permission level your application requires',
  state='Optional state data to be included'
)
# Redirect your users to authorize_url.

If your user approves your application, they will then be redirected to the redirect_uri you specified, which will include a code parameter, and optionally a state parameter in the query string. Your application should implement a handler which can exchange the code passed to it for an access token, using the exchange_token() function like so:

from createsend import *

cs = CreateSend()
access_token, expires_in, refresh_token = cs.exchange_token(
  client_id='Client ID for your application',
  client_secret='Client Secret for your application',
  redirect_uri='Redirect URI for your application',
  code='A unique code for your user' # Get the code parameter from the query string
)
# Save access_token, expires_in, and refresh_token.

At this point you have an access token and refresh token for your user which you should store somewhere convenient so that your application can look up these values when your user wants to make future Campaign Monitor API calls.

Once you have an access token and refresh token for your user, you can authenticate and make further API calls like so:

from createsend import *

cs = CreateSend({
  'access_token': 'your access token',
  'refresh_token': 'your refresh token' })
clients = cs.clients()

All OAuth tokens have an expiry time, and can be renewed with a corresponding refresh token. If your access token expires when attempting to make an API call, the ExpiredOAuthToken exception will be raised, so your code should handle this. Here's an example of how you could do this:

from createsend import *

try:
  cs = CreateSend({
    'access_token': 'your access token',
    'refresh_token': 'your refresh token' })
  clients = cs.clients()
except ExpiredOAuthToken as eot:
  access_token, expires_in, refresh_token = cs.refresh_token()
  # Save your updated access_token, expires_in, and refresh_token.
  clients = cs.clients()
except Exception as e:
  print("Error: %s" % e)

Using an API key

from createsend import *

cs = CreateSend({'api_key': 'your api key'})
clients = cs.clients()

Basic usage

This example of listing all your clients and their draft campaigns demonstrates basic usage of the library and the data returned from the API:

from createsend import *

auth = {
  'access_token': 'your access token',
  'refresh_token': 'your refresh token' }
cs = CreateSend(auth)
clients = cs.clients()

for cl in clients:
  print("Client: %s" % cl.Name)
  client = Client(auth, cl.ClientID)
  print("- Campaigns:")
  for cm in client.drafts():
    print("  - %s" % cm.Subject)

Running this example will result in something like:

Client: First Client
- Campaigns:
  - Newsletter Number One
  - Newsletter Number Two
Client: Second Client
- Campaigns:
  - News for January 2013

Transactional

Sample code that uses Transactional message detail and timeline endpoint API.

from createsend import Transactional
import os
import sys

auth = {'api_key': os.getenv('CREATESEND_API_KEY', '')}
msg_id = os.getenv('MESSAGE_ID', '')

if len(auth) == 0:
    print("API Key Not Provided")
    sys.exit(1)

if len(msg_id) == 0:
    print("Message ID Not Provided")
    sys.exit(1)

#auth = {'api_key': '[api_key]'}
#msg_id = "[message id]" # e.g., becd8473-6a19-1feb-84c5-28d16948a5fc

tx = Transactional(auth)

# Get message details using message id. 
# We can optionally disable loading the body by setting exclude_message_body to `True`.
msg_details = tx.message_details(msg_id, statistics=False, exclude_message_body=True)
print(f'smart email id: {msg_details.SmartEmailID}')
print(f'bounce type: {msg_details.BounceType}')
print(f'bounce category: {msg_details.BounceCategory}')
print(f'html: {msg_details.Message.Body.Html}')
print('--')

# Count the number of bounced mail using message timeline
msg_timeline = tx.message_timeline()
num_bounced = 0
for m in msg_timeline:
    print('--')
    print(f'message id: {m.MessageID}')
    if str.lower(m.Status) == 'bounced':
        num_bounced += 1
        print(f'bounce type: {m.BounceType}')
        print(f'bounce category: {m.BounceCategory}')
print('--')
print(f"total bounces: {num_bounced}")

Handling errors

If the Campaign Monitor API returns an error, an exception will be raised. For example, if you attempt to create a campaign and enter empty values for subject and other required fields:

from createsend import *

campaign = Campaign({
  'access_token': 'your access token',
  'refresh_token': 'your refresh token' })

try:
  id = campaign.create("4a397ccaaa55eb4e6aa1221e1e2d7122", "", "", "", "", "", "", "", [], [])
  print("New campaign ID: %s" % id)
except BadRequest as br:
  print("Bad request error: %s" % br)
  print("Error Code:    %s" % br.data.Code)
  print("Error Message: %s" % br.data.Message)
except Exception as e:
  print("Error: %s" % e)

Running this example will result in:

Bad request error: The CreateSend API responded with the following error - 304: Campaign Subject Required
Error Code:    304
Error Message: Campaign Subject Required

Expected input and output

The best way of finding out the expected input and output of a particular method in a particular class is to use the unit tests as a reference.

For example, if you wanted to find out how to call the Subscriber.add() method, you would look at the file test/test_subscriber.py

def test_add_with_custom_fields(self):
  self.subscriber.stub_request("subscribers/%s.json" % self.list_id, "add_subscriber.json")
  custom_fields = [ { "Key": 'website', "Value": 'http://example.com/' } ]
  email_address = self.subscriber.add(self.list_id, "[email protected]", "Subscriber", custom_fields, True)
  self.assertEquals(email_address, "[email protected]")

Contributing

Please check the guidelines for contributing to this repository.

Releasing

Please check the instructions for releasing the createsend package.

This stuff should be green

Build Status Coverage Status

createsend-python's People

Contributors

27red avatar asadighi avatar gregplaysguitar avatar henrys-cm avatar ishworg avatar izquierdo avatar jdennes avatar katharosada avatar katie-cm avatar mlangsworth avatar petrdlouhy avatar philoye avatar richardbremner avatar russella-acm avatar stoneg avatar tobio avatar tysonclugg avatar yahyaz avatar zachartmann avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

createsend-python's Issues

Python 2.5 compatibility

Please change import json to

try:
import json
except ImportError:
import simplejson as json

Legacy Systems use your api
Thanks

WHATEVER CAMPAIGN MONITOR TEAM THAT OWNS THIS BURNING PILE OF RUBISH PLEASE GET YOUR ACT TOGETHER

This is blunt. This is some of the worst code and worst customer service I and many others have experienced in a long time.

It is extremely unfortunate that your paying customers receive no support for a code base you have decided to drop with no warning. This is a Python 2 project that simply needed to be made compatible with Python 3. Many people who use this are being forced to update to Python 3 due to, well it being 2017, and other libraries dropping support for Python 2. Even the Python Foundation is dropping support for Python 2.

You could have given a intern or junior dev this as busy work. Instead no, you got a professional from another company to spend time to rewrite your library to something that resembles clean code. And now it can't even be pushed and many more of your customers are without a working environment. A number of issues now exist simply because the newest releases I MADE, not your team, needs to be deployed to PyPi. Someone has literally already done the work you were supposed to do, and you can't even spend 30 minutes to deploy the package.

I have reached out to your support team who informed me in ticket reference number: ref:_00Do0eE9F._5001JRs4pf:ref that someone on the "engineering team that work on the Public API" has begun looking at this. Well they certainly have dropped the ball. Still no response, still no commits to the code base from your team. Instead we have myself and @jdennes maintaining your library.

This is pretty frustrating, especially for the great number of engineers out there being forced to maintain connections and write work-arounds to connect to Campaign Monitor simply because we choose Python. I guess the real solution is to not choose Campaign Monitor.

FOR ALL YOU OTHER ENGINEERS THAT HAVE TO DEAL WITH THIS, WE HAVE CREATED AN OPEN SOURCE VERSION OF THIS LIBRARY HERE: https://github.com/getbento/createsend-python

TO USE IT IN YOUR PROJECT SIMPLY ADD git+git://github.com/getbento/createsend-python.git TO YOUR requirements.txt

Values of PageNumber and NumberOfPages seems inconsistent.

When retrieving a list of subscribers via List.active(), an empty list has 0 pages and 1 numbered page while a populated list has 1 pages and also 1 numbered page. In both cases the caller receives a 'page', right?

More explicitly:

  • if the list has subscribers, the response has:
    • NumberOfPages > 0
    • PageNumber > 0
  • if not, the response has:
    • NumberOfPages == 0
    • PageNumber > 0

Empty List:

    'NumberOfPages': 0,
    'RecordsOnThisPage': 0,
    'PageNumber': 1,
    ...

Populated List:

    'NumberOfPages': 1,
    'RecordsOnThisPage': 2,
    'PageNumber': 1,
    ...

No module named 'createsend'

Hi,

I have installed createsend using pip in command line interface.
When I type in from createsend import * in my Python interpreter (spyder), I get an error ModuleNotFoundError: No module named 'createsend'.

Not sure what am I doing wrong here. My goal is to get a list of all subscribers and their activities for a time period.

Add default parameters for various API calls

Just a suggestion: working with this API has a bit of a learning curve. One easy way to improve the situation would be to add default values to parameters that aren't actually required.

For example, if I just want to change a subscriber's name, it looks something like:

    subscriber.update(None,my_new_name,[],False)

And I had to go study the function to figure out that the first parameter is None because I don't want to change the email, but the third is an empty set because it expects a dictionary for custom fields. Yuck.

It sure would be nice if the update() function defaulted to assuming you always want to change everything about the subscriber at once so I could just do:

    subscriber.update(name=my_new_name)

Same goes for many similar functions. Would there be interest in a pull request?

None handling

It looks like previous attribute value is retained if the new attribute value given is None. How does the API handle None?

Failed to install 4.2.2 from pypi

Collecting createsend
  Downloading createsend-4.2.2.tar.gz (160kB)
    100% |████████████████████████████████| 163kB 2.4MB/s
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/my/cvndjd3s70z22zmybwh59rvc0000gn/T/pip-build-EzVTCF/createsend/setup.py", line 3, in <module>
        from createsend.createsend import __version__
      File "createsend/__init__.py", line 8, in <module>
        from createsend.createsend import *
      File "createsend/createsend.py", line 12, in <module>
        from createsend.utils import VerifiedHTTPSConnection, json_to_py, get_faker
    ImportError: No module named utils

Code in master can't doesn't allow installation using pip

I tried to publish a new release twice, but the code in master doesn't allow installation via pip:

› pip3 install createsend
Collecting createsend
  Downloading createsend-4.2.4.tar.gz (161kB)
    100% |████████████████████████████████| 163kB 3.2MB/s 
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/private/var/folders/9d/glt4w3hn1ng3bp621522kw7h0000gn/T/pip-build-4emdtbbh/createsend/setup.py", line 11, in <module>
        from lib.release import __version__, __author__
    ModuleNotFoundError: No module named 'lib.release'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /private/var/folders/9d/glt4w3hn1ng3bp621522kw7h0000gn/T/pip-build-4emdtbbh/createsend/

I have removed both the bad releases.

I do not have time to resolve this.

Whoever ends up doing it will need to bump the version to 4.2.5 when it's published.

Add lib maintainer(s) to PyPi package

Tracking / closing the loop on @jdennes comment in #30 (comment)

I released 4.2.0 on PyPI after someone from Campaign Monitor contacted me asking about this. I'm not an active maintainer of this project, so someone from Campaign Monitor should be added as an owner of the package, since they are the maintainers of it.

Python wrapper not working with API connection

I am trying to connect to my client / campaign using the Python Wrapper and an API connection.

I apply the following code:

from createsend import *

#Connect via API key
cs = CreateSend({'api_key': '123<api-redacted>'})
clients = cs.clients()
auth = {
  'api_key': '123<api-redacted>'
  }

#clients.
 for cl in clients:
     print(cl.Name)
     cl = Client(cl.ClientID,auth)
    
    #client = Client(cl.ClientID)
    for cm in cl.campaigns():
        print("  - %s" % cm.Subject)

But get the following error message:

Unauthorized: The CreateSend API responded with the following error - 50: Must supply a valid HTTP Basic Authorization header

What do I need to pass to the client to provide a valid authorization header? The documentation explains how to do this with OAuth, but does not show the required code when using the API: https://github.com/campaignmonitor/createsend-python/blob/master/README.md

access response headers for transactional API calls

I use createsend-python like this:

transactional = Transactional({"api_key": conf.CAMPAIGN_MONITOR_API_KEY, })
response = transactional.classic_email_send(object.title, '[email protected]', recipient.email,
                                         conf.CAMPAIGN_MONITOR_API_CLIENT_ID, html=body)
print response[0]

how can I access the X-Rate-Limit headers provided by the response? I can only see the json, transformed into a python object (and list?). As seen here: https://www.campaignmonitor.com/api/getting-started/#rate_limiting

client.campaigns() TypeError: 'type' object is not iterable

hello

when i run

cs = CreateSend(auth)

clients = cs.clients()
campaign_ids = cs.clients()

for cl in clients:
print("Client: %s" % cl.Name)
client = Client(auth, cl.ClientID)
print("- Campaigns:")
for cm in client.campaigns():
print(" - %s" % cm.Subject)
print(" - %s" % cm.CampaignID)

i have this issue

File "C:\Users\eguevara\AppData\Local\Temp/ipykernel_17348/3998498663.py", line 1, in
for i in client.campaigns():

TypeError: 'type' object is not iterable

API auth not working, contradicting documentation.

In the readme, this example is shown:

from createsend import *

cs = CreateSend({'api_key': 'your api key'})
clients = cs.clients()

I installed with pip install createsend and have version 6.0.0. When I run this code (substituting my api key for the placeholder text, I get this:

  File "<stdin>", line 1, in <module>
  File "/Users/dharris/src/gdpr/auth.py", line 7, in <module>
    clients = cs.clients()
  File "/Users/dharris/.virtualenvs/gdpr-campaigner/lib/python3.6/site-packages/createsend/createsend.py", line 272, in clients
    response = self._get('/clients.json')
  File "/Users/dharris/.virtualenvs/gdpr-campaigner/lib/python3.6/site-packages/createsend/createsend.py", line 240, in _get
    return self.make_request(path=path, method="GET", params=params, username=username, password=password)
  File "/Users/dharris/.virtualenvs/gdpr-campaigner/lib/python3.6/site-packages/createsend/createsend.py", line 215, in make_request
    return self.handle_response(response.status, data)
  File "/Users/dharris/.virtualenvs/gdpr-campaigner/lib/python3.6/site-packages/createsend/createsend.py", line 230, in handle_response
    raise Unauthorized(json_data)
createsend.createsend.Unauthorized: The CreateSend API responded with the following error - 50: Must supply a valid HTTP Basic Authorization header

Note that this fails on cs.clients(), suggesting that the authentication on the line before worked.

What's going on?

Can't submit a new subscriber

So I'm pretty sure I'm implementing this correctly (though I've had to just look at the code since the docs don't mention how to subscribe a user).

I'm subscribing a new user to a list, and I'm getting this error response back from the server:

The CreateSend API responded with the following error - 400: Failed to deserialize your request. Please check the documentation and try again. Fields in error: subscriber

Here is my code:

      from createsend import *
      CreateSend.api_key = MYKEY
      sub = Subscriber()
      ret = sub.add(MYLIST, user.email, user.first_name + ' ' + user.last_name, False, False)

I'm using Python 2.7.3 on Linux.

Test requirements installed

This package installs nose, coverage, and coveralls. These packages should be omitted except when testing.

Clean up `simplejson` imports

This is everywhere in the code:

try:
    import json
except ImportError:
    import simplejson as json

This is no longer necessary since Python versions pre-json-module (pre 2.6) are not supported and probably already broken since simplejson is not being installed.

Issue installing as a dependency

12:34:47 Downloading createsend-4.2.0.tar.gz (159kB)
12:34:47 Complete output from command python setup.py egg_info:
12:34:47 Traceback (most recent call last):
12:34:47 File "", line 20, in
12:34:47 File "/tmp/pip-build-RzioYi/createsend/setup.py", line 5, in
12:34:47 from createsend.createsend import version
12:34:47 File "createsend/init.py", line 1, in
12:34:47 from .createsend import *
12:34:47 File "createsend/createsend.py", line 6, in
12:34:47 from six import BytesIO
12:34:47 ImportError: No module named six

^^ got this from one of my Jenkins jobs installing the setup.py file for a service that uses "createsend" as a dependency

How do I get data using createsend?

Hello, thanks for reading!

I am in a little over my head as python is not my home language, however the company I work for wants to take data from Campaign Monitor and put it into our BI platform (which does not have a connector of course).

Here is what I have so far, mostly from the basic usage:

from createsend import *

auth = {'api_key': 'company's api key}
cs = CreateSend(auth)
clients = cs.clients()
campaign_ids = cs.clients()

for cl in clients:
  print("Client: %s" % cl.Name)
  client = Client(auth, cl.ClientID)
  print("- Campaigns:")
  for cm in client.campaigns():
    print("  - %s" % cm.Subject)
    print(" - %s" % cm.CampaignID)
    

camp1 = Campaign(auth, 'campaign of interest)
print(camp1.summary())

Now when I print the last line, it returns

<class 'createsend.utils.CreateSendModel'>

I have poured through the documentation and am just really unclear on how to get the data out of here. and into a table that I can convert to a csv and upload etc. (The goal is to eventually automate, I've got some experience with R and google cloud build I'm hoping I can transfer over to python).

Any help you can give would be much appreciated, I've been banging my head on a wall with this for awhile now.

Thanks!

Upgrading to 6.0.0 breaks existing Subscriber implementations

This may be intentional - in which case I'd advise adding some documentation in README.md about this - but it appears a 'consent_to_track' parameter (for GDPR) has been added as a required field (with no default) to the .update() and .add() API's on Subscriber:

def add(self, list_id, email_address, name, custom_fields, resubscribe, consent_to_track, restart_subscription_based_autoresponders=False):

This has resulted in our app breaking when doing a routine package upgrade. The fix is easy, but this is likely going to bite others.

I'm happy to put in a PR - either for adding some 'beware when upgrading' info to README.md, or having a default for this parameter (with an info.warning() advising the API call should be upgraded).

Stop hiding HTTP status codes

If the API returns a status code beginning 4, the library raises a ClientError.
If it returns a status code beginning 5, the library raises a ServerError.

Why is it not including the HTTP status code in the error?? It's impossible to see what the actual error was if you just return an empty exception with literally no information in it.

Question: timing expectations

This is more of a question (apologies if this is the wrong place for this) about what kind of timing expectations can be expected from the API in regards to data syncing. The position I am in is:

  • Service A sends a request to Service B
  • In response to the request, Service B sends an email via Transactional
  • After getting the response from Service B, Service A attempts to query the message_timeline to validate that the email went through (yes Service B likely should handle this)

I have the lookup wrapped in a "try, wait" loop to give the API some time to sync. I also lookup the last send email id (before making the request) to have an anchor to search from. Here is the code (more or less):

tx = Transactional({"api_key": "#######################"})

# get the last sent id before making the request
timeline = tx.message_timeline(
    params={
        "group": group
    }
)
sent_after_id = timeline[0].MessageID

# send the request (blocking)
actual_email = "[email protected]"
group = "foobar"
result = sent_request_to_service_b(actual_email, group)

found = False
attempts = 30
wait = 10
for i in range(attempts):
    timeline = tx.message_timeline(
        params={
            "group": group,
            "sentAfterId": sent_after_id
        }
    )

    sent_emails = [e.Recipient for e in timeline]
    found = any([actual_email == e for e in sent_emails])

    if found:
        break
    else:
        time.sleep(wait)

There are cases where this works (the email is found) on the very first try (i.e. the API is synced within a few seconds at most) and there are times where it takes 100+ seconds of waiting in order for the email to be found. I can go into the web browser and see the email in the Latest emails sent table long before the API resolves it. I am just wondering if there is some kind of rate limiting that is going on or some other magic happening? I ask because the behavior is fairly back and forth, it works on the first try, then it takes 100 seconds, then it works on the first try, etc (all with different emails that were sent the same way in the same time frame).

Everything is running createsend==4.2.7

I'd appreciate any advice you might have. Thanks for working on this api!

Latest version does not install via Pypi

  File "/home/deploy/virtualenvs/bentobox/build/createsend/setup.py", line 5, in <module>

    from createsend import __version__

  File "createsend/__init__.py", line 11, in <module>

    from transactional import Transactional

ImportError: No module named transactional

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.