Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
de2976e
Implements the setup.py file.
May 30, 2013
d825dfb
Ignores env folder on git (virtual env folder)
May 30, 2013
6d43853
Adds requirements file for fast dependency installation
May 30, 2013
138798c
Adds provider class for akamai services
May 30, 2013
6570dee
Adds utils folder for general tools
May 30, 2013
b37c13f
Adds service client for akamai real time reports.
May 30, 2013
6438819
Adds service client for akamai streaming reports.
May 30, 2013
590f93f
Adds main script for console usage.
May 30, 2013
ae26616
Merge pull request #1 from micho8402/develop
May 30, 2013
4462df4
Creates base custom exception class.
May 30, 2013
c8d0da9
Implements the BaseService class.
May 30, 2013
fdaf050
Implements the Connection class that returns services dynamically cre…
May 30, 2013
1d3872b
Removes no longer needed files.
May 30, 2013
ccbf76c
Merge pull request #2 from mobilerider/using-dynamic-classes
May 30, 2013
44c84fc
Removes unnecessary import.
May 31, 2013
5af3f4e
Modifies client console script to support last service lib changes.
May 31, 2013
18d6487
Adds time zone and format params to the console script for service da…
May 31, 2013
247b36d
Parses returned data into a list of dicts.
May 31, 2013
7d7bc75
Validates dict keys on parsing returned data
May 31, 2013
fc060fe
Adds method to retrieve service codes from the creation of the service.
May 31, 2013
7c8b32b
Allows to invoke a service method with all cp codes allowed.
Jun 1, 2013
7ebc3c9
Updates console script
Jun 1, 2013
503c911
Manages invalid codes in strict mode
Jun 1, 2013
4db31b1
Removes initial # from first key
Jun 1, 2013
3c4c14a
Ignores another IDE project folder.
Jun 5, 2013
91bf6fd
Updates console client script (just for internal tests atm)
Jun 7, 2013
f71b0bc
Better/Explicit exception handling
mandx Jun 8, 2013
293d992
WS_Attachment hack to make it work with Suds
mandx Jun 8, 2013
2fabc09
Fixes invalid NULL byte.
Jun 12, 2013
0d76ddd
Merge pull request #3 from mobilerider/ws-attachment-hack
Jun 18, 2013
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
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -33,3 +33,7 @@ nosetests.xml
.mr.developer.cfg
.project
.pydevproject
.idea

# Python enviroment
env
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
python-akamai-webservice
========================

Python bindings for Akamai's web service
Python client for Akamai's web services
3 changes: 3 additions & 0 deletions akamaiservice/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
__version__ = '0.2.0'

__author__ = 'Julio Menendez & Michel Perez'
119 changes: 119 additions & 0 deletions akamaiservice/baseservice.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
from cStringIO import StringIO
from csv import reader as csv_reader

from suds import WebFault

from .exception import AkamaiServiceException


class BaseService(object):
client = None

def __init__(self, client, load_cp_codes=False):
self.client = client
self.codes = None
self.invalid_codes = None

if load_cp_codes:
self.get_cp_codes()

def __getattr__(self, name):
try:
return getattr(self.client.service, name)
except KeyError:
message = ('%s does not exist in service %s' % (name, self.__name__))
raise AkamaiServiceException(message)

def get_cp_codes(self, force_refresh=False):
"""
Retrieves a list of CPCodeInfo instances from akamai current service.
CPCodeInfo format: {cpcode = 123 description = "desc" service = "Service::Method"}
The numeric cp codes are stored in this service instance
@param force_refresh: bool Forces retrieving of codes from remote service
@return: list
"""
if self.codes and not force_refresh:
if self.invalid_codes:
return [x for x in self.codes if x not in self.invalid_codes]
else:
return self.codes

data = self.invoke_method('getCPCodes')
self.codes = [x.cpcode for x in data]
self.invalid_codes = []

return data

def invoke_method(self, name, *args, **kwargs):
"""
Executes akamai service method given by name and returns a result.
It parses result data if is of string type
Use all_cp_codes=True to test for all codes allowed, if you use
this option you may pass the cp codes param as empty list or None
Use strict to force exception raising
@param name: string
@param args: list
@param kwargs: dict
@return: mixed
"""
strict = kwargs.get('strict')
all_cp_codes = kwargs.get('all_cp_codes')

if all_cp_codes:
args = list(args)
args[0] = self.get_cp_codes()

try:
data = getattr(self, name)(*args)
except WebFault as ex:
if not strict:
if 'The following cpcodes are invalid for you' in ex.message:
self.invalid_codes = [int(x.strip()) for x in ex.message[:-2].split(':')[-1].split(',')]
else:
raise ex

# Protects against infinite loop
kwargs['strict'] = False
# Call itself again
return self.invoke_method(name, *args, **kwargs)
else:
raise ex

return self.parse(data) if isinstance(data, basestring) else data

def parse(self, data):
"""
Parses csv data into a list of dictionaries where every csv row
represents an item on the list.
@param data:
@return: list
"""
mem_file = StringIO(data)
mem_content = mem_file.getvalue()
mem_file.close()
mem_file = StringIO(mem_content.replace('\0', ''))

reader = csv_reader(mem_file)

result = []
keys = None

for row in reader:
if not keys and row[0].startswith('#') and len(row) > 1:
keys = row
keys[0] = keys[0].replace('#', '').strip()
continue
elif row[0].startswith('#') and len(row) <= 1:
continue

if keys:
item = {}
for count, k in enumerate(keys):
item[k] = row[count]
else:
item = row

result.append(item)

return result

69 changes: 69 additions & 0 deletions akamaiservice/connection.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
from email import message_from_string
import re
from xml.sax import SAXParseException
from lxml.etree import XMLParser, fromstring as xml_from_string, tostring as xml_to_string

from suds.client import Client
from suds.sax.parser import Parser
from suds.bindings.binding import Binding

from .constants import WSDL_BASE_URL
from .baseservice import BaseService


BOUNDARY_REGEX = re.compile('^-+=(?P<boundary_name>[^-]+)-*$')


def replyfilter(self, reply_string):
try:
Parser().parse(string=reply_string)
return reply_string
except SAXParseException:
parts = []
part = []
for l in reply_string.splitlines():
if not BOUNDARY_REGEX.match(l):
part.append(l)
else:
parts.append('\n'.join(part))
part = []
parts = [
message_from_string(_p).get_payload()
for _p in parts
if ''.join(_p.splitlines()).strip()
]
xml = xml_from_string(parts[0], parser=XMLParser(
attribute_defaults=False,
dtd_validation=False,
load_dtd=False,
no_network=False,
ns_clean=False,
remove_blank_text=False,
remove_comments=False,
remove_pis=False,
strip_cdata=False,
compact=False,
resolve_entities=False,

))
xml.find('.//getLiveStreamTrafficForCPCodeV2Return').text = parts[1]
return xml_to_string(xml)

Binding.replyfilter = replyfilter


class Connection(object):
username = None
password = None

def __init__(self, username, password):
self.username = username
self.password = password

def __get_client(self, service_name):
wsdl_url = '%s%s?wsdl' % (WSDL_BASE_URL, service_name)
return Client(wsdl_url, username=self.username, password=self.password)

def get_service(self, service_name, load_cp_codes=False):
client = self.__get_client(service_name)
return type(service_name, (BaseService,), dict())(client=client, load_cp_codes=load_cp_codes)
1 change: 1 addition & 0 deletions akamaiservice/constants.py
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
WSDL_BASE_URL = 'https://control.akamai.com/nmrws/services/'
2 changes: 2 additions & 0 deletions akamaiservice/exception.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
class AkamaiServiceException(Exception):
pass
67 changes: 67 additions & 0 deletions client.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import sys
import getopt
from datetime import datetime
from akamaiservice.connection import Connection


def print_help():
print("Usage : <service> <method> [-c <codes> | --codes=<codes> ]")
print("Gets report data from Akamai web services.")
print("Report types: live | vod | storage | total_storage, ")
print(" -c, --codes list of CP codes separated by comma")
print(" -u, --username Akamai Luna Control Centerusername")
print(" -p, --password Akamai Luna Control Center password")
print(" -s, --start start date for report filtering, by default -1 day, format: mm/dd/yyyy-hh:mm")
print(" -e, --end end date for report filtering, by default now, format: mm/dd/yyyy-hh:mm")
print(" -f, --format media format for filtering, supported: all, flash, real, quickTime, windowsMedia")
print(" -h, --help display this help and exit")
print(" -v verbose mode, output debug information")
print(" --version output version information and exit")


if __name__ == "__main__":
#try:
if len(sys.argv) <= 3:
raise Exception('Missing required parameters')

opts, args = getopt.getopt(sys.argv[3:], "hc:u:p:s:e:f:m:",
["help,codes=,username=,password=,start=,end=,format=,media="])

service_args = []
service_kwargs = {'service_name': sys.argv[1], 'service_method': sys.argv[2]}

for opt, arg in opts:
if opt in ('-h', '--help'):
print_help()
sys.exit()
elif opt in ('-c', '--codes'):
service_args.append(arg.split(','))
elif opt in ('-u', '--username'):
service_kwargs['username'] = arg
elif opt in ('-p', '--password'):
service_kwargs['password'] = arg
elif opt in ('-s', '--start'):
service_args.append(datetime.strptime(arg, '%m/%d/%Y-%H:%M:%S'))
elif opt in ('-e', '--end'):
service_args.append(datetime.strptime(arg, '%m/%d/%Y-%H:%M:%S'))
service_args.append('GMT') #Default time zone
service_args.append(None) #Null for column array, not supported yet
elif opt in ('-f', '--format'):
f = service_kwargs.get('filter', {})
f['format'] = arg
service_kwargs['filter'] = f
service_args.append(f)
elif opt in ('-m', '--media'):
service_args.append(arg.split(','))
else:
raise Exception('Argument unknown')

connection = Connection(service_kwargs.get('username'), service_kwargs.get('password'))
service = connection.get_service(service_kwargs['service_name'], True)

print service.invoke_method(service_kwargs['service_method'], *service_args, all_cp_codes=True)

#except Exception as ex:
# print("Error: " + ex.msg if hasattr(ex, 'msg') else ex.message)
# print_help()

2 changes: 2 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
distribute==0.6.34
suds-jurko==0.4.1.jurko.4
44 changes: 44 additions & 0 deletions setup.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
#!/usr/bin/env python
# -*- coding: utf-8 -*-

from setuptools import setup
import re
import os
import sys


init_py = open('akamaiservice/__init__.py').read()
author = re.search("__author__ = ['\"]([^'\"]+)['\"]", init_py).group(1)
version = re.search("__version__ = ['\"]([^'\"]+)['\"]", init_py).group(1)

if sys.argv[-1] == 'publish':
os.system("python setup.py sdist upload")
print("You probably want to also tag the version now:")
print(" git tag -a %s -m 'version %s'" % (version, version))
print(" git push --tags")
sys.exit()

setup(
name='python-akamai-webservice',
version=version,
description="Python client for Akamai's web services.",
long_description=open('README.md').read(),
classifiers=[
"Development Status :: 2 - Pre-Alpha",
"License :: OSI Approved :: MIT License",
"Operating System :: OS Independent",
"Programming Language :: Python :: 2.7",
"Topic :: Software Development :: Libraries :: Python Modules",
],
keywords='akamai,webservice',
author=author,
author_email='julio@mobilerider.com',
url='https://github.com/mobilerider/python-akamai-webservice',
license='MIT',
packages=[
'akamaiservice',
],
include_package_data=True,
install_requires=[],
zip_safe=False,
)