diff --git a/.gitignore b/.gitignore
index 5fb9841..5e86898 100755
--- a/.gitignore
+++ b/.gitignore
@@ -2,6 +2,7 @@
*.pyc
*.egg-info
dist
+build
*-jgrover*
*.log
.coverage
diff --git a/build.sh b/build.sh
deleted file mode 100755
index c4e9b98..0000000
--- a/build.sh
+++ /dev/null
@@ -1,6 +0,0 @@
-#!/bin/bash
-
-python setup.py sdist --formats=gztar,zip
-python setup.py bdist --format=gztar,zip
-python setup.py bdist_egg
-
diff --git a/build/lib/omniture/__init__.py b/build/lib/omniture/__init__.py
deleted file mode 100644
index fa5e20f..0000000
--- a/build/lib/omniture/__init__.py
+++ /dev/null
@@ -1,85 +0,0 @@
-# encoding: utf-8
-from __future__ import absolute_import
-
-import os
-import json
-import logging.config
-import io
-
-from .account import Account, Suite
-from .elements import Value
-from .query import Query, ReportNotSubmittedError
-from .reports import InvalidReportError, Report, DataWarehouseReport
-from .version import __version__
-from .utils import AddressableList, affix
-
-
-def authenticate(username, secret=None, endpoint=Account.DEFAULT_ENDPOINT, prefix='', suffix=''):
- """ Authenticate to the Adobe API using WSSE """
- #setup logging
- setup_logging()
- # if no secret is specified, we will assume that instead
- # we have received a dictionary with credentials (such as
- # from os.environ)
- if not secret:
- source = username
- key_to_username = affix(prefix, 'OMNITURE_USERNAME', suffix)
- key_to_secret = affix(prefix, 'OMNITURE_SECRET', suffix)
- username = source[key_to_username]
- secret = source[key_to_secret]
-
- return Account(username, secret, endpoint)
-
-
-def queue(queries):
- if isinstance(queries, dict):
- queries = queries.values()
-
- for query in queries:
- query.queue()
-
-
-def sync(queries, heartbeat=None, interval=1):
- """
- `omniture.sync` will queue a number of reports and then
- block until the results are all ready.
-
- Queueing reports is idempotent, meaning that you can also
- use `omniture.sync` to fetch the results for queries that
- have already been queued:
-
- query = mysuite.report.range('2013-06-06').over_time('pageviews', 'page')
- omniture.queue(query)
- omniture.sync(query)
-
- The interval will operate under an exponetial decay until it reaches 5 minutes. At which point it will ping every 5 minutes
- """
-
- queue(queries)
-
- if isinstance(queries, list):
- return [query.sync(heartbeat, interval) for query in queries]
- elif isinstance(queries, dict):
- return {key: query.sync(heartbeat, interval) for key, query in queries.items()}
- else:
- message = "Queries should be a list or a dictionary, received: {}".format(
- queries.__class__)
- raise ValueError(message)
-
-
-def setup_logging(default_path='logging.json', default_level=logging.INFO, env_key='LOG_CFG'):
- """Setup logging configuration. """
- path = default_path
- value = os.getenv(env_key, None)
- if value:
- path = value
- if os.path.exists(path):
- with io.open(path, 'rt') as f:
- config = json.load(f)
- f.close()
- logging.config.dictConfig(config)
- requests_log = logging.getLogger("requests")
- requests_log.setLevel(logging.WARNING)
-
- else:
- logging.basicConfig(level=default_level)
diff --git a/build/lib/omniture/account.py b/build/lib/omniture/account.py
deleted file mode 100644
index 23967cd..0000000
--- a/build/lib/omniture/account.py
+++ /dev/null
@@ -1,270 +0,0 @@
-from __future__ import absolute_import
-from __future__ import print_function
-
-import requests
-import binascii
-import json
-from datetime import datetime, date
-import logging
-import uuid
-import hashlib
-import base64
-import os
-from datetime import datetime
-
-from .elements import Value
-from .query import Query
-from . import reports
-from . import utils
-
-
-class Account(object):
- """ A wrapper for the Adobe Analytics API. Allows you to query the reporting API """
- DEFAULT_ENDPOINT = 'https://api.omniture.com/admin/1.4/rest/'
-
- def __init__(self, username, secret, endpoint=DEFAULT_ENDPOINT, cache=False, cache_key=None):
- """Authentication to make requests."""
- self.log = logging.getLogger(__name__)
- self.log.info(datetime.now().strftime("%Y-%m-%d %I%p:%M:%S"))
- self.username = username
- self.secret = secret
- self.endpoint = endpoint
- #Allow someone to set a custom cache key
- self.cache = cache
- if cache_key:
- self.cache_key = cache_key
- else:
- self.cache_key = date.today().isoformat()
- if self.cache:
- data = self.request_cached('Company', 'GetReportSuites')['report_suites']
- else:
- data = self.request('Company', 'GetReportSuites')['report_suites']
- suites = [Suite(suite['site_title'], suite['rsid'], self) for suite in data]
- self.suites = utils.AddressableList(suites)
-
- def request_cached(self, api, method, query={}, cache_key=None):
- if cache_key:
- key = cache_key
- else:
- key = self.cache_key
-
- #Generate a shortened hash of the query string so that method don't collide
- query_hash = base64.urlsafe_b64encode(hashlib.md5(query).digest())
-
- try:
- with open(self.file_path+'/data_'+api+'_'+method+'_'+query_hash+'_'+key+'.txt') as fp:
- for line in fp:
- if line:
- data = ast.literal_eval(line)
-
- except IOError as e:
- data = self.request(api, method, query)
-
- # Capture all other old text files
- #TODO decide if the query should be included in the file list to be cleared out when the cache key changes
- filelist = [f for f in os.listdir(self.file_path) if f.startswith('data_'+api+'_'+method)]
-
- # Delete them
- for f in filelist:
- os.remove(self.file_path+'/'+f)
-
- # Build the new data
- the_file = open(self.file_path+'/data_'+api+'_'+method+'_'+query_hash+'_'+key+'.txt', 'w')
- the_file.write(str(data))
- the_file.close()
-
-
- def request(self, api, method, query={}):
- """
- Make a request to the Adobe APIs.
-
- * api -- the class of APIs you would like to call (e.g. Report,
- ReportSuite, Company, etc.)
- * method -- the method you would like to call inside that class
- of api
- * query -- a python object representing the parameters you would
- like to pass to the API
- """
- self.log.info("Request: %s.%s Parameters: %s", api, method, query)
- response = requests.post(
- self.endpoint,
- params={'method': api + '.' + method},
- data=json.dumps(query),
- headers=self._build_token()
- )
- self.log.debug("Response for %s.%s:%s", api, method, response.text)
- json_response = response.json()
-
- if type(json_response) == dict:
- self.log.debug("Error Code %s", json_response.get('error'))
- if json_response.get('error') == 'report_not_ready':
- raise reports.ReportNotReadyError(json_response)
- elif json_response.get('error') != None:
- raise reports.InvalidReportError(json_response)
- else:
- return json_response
- else:
- return json_response
-
- def jsonReport(self, reportJSON):
- """Generates a Report from the JSON (including selecting the report suite)"""
- if type(reportJSON) == str:
- reportJSON = json.loads(reportJSON)
- suiteID = reportJSON['reportDescription']['reportSuiteID']
- suite = self.suites[suiteID]
- return suite.jsonReport(reportJSON)
-
-
- def _serialize_header(self, properties):
- header = []
- for key, value in properties.items():
- header.append('{key}="{value}"'.format(key=key, value=value))
- return ', '.join(header)
-
- def _build_token(self):
- nonce = str(uuid.uuid4())
- base64nonce = binascii.b2a_base64(binascii.a2b_qp(nonce))
- created_date = datetime.utcnow().isoformat() + 'Z'
- sha = nonce + created_date + self.secret
- sha_object = hashlib.sha1(sha.encode())
- password_64 = binascii.b2a_base64(sha_object.digest())
-
- properties = {
- "Username": self.username,
- "PasswordDigest": password_64.decode().strip(),
- "Nonce": base64nonce.decode().strip(),
- "Created": created_date,
- }
- header = 'UsernameToken ' + self._serialize_header(properties)
-
- return {'X-WSSE': header}
-
- def _repr_html_(self):
- """ Format in HTML for iPython Users """
- html = ""
- html += "{0}: {1}".format("Username", self.username)
- html += "{0}: {1}".format("Secret", "***************")
- html += "{0}: {1}".format("Report Suites", len(self.suites))
- html += "{0}: {1}".format("Endpoint", self.endpoint)
- return html
-
- def __str__(self):
- return "Analytics Account -------------\n Username: \
- {0} \n Report Suites: {1} \n Endpoint: {2}" \
- .format(self.username, len(self.suites), self.endpoint)
-
-
-class Suite(Value):
- """Lets you query a specific report suite. """
- def request(self, api, method, query={}):
- raw_query = {}
- raw_query.update(query)
- if method == 'GetMetrics' or method == 'GetElements':
- raw_query['reportSuiteID'] = self.id
-
- return self.account.request(api, method, raw_query)
-
- def __init__(self, title, id, account, cache=False):
- self.log = logging.getLogger(__name__)
- super(Suite, self).__init__(title, id, account)
- self.account = account
-
- @property
- @utils.memoize
- def metrics(self):
- """ Return the list of valid metricsfor the current report suite"""
- if self.account.cache:
- data = self.request_cache('Report', 'GetMetrics')
- else:
- data = self.request('Report', 'GetMetrics')
- return Value.list('metrics', data, self, 'name', 'id')
-
- @property
- @utils.memoize
- def elements(self):
- """ Return the list of valid elementsfor the current report suite """
- if self.account.cache:
- data = self.request_cached('Report', 'GetElements')
- else:
- data = self.request('Report', 'GetElements')
- return Value.list('elements', data, self, 'name', 'id')
-
- @property
- @utils.memoize
- def segments(self):
- """ Return the list of valid segments for the current report suite """
- if self.account.cache:
- data = self.request_cached('Segments', 'Get',{"accessLevel":"shared"})
- else:
- data = self.request('Segments', 'Get',{"accessLevel":"shared"})
- return Value.list('segments', data, self, 'name', 'id',)
-
- @property
- def report(self):
- """ Return a report to be run on this report suite """
- return Query(self)
-
- def jsonReport(self,reportJSON):
- """Creates a report from JSON. Accepts either JSON or a string. Useful for deserializing requests"""
- q = Query(self)
- #TODO: Add a method to the Account Object to populate the report suite this call will ignore it on purpose
- if type(reportJSON) == str:
- reportJSON = json.loads(reportJSON)
-
- reportJSON = reportJSON['reportDescription']
-
- if 'dateFrom' in reportJSON and 'dateTo' in reportJSON:
- q = q.range(reportJSON['dateFrom'],reportJSON['dateTo'])
- elif 'dateFrom' in reportJSON:
- q = q.range(reportJSON['dateFrom'])
- elif 'date' in reportJSON:
- q = q.range(reportJSON['date'])
- else:
- q = q
-
- if 'dateGranularity' in reportJSON:
- q = q.granularity(reportJSON['dateGranularity'])
-
- if 'source' in reportJSON:
- q = q.set('source',reportJSON['source'])
-
- if 'metrics' in reportJSON:
- for m in reportJSON['metrics']:
- q = q.metric(m['id'])
-
- if 'elements' in reportJSON:
- for e in reportJSON['elements']:
- id = e['id']
- del e['id']
- q= q.element(id, **e)
-
- if 'locale' in reportJSON:
- q = q.set('locale',reportJSON['locale'])
-
- if 'sortMethod' in reportJSON:
- q = q.set('sortMethod',reportJSON['sortMethod'])
-
- if 'sortBy' in reportJSON:
- q = q.sortBy(reportJSON['sortBy'])
-
- #WARNING This doesn't carry over segment IDs meaning you can't manipulate the segments in the new object
- #TODO Loop through and add segment ID with filter method (need to figure out how to handle combined)
- if 'segments' in reportJSON:
- q = q.set('segments', reportJSON['segments'])
-
- if 'anomalyDetection' in reportJSON:
- q = q.set('anomalyDetection',reportJSON['anomalyDetection'])
-
- if 'currentData' in reportJSON:
- q = q.set('currentData',reportJSON['currentData'])
-
- if 'elementDataEncoding' in reportJSON:
- q = q.set('elementDataEncoding',reportJSON['elementDataEncoding'])
- return q
-
- def _repr_html_(self):
- """ Format in HTML for iPython Users """
- return "
{0} | {1} | ".format(self.id, self.title)
-
- def __str__(self):
- return "ID {0:25} | Name: {1} \n".format(self.id, self.title)
diff --git a/build/lib/omniture/elements.py b/build/lib/omniture/elements.py
deleted file mode 100644
index 78ab08a..0000000
--- a/build/lib/omniture/elements.py
+++ /dev/null
@@ -1,48 +0,0 @@
-# encoding: utf-8
-from __future__ import absolute_import
-from __future__ import print_function
-
-import copy
-import logging
-
-from .import utils
-
-
-class Value(object):
- """ Searchable Dict. Can search on both the key and the value """
- def __init__(self, title, id, parent, extra={}):
- self.log = logging.getLogger(__name__)
- self.title = str(title)
- self.id = id
- self.parent = parent
- self.properties = {'id': id}
-
- for k, v in extra.items():
- setattr(self, k, v)
-
- @classmethod
- def list(cls, name, items, parent, title='title', id='id'):
- values = [cls(item[title], str(item[id]), parent, item) for item in items]
- return utils.AddressableList(values, name)
-
- def __repr__(self):
- print(self)
- return "<{title}: {id} in {parent}>".format(**self.__dict__)
-
- def copy(self):
- value = self.__class__(self.title, self.id, self.parent)
- value.properties = copy.copy(self.properties)
- return value
-
- def serialize(self):
- return self.properties
-
- def _repr_html_(self):
- """ Format in HTML for iPython Users """
- return "{0} | {1} | ".format(self.id, self.title)
-
-
- def __str__(self):
- """ allows users to print this out in a user friendly using print
- """
- return "ID {0:25} | Name: {1} \n".format(self.id, self.title)
diff --git a/build/lib/omniture/query.py b/build/lib/omniture/query.py
deleted file mode 100644
index 0393fcd..0000000
--- a/build/lib/omniture/query.py
+++ /dev/null
@@ -1,408 +0,0 @@
-# encoding: utf-8
-from __future__ import absolute_import
-from __future__ import print_function
-
-import time
-from copy import copy, deepcopy
-import functools
-from dateutil.relativedelta import relativedelta
-import json
-import logging
-import sys
-
-from .elements import Value
-from . import reports
-from . import utils
-
-
-def immutable(method):
- @functools.wraps(method)
- def wrapped_method(self, *vargs, **kwargs):
- obj = self.clone()
- method(obj, *vargs, **kwargs)
- return obj
-
- return wrapped_method
-
-class ReportNotSubmittedError(Exception):
- """ Exception that is raised when a is requested by hasn't been submitted
- to Adobe
- """
- def __init__(self,error):
- self.log = logging.getLogger(__name__)
- self.log.debug("Report Has not been submitted, call async() or run()")
- super(ReportNotSubmittedError, self).__init__("Report Not Submitted")
-
-class Query(object):
- """ Lets you build a query to the Reporting API for Adobe Analytics.
-
- Methods in this object are chainable. For example
- >>> report = report.element("page").element("prop1").
- metric("pageviews").granularity("day").run()
- Making it easy to create a report.
-
- To see the raw definition use
- >>> print report
- """
-
- GRANULARITY_LEVELS = ['hour', 'day', 'week', 'month', 'quarter', 'year']
- STATUSES = ["Not Submitted","Not Ready","Done"]
-
- def __init__(self, suite):
- """ Setup the basic structure of the report query. """
- self.log = logging.getLogger(__name__)
- self.suite = suite
- self.raw = {}
- #Put the report suite in so the user can print
- #the raw query and have it work as is
- self.raw['reportSuiteID'] = str(self.suite.id)
- self.id = None
- self.method = "Get"
- self.status = self.STATUSES[0]
- #The report object
- self.report = reports.Report
- #The fully hydrated report object
- self.processed_response = None
- self.unprocessed_response = None
-
- def _normalize_value(self, value, category):
- if isinstance(value, Value):
- return value
- else:
- return getattr(self.suite, category)[value]
-
- def _serialize_value(self, value, category):
- return self._normalize_value(value, category).serialize()
-
- def _serialize_values(self, values, category):
- if not isinstance(values, list):
- values = [values]
-
- return [self._serialize_value(value, category) for value in values]
-
- def _serialize(self, obj):
- if isinstance(obj, list):
- return [self._serialize(el) for el in obj]
- elif isinstance(obj, Value):
- return obj.serialize()
- else:
- return obj
-
- def clone(self):
- """ Return a copy of the current object. """
- query = Query(self.suite)
- query.raw = copy(self.raw)
- query.report = self.report
- query.status = self.status
- query.processed_response = self.processed_response
- query.unprocessed_response = self.unprocessed_response
- return query
-
- @immutable
- def range(self, start, stop=None, months=0, days=0, granularity=None):
- """
- Define a date range for the report.
-
- * start -- The start date of the report. If stop is not present
- it is assumed to be the to and from dates.
- * stop (optional) -- the end date of the report (inclusive).
- * months (optional, named) -- months to run used for relative dates
- * days (optional, named)-- days to run used for relative dates)
- * granulartiy (optional, named) -- set the granularity for the report
- """
- start = utils.date(start)
- stop = utils.date(stop)
-
- if days or months:
- stop = start + relativedelta(days=days-1, months=months)
- else:
- stop = stop or start
-
- if start == stop:
- self.raw['date'] = start.isoformat()
- else:
- self.raw.update({
- 'dateFrom': start.isoformat(),
- 'dateTo': stop.isoformat(),
- })
-
- if granularity:
- self.raw = self.granularity(granularity).raw
-
- return self
-
- @immutable
- def granularity(self, granularity):
- """
- Set the granulartiy for the report.
-
- Values are one of the following
- 'hour', 'day', 'week', 'month', 'quarter', 'year'
- """
- if granularity not in self.GRANULARITY_LEVELS:
- levels = ", ".join(self.GRANULARITY_LEVELS)
- raise ValueError("Granularity should be one of: " + levels)
-
- self.raw['dateGranularity'] = granularity
-
- return self
-
- @immutable
- def set(self, key=None, value=None, **kwargs):
- """
- Set a custom property in the report
-
- `set` is a way to add raw properties to the request,
- for features that python-omniture does not support but the
- SiteCatalyst API does support. For convenience's sake,
- it will serialize Value and Element objects but will
- leave any other kind of value alone.
- """
-
- if key and value:
- self.raw[key] = self._serialize(value)
- elif key or kwargs:
- properties = key or kwargs
- for key, value in properties.items():
- self.raw[key] = self._serialize(value)
- else:
- raise ValueError("Query#set requires a key and value, \
- a properties dictionary or keyword arguments.")
-
- return self
-
- @immutable
- def filter(self, segment=None, segments=None, disable_validation=False, **kwargs):
- """ Set Add a segment to the report. """
- # It would appear to me that 'segment_id' has a strict subset
- # of the functionality of 'segments', but until I find out for
- # sure, I'll provide both options.
- if 'segments' not in self.raw:
- self.raw['segments'] = []
-
- if disable_validation == False:
- if segments:
- self.raw['segments'].extend(self._serialize_values(segments, 'segments'))
- elif segment:
- self.raw['segments'].append({"id":self._normalize_value(segment,
- 'segments').id})
- elif kwargs:
- self.raw['segments'].append(kwargs)
- else:
- raise ValueError()
-
- else:
- if segments:
- self.raw['segments'].extend([{"id":segment} for segment in segments])
- elif segment:
- self.raw['segments'].append({"id":segment})
- elif kwargs:
- self.raw['segments'].append(kwargs)
- else:
- raise ValueError()
- return self
-
- @immutable
- def element(self, element, disable_validation=False, **kwargs):
- """
- Add an element to the report.
-
- This method is intended to be called multiple time. Each time it will
- add an element as a breakdown
- After the first element, each additional element is considered
- a breakdown
- """
-
- if self.raw.get('elements', None) == None:
- self.raw['elements'] = []
-
- if disable_validation == False:
- element = self._serialize_value(element, 'elements')
- else:
- element = {"id":element}
-
- if kwargs != None:
- element.update(kwargs)
- self.raw['elements'].append(deepcopy(element))
-
- #TODO allow this method to accept a list
- return self
-
-
- def breakdown(self, element, **kwargs):
- """ Pass through for element. Adds an element to the report. """
- return self.element(element, **kwargs)
-
-
- def elements(self, *args, **kwargs):
- """ Shortcut for adding multiple elements. Doesn't support arguments """
- obj = self
- for e in args:
- obj = obj.element(e, **kwargs)
-
- return obj
-
- @immutable
- def metric(self, metric, disable_validation=False):
- """
- Add an metric to the report.
-
- This method is intended to be called multiple time.
- Each time a metric will be added to the report
- """
- if self.raw.get('metrics', None) == None:
- self.raw['metrics'] = []
- if disable_validation == False:
- self.raw['metrics'].append(self._serialize_value(metric, 'metrics'))
- else:
- self.raw['metrics'].append({"id":metric})
- #self.raw['metrics'] = self._serialize_values(metric, 'metrics')
- #TODO allow this metric to accept a list
- return self
-
- def metrics(self, *args, **kwargs):
- """ Shortcut for adding multiple metrics """
- obj = self
- for m in args:
- obj = obj.metric(m, **kwargs)
-
- return obj
-
- @immutable
- def sortBy(self, metric):
- """ Specify the sortBy Metric """
- self.raw['sortBy'] = metric
- return self
-
- @immutable
- def currentData(self):
- """ Set the currentData flag """
- self.raw['currentData'] = True
- return self
-
-
- def build(self):
- """ Return the report descriptoin as an object """
- return {'reportDescription': self.raw}
-
- def queue(self):
- """ Submits the report to the Queue on the Adobe side. """
- q = self.build()
- self.log.debug("Suite Object: %s Method: %s, Query %s",
- self.suite, self.report.method, q)
- self.id = self.suite.request('Report',
- self.report.method,
- q)['reportID']
- self.status = self.STATUSES[1]
- return self
-
- def probe(self, heartbeat=None, interval=1, soak=False):
- """ Keep checking until the report is done"""
- #Loop until the report is done
- while self.is_ready() == False:
- if heartbeat:
- heartbeat()
- time.sleep(interval)
- #Use a back off up to 30 seconds to play nice with the APIs
- if interval < 1:
- interval = 1
- elif interval < 30:
- interval = round(interval * 1.5)
- else:
- interval = 30
- self.log.debug("Check Interval: %s seconds", interval)
-
- def is_ready(self):
- """ inspects the response to see if the report is ready """
- if self.status == self.STATUSES[0]:
- raise ReportNotSubmittedError('{"message":"Doh! the report needs to be submitted first"}')
- elif self.status == self.STATUSES[1]:
- try:
- # the request method catches the report and populates it automatically
- response = self.suite.request('Report','Get',{'reportID': self.id})
- self.status = self.STATUSES[2]
- self.unprocessed_response = response
- self.processed_response = self.report(response, self)
- return True
- except reports.ReportNotReadyError:
- self.status = self.STATUSES[1]
- #raise reports.InvalidReportError(response)
- return False
- elif self.status == self.STATUSES[2]:
- return True
-
-
- def sync(self, heartbeat=None, interval=0.01):
- """ Run the report synchronously,"""
- print("sync called")
- if self.status == self.STATUSES[0]:
- print("Queing Report")
- self.queue()
- self.probe(heartbeat, interval)
- if self.status == self.STATUSES[1]:
- self.probe()
- return self.processed_response
-
- def async(self, callback=None, heartbeat=None, interval=1):
- """ Run the Report Asynchrnously """
- if self.status == self.STATUSES[0]:
- self.queue()
- return self
-
- def get_report(self):
- self.is_ready()
- if self.status == self.STATUSES[2]:
- return self.processed_response
- else:
- raise reports.ReportNotReadyError('{"message":"Doh! the report is not ready yet"}')
-
- def run(self, defaultheartbeat=True, heartbeat=None, interval=0.01):
- """Shortcut for sync(). Runs the current report synchronously. """
- if defaultheartbeat == True:
- rheartbeat = self.heartbeat
- else:
- rheartbeat = heartbeat
-
- return self.sync(rheartbeat, interval)
-
- def heartbeat(self):
- """ A default heartbeat method that prints a dot for each request """
- sys.stdout.write('.')
- sys.stdout.flush()
-
-
- def check(self):
- """
- Basically an alias to is ready to make the interface a bit better
- """
- return self.is_ready()
-
- def cancel(self):
- """ Cancels a the report from the Queue on the Adobe side. """
- return self.suite.request('Report',
- 'CancelReport',
- {'reportID': self.id})
- def json(self):
- """ Return a JSON string of the Request """
- return str(json.dumps(self.build(), indent=4, separators=(',', ': '), sort_keys=True))
-
- def __str__(self):
- return self.json()
-
- def _repr_html_(self):
- """ Format in HTML for iPython Users """
- report = { str(key):value for key,value in self.raw.items() }
- html = "Current Report Settings"
- for k,v in sorted(list(report.items())):
- html += "{0}: {1} ".format(k,v)
- if self.id:
- html += "This report has been submitted"
- html += "{0}: {1} ".format("ReportId", self.id)
- return html
-
-
- def __dir__(self):
- """ Give sensible options for Tab Completion mostly for iPython """
- return ['async','breakdown','cancel','clone','currentData', 'element',
- 'filter', 'granularity', 'id','json' ,'metric', 'queue', 'range', 'raw', 'report',
- 'request', 'run', 'set', 'sortBy', 'suite']
diff --git a/build/lib/omniture/reports.py b/build/lib/omniture/reports.py
deleted file mode 100644
index 40c1c01..0000000
--- a/build/lib/omniture/reports.py
+++ /dev/null
@@ -1,235 +0,0 @@
-# encoding: utf-8
-from __future__ import absolute_import
-from __future__ import print_function
-
-import logging
-from datetime import datetime
-import json
-
-from .elements import Value
-
-
-class InvalidReportError(Exception):
- """
- Exception raised when the API says a report defintion is
- invalid
- """
- def normalize(self, error):
- print ('error', error)
- return {
- 'error': error.get('error'),
- 'error_description': error.get('error_description'),
- 'error_uri': error.get('error_uri', ''),
- }
-
- def __init__(self, error):
- self.log = logging.getLogger(__name__)
- error = self.normalize(error)
- self.message = "{error}: {error_description} ({error_uri})".format(**error)
- super(InvalidReportError, self).__init__(self.message)
-
-class ReportNotReadyError(Exception):
- """ Exception that is raised when a report is not ready to be downloaded
- """
- def __init__(self,error):
- self.log = logging.getLogger(__name__)
- self.log.debug("Report Not Ready")
- super(ReportNotReadyError, self).__init__("Report Not Ready")
-
-
-# TODO: also make this iterable (go through rows)
-class Report(object):
- """
- Object to parse the responses of the report
-
- To get the data use
- >>> report.data
-
- To get a Pandas DataFrame use
- >>> report.dataframe
-
- To get the raw response use
- >>> print report
-
- """
- def process(self):
- """ Parse out the relevant data from the report and store it for easy access
- Should only be used internally to the class
- """
- self.timing = {
- 'queue': float(self.raw['waitSeconds']),
- 'execution': float(self.raw['runSeconds']),
- }
- self.log.debug("Report Wait Time: %s, Report Execution Time: %s", self.timing['queue'], self.timing['execution'])
- self.report = report = self.raw['report']
- self.metrics = Value.list('metrics', report['metrics'], self.suite, 'name', 'id')
- self.elements = Value.list('elements', report['elements'], self.suite, 'name', 'id')
- self.period = str(report['period'])
- self.type = str(report['type'])
-
- segments = report.get('segments')
- if segments:
- self.segments = []
- for s in segments:
- self.segments.append(self.query.suite.segments[s['id']])
- else:
- self.segments = None
-
- #Set as none until it is actually used
- self.dict_data = None
- self.pandas_data = None
-
- @property
- def data(self):
- """ Returns the report data as a set of dicts for easy quering
- It generates the dicts on the 1st call then simply returns the reference to the data in subsequent calls
- """
- #If the data hasn't been generate it generate the data
- if self.dict_data == None:
- self.dict_data = self.parse_rows(self.report['data'])
-
- return self.dict_data
-
- def parse_rows(self,row, level=0, upperlevels=None):
- """
- Parse through the data returned by a repor. Return a list of dicts.
-
- This method is recursive.
- """
- #self.log.debug("Level %s, Upperlevels %s, Row Type %s, Row: %s", level,upperlevels, type(row), row)
- data = {}
- data_set = []
-
- #merge in the upper levels
- if upperlevels != None:
- data.update(upperlevels)
-
-
- if type(row) == list:
- for r in row:
- #on the first call set add to the empty list
- pr = self.parse_rows(r,level, data.copy())
- if type(pr) == dict:
- data_set.append(pr)
- #otherwise add to the existing list
- else:
- data_set.extend(pr)
-
- #pull out the metrics from the lowest level
- if type(row) == dict:
- #pull out any relevant data from the current record
- #Handle datetime isn't in the elements list for trended reports
- if level == 0 and self.type == "trended":
- element = "datetime"
- elif self.type == "trended":
- if hasattr(self.elements[level-1], 'classification'):
- #handle the case where there are multiple classifications
- element = str(self.elements[level-1].id) + ' | ' + str(self.elements[level-1].classification)
- else:
- element = str(self.elements[level-1].id)
- else:
- if hasattr(self.elements[level], 'classification'):
- #handle the case where there are multiple classifications
- element = str(self.elements[level].id) + ' | ' + str(self.elements[level].classification)
- else:
- element = str(self.elements[level].id)
-
-
- if element == "datetime":
- data[element] = datetime(int(row.get('year',0)),int(row.get('month',0)),int(row.get('day',0)),int(row.get('hour',0)))
- data["datetime_friendly"] = str(row['name'])
- else:
- try:
- data[element] = str(row['name'])
-
- # If the name value is Null or non-encodable value, return null
- except:
- data[element] = "null"
- #parse out any breakdowns and add to the data set
- if 'breakdown' in row and len(row['breakdown']) > 0:
- data_set.extend(self.parse_rows(row['breakdown'], level+1, data))
- elif 'counts' in row:
- for index, metric in enumerate(row['counts']):
- #decide what type of event
- if self.metrics[index].decimals > 0 or metric.find('.') >-1:
- data[str(self.metrics[index].id)] = float(metric)
- else:
- data[str(self.metrics[index].id)] = int(metric)
-
-
-
- if len(data_set)>0:
- return data_set
- else:
- return data
-
- @property
- def dataframe(self):
- """
- Returns pandas DataFrame for additional analysis.
-
- Will generate the data the first time it is called otherwise passes a cached version
- """
-
- if self.pandas_data is None:
- self.pandas_data = self.to_dataframe()
-
- return self.pandas_data
-
-
- def to_dataframe(self):
- import pandas as pd
- return pd.DataFrame.from_dict(self.data)
-
- def __init__(self, raw, query):
- self.log = logging.getLogger(__name__)
- self.raw = raw
- self.query = query
- self.suite = query.suite
- self.process()
-
- def __repr__(self):
- info = {
- 'metrics': ", ".join(map(str, self.metrics)),
- 'elements': ", ".join(map(str, self.elements)),
- }
- return "".format(**info)
-
- def __div__(self):
- """ Give sensible options for Tab Completion mostly for iPython """
- return ['data','dataframe', 'metrics','elements', 'segments', 'period', 'type', 'timing']
-
- def _repr_html_(self):
- """ Format in HTML for iPython Users """
- html = ""
- for index, item in enumerate(self.data):
- html += ""
- #populate header Row
- if index < 1:
- html += "
"
- if 'datetime' in item:
- html += "| {0} | ".format('datetime')
- for key in sorted(list(item.keys())):
- if key != 'datetime':
- html += "{0} | ".format(key)
- html += "
"
-
- #Make sure date time is alway listed first
- if 'datetime' in item:
- html += "| {0} | ".format(item['datetime'])
- for key, value in sorted(list(item.items())):
- if key != 'datetime':
- html += "{0} | ".format(value)
- html += "