Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
A small class to help connect to the OpenX Enterprise API. As of version 0.5.0 it uses
[requests_oauthlib](https://github.com/requests/requests-oauthlib) instead of oauth2.

It currently supports Python 2.6 - 2.7, with 3.x support coming in the future.
It currently supports Python 2.6 - 3.5.

As of version 0.4.0, ox3apiclient supports API v2. If your instance is v2,
set the api_path option to "/ox/4.0".
Expand Down
38 changes: 21 additions & 17 deletions ox3apiclient/__init__.py
Original file line number Diff line number Diff line change
@@ -1,17 +1,18 @@
# -*- coding: utf-8 -*-

import ConfigParser
import cookielib
import six
from six.moves import configparser as ConfigParser
from six.moves import http_cookiejar as cookielib
import logging
import mimetypes
from pprint import pformat
import random
import json
from urlparse import parse_qs, urlparse
from six.moves.urllib.parse import parse_qs, urlparse

import requests
from requests_oauthlib import OAuth1


__version__ = '0.5.0'

REQUEST_TOKEN_URL = 'https://sso.openx.com/api/index/initiate'
Expand Down Expand Up @@ -123,9 +124,9 @@ def log_request(self, response):
self.logger.debug("%s: %s" % (k, v))
self.logger.debug('====={0:=<45}'.format('OX3 api call response body'))
try:
self.logger.debug(pformat(json.loads(response.content)))
self.logger.debug(pformat(json.loads(response.text)))
except ValueError:
self.logger.debug("%s" % response.content)
self.logger.debug("%s" % response.text)
self.logger.debug('====={0:=<45}'.format('OX3 api call finished'))

def request(self, url, method='GET', headers=None, data=None, sign=False,
Expand Down Expand Up @@ -164,8 +165,8 @@ def fetch_request_token(self):
response = self._session.post(url=self.request_token_url, auth=oauth, timeout=self.timeout)
self.log_request(response)
if response.status_code != 200:
raise OAuthException("OAuth token request failed (%s) %s" % (response.status_code, response.content))
credentials = parse_qs(response.content)
raise OAuthException("OAuth token request failed (%s) %s" % (response.status_code, response.text))
credentials = parse_qs(response.text)
self._token = {'key': credentials['oauth_token'][0],
'secret': credentials['oauth_token_secret'][0]}
return self._token
Expand Down Expand Up @@ -193,12 +194,12 @@ def authorize_token(self, email=None, password=None):
response = self._session.post(url=self.authorization_url, data=data, timeout=self.timeout)
self.log_request(response)
if response.status_code != 200:
raise OAuthException("OAuth login failed (%s) %s" % (response.status_code, response.content))
raise OAuthException("OAuth login failed (%s) %s" % (response.status_code, response.text))

# Clear user credentials.
self._email = self._password = None
# set token verifier
self._token['verifier'] = parse_qs(response.content)['oauth_verifier'][0]
self._token['verifier'] = parse_qs(response.text)['oauth_verifier'][0]

def fetch_access_token(self):
"""Helper method to fetch and set access token.
Expand All @@ -215,8 +216,9 @@ def fetch_access_token(self):
response = self._session.post(url=self.access_token_url, auth=oauth, timeout=self.timeout)
self.log_request(response)
if response.status_code != 200:
raise OAuthException("OAuth token verification failed (%s) %s" % (response.status_code, response.content))
self._token = parse_qs(response.content)['oauth_token'][0]
raise OAuthException("OAuth token verification failed (%s) %s" % (response.status_code, response.text))
self._token = parse_qs(response.text)['oauth_token'][0]

return self._token

def validate_session(self):
Expand Down Expand Up @@ -247,7 +249,7 @@ def validate_session(self):
if self.api_path == API_PATH_V1:
response = self._session.put(url=self._resolve_url('/a/session/validate'), timeout=self.timeout)
self.log_request(response)
return response.content
return response.text

def logon(self, email=None, password=None):
"""Returns self after authentication.
Expand Down Expand Up @@ -279,7 +281,7 @@ def logoff(self):

response = self._session.delete(url=self.access_token_url, auth=oauth, timeout=self.timeout)
if response.status_code != 204:
raise OAuthException("OAuth token deletion failed (%s) %s" % (response.status_code, response.content))
raise OAuthException("OAuth token deletion failed (%s) %s" % (response.status_code, response.text))
else:
raise UnknownAPIFormatError(
'Unrecognized API path: %s' % self.api_path)
Expand Down Expand Up @@ -307,14 +309,14 @@ def _resolve_url(self, url):
return url

def _response_value(self, response):
""" Utility method. Returns decoded json. If the response content cannot be decoded, then
""" Utility method. Returns decoded json. If the response.text cannot be decoded, then
the content is returned.

"""
try:
return response.json()
except ValueError:
return response.content
return response.text

def get(self, url):
"""Issue a GET request to the given URL or API shorthand
Expand Down Expand Up @@ -395,7 +397,9 @@ def upload_creative(self, account_id, file_path):
parts.append('Content-Type: %s' % mimetypes.guess_type(file_path)[0] or 'application/octet-stream')
parts.append('')
# TODO: catch errors with opening file.
parts.append(open(file_path, 'r').read())
# do this within a context manager to avoid unclosed file
with open(file_path, 'r') as f:
parts.append(f.read())

parts.append('--' + boundary + '--')
parts.append('')
Expand Down
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
description='Client to connect to OpenX Enterprise API.',
long_description='Client to connect to OpenX Enterprise API.',
packages=['ox3apiclient'],
install_requires=['requests_oauthlib'],
install_requires=['requests_oauthlib', 'six'],
classifiers=[
'Environment :: Console',
'Environment :: Web Environment',
Expand All @@ -28,6 +28,7 @@
'Programming Language :: Python',
'Programming Language :: Python :: 2.6',
'Programming Language :: Python :: 2.7',
'Programming Language :: Python :: 3'
'Programming Language :: Python :: Implementation :: CPython',
'Topic :: Internet :: WWW/HTTP :: Dynamic Content',
'Topic :: Software Development :: Libraries',
Expand Down
88 changes: 40 additions & 48 deletions tests/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,16 +3,14 @@
import unittest
from mock import Mock, patch
import os
from contextlib import nested


class TestClient(unittest.TestCase):
ex_resp = Mock()
ex_resp.request.headers = {'rheader1': 'rvalue1',
'rheader2': 'rvalue2'}
ex_resp.headers = {'header1': 'value1',
'header2': 'value2'}
ex_resp.content = 'oauth_token=key&oauth_token_secret=secret&oauth_callback_confirmed=true'
ex_resp.text = 'oauth_token=key&oauth_token_secret=secret&oauth_callback_confirmed=true'
ex_resp.json.return_value = {'key1': 'value1',
'key2': 'value2',
'key3': 'value3'}
Expand All @@ -33,28 +31,25 @@ def setUp(self):
self.api_path_v2 = '/ox/4.0'
self.url = 'https://www.example.com'

with nested(
patch('ox3apiclient.requests.Session'),
patch('ox3apiclient.Client.log_request')
) as (self.mock_requests_session, self.mock_client_log_request):

self.mock_requests_session.return_value.get.return_value = self.ex_resp
self.mock_requests_session.return_value.post.return_value = self.ex_resp
self.mock_requests_session.return_value.put.return_value = self.ex_resp
self.mock_requests_session.return_value.options.return_value = self.ex_resp
self.mock_requests_session.return_value.delete.return_value = self.ex_resp

self.mock_client_log_request.return_value = None
self.client = ox3apiclient.Client(
email=self.email,
password=self.password,
domain=self.domain,
realm=self.realm,
consumer_key=self.consumer_key,
consumer_secret=self.consumer_secret,
request_token_url=self.request_token_url,
access_token_url=self.access_token_url,
authorization_url=self.authorization_url)
with patch('ox3apiclient.requests.Session') as self.mock_requests_session:
with patch('ox3apiclient.Client.log_request') as self.mock_client_log_request:
self.mock_requests_session.return_value.get.return_value = self.ex_resp
self.mock_requests_session.return_value.post.return_value = self.ex_resp
self.mock_requests_session.return_value.put.return_value = self.ex_resp
self.mock_requests_session.return_value.options.return_value = self.ex_resp
self.mock_requests_session.return_value.delete.return_value = self.ex_resp

self.mock_client_log_request.return_value = None
self.client = ox3apiclient.Client(
email=self.email,
password=self.password,
domain=self.domain,
realm=self.realm,
consumer_key=self.consumer_key,
consumer_secret=self.consumer_secret,
request_token_url=self.request_token_url,
access_token_url=self.access_token_url,
authorization_url=self.authorization_url)

def test_init(self):
pass
Expand Down Expand Up @@ -83,7 +78,7 @@ def test_authorize_token(self,
mock_fetch_request_token):
# mock the post response, and do some setup
r = Mock()
r.content = 'oauth_verifier=verifier'
r.text = 'oauth_verifier=verifier'
self.mock_requests_session.return_value.post.return_value = r
mock_client_log_request.return_value = None
mock_fetch_request_token.return_value = {'key': 'key',
Expand All @@ -106,7 +101,7 @@ def test_fetch_access_token(self, mock_client_log_request, mock_oauth1):
# mock the OAuth1 and session post response
mock_oauth1.return_value = 'oauth'
r = Mock()
r.content = 'oauth_token=key'
r.text = 'oauth_token=key'
self.mock_requests_session.return_value.post.return_value = r
self.client._token = {'key': 'key',
'secret': 'secret',
Expand All @@ -128,19 +123,16 @@ def test_validate_session(self):
'oauth_callback_confirmed=true')

def test_logon(self):
with nested(
patch('ox3apiclient.Client.fetch_request_token'),
patch('ox3apiclient.Client.authorize_token'),
patch('ox3apiclient.Client.fetch_access_token'),
patch('ox3apiclient.Client.validate_session'),
) as (mock_fetch_request_token, mock_authorize_token,
mock_fetch_access_token, mock_validate_session):
mock_fetch_request_token.return_value = None
mock_authorize_token.return_value = None
mock_fetch_access_token.return_value = None
mock_validate_session.return_value = None
ret_val = self.client.logon()
self.assertTrue(isinstance(ret_val, ox3apiclient.Client))
with patch('ox3apiclient.Client.fetch_request_token') as mock_fetch_request_token:
with patch('ox3apiclient.Client.authorize_token') as mock_authorize_token:
with patch('ox3apiclient.Client.fetch_access_token') as mock_fetch_access_token:
with patch('ox3apiclient.Client.validate_session') as mock_validate_session:
mock_fetch_request_token.return_value = None
mock_authorize_token.return_value = None
mock_fetch_access_token.return_value = None
mock_validate_session.return_value = None
ret_val = self.client.logon()
self.assertTrue(isinstance(ret_val, ox3apiclient.Client))

def test_logoff(self):
ret_val = self.client.logoff()
Expand All @@ -156,31 +148,31 @@ def test_get(self):
'key3': 'value3'})

def test_options(self):
ret_val = self.client.options('https://example.com')
ret_val = self.client.options(self.url)
self.assertEqual(ret_val, {'key1': 'value1',
'key2': 'value2',
'key3': 'value3'})

def test_put(self):
ret_val = self.client.put('https://example.com', data={'k': 'v'})
ret_val = self.client.put(self.url, data={'k': 'v'})
self.assertEqual(ret_val, {'key1': 'value1',
'key2': 'value2',
'key3': 'value3'})

def test_post(self):
ret_val = self.client.post('https://example.com', data={'k': 'v'})
ret_val = self.client.post(self.url, data={'k': 'v'})
self.assertEqual(ret_val, {'key1': 'value1',
'key2': 'value2',
'key3': 'value3'})

@patch('ox3apiclient.requests.delete')
@patch('ox3apiclient.requests.Session.delete')
@patch('ox3apiclient.Client.log_request')
def test_delete(self, mock_client_log_request, mock_requests_delete):
def test_delete(self, mock_client_log_request, mock_requests_session_delete):
mock_client_log_request.return_value = None
r = Mock()
r.status_code = 204
mock_requests_delete.return_value = r
ret_val = self.client.delete('https://example.com')
mock_requests_session_delete.return_value = r
ret_val = self.client.delete(self.url)
self.assertEqual(ret_val, [])

r.status_code = 200
Expand All @@ -190,7 +182,7 @@ def test_delete(self, mock_client_log_request, mock_requests_delete):
# self.assertEqual(ret_val, {'key': 'value'})

r.json.return_value = {'key': 'value'}
ret_val = self.client.delete('https://example.com')
ret_val = self.client.delete(self.url)
self.assertEqual(ret_val, {'key': 'value'})

def test_upload_creative(self):
Expand Down