<commit_before>import codecs
import calendar
import datetime
import io
import json
import logging
import mimetypes
import sys
import urllib
import urllib2
import uuid

FACEBOOK_API = 'https://graph.facebook.com'

logger = logging.getLogger(__name__)


class MultipartFormdataEncoder(object):
    def __init__(self):
        self.boundary = uuid.uuid4().hex
        self.content_type = 'multipart/form-data; boundary={}'.format(
            self.boundary)

    @classmethod
    def u(cls, s):
        if sys.hexversion < 0x03000000 and isinstance(s, str):
            s = s.decode('utf-8')
        if sys.hexversion >= 0x03000000 and isinstance(s, bytes):
            s = s.decode('utf-8')
        return s

    def iter(self, fields, files):
        """
        fields is a sequence of (name, value) elements for regular form fields.
        files is a sequence of (name, file-like) elements for data
        to be uploaded as files.
        Yield body's chunk as bytes
        """
        encoder = codecs.getencoder('utf-8')
        for key, value in fields.iteritems():
            key = self.u(key)
            yield encoder('--{}\r\n'.format(self.boundary))
            yield encoder(self.u(
                'Content-Disposition: form-data; name="{}"\r\n').format(key))
            yield encoder('\r\n')
            if isinstance(value, int) or isinstance(value, float):
                value = str(value)
            yield encoder(self.u(value))
            yield encoder('\r\n')
        for key, value in files.iteritems():
            key = self.u(key)
            filename = self.u(value.name)
            yield encoder('--{}\r\n'.format(self.boundary))
            yield encoder(self.u('Content-Disposition: form-data; name="{}"; filename="{}"\r\n').format(key, filename))
            yield encoder('Content-Type: {}\r\n'.format(mimetypes.guess_type(filename)[0] or 'application/octet-stream'))
            yield encoder('\r\n')
            buff = value.read()
            yield (buff, len(buff))
            yield encoder('\r\n')
        yield encoder('--{}--\r\b'.format(self.boundary))

    def encode(self, fields, files):
        body = io.BytesIO()
        for chunk, chunk_len in self.iter(fields, files):
            body.write(chunk)
        return self.content_type, body.getvalue()


class AdsAPIError(Exception):
    """
    Errors as defined in the Facebook documentation
    https://developers.facebook.com/docs/reference/ads-api/error-reference/
    """
    def __init__(self, error):
        try:
            self.error = json.load(error)
        except:
            self.message = '{}'.format(error)
            self.code = None
            self.type = None
            self.str = self.message
            self.error = {'message': self.message}
        else:
            error_dict = self.error.get('error', {})
            self.message = error_dict.get('message', '')
            # New error details in api 2.2
            if 'error_user_title' in error_dict:
                self.message += ' - ' + error_dict['error_user_title']
            if 'error_user_msg' in error_dict:
                self.message += ' - ' + error_dict['error_user_msg']
            self.code = self.error.get('error', {}).get('code')
            self.type = self.error.get('error', {}).get('type')
            self.str = '(%s %s) %s' % (self.type, self.code, self.message)

    def __str__(self):
        return self.str


class AdsAPI(object):
    """A client for the Facebook Ads API."""
    DATA_LIMIT = 1000

    def __init__(self, access_token, app_id='', app_secret='', version=None):
        """
        :param access_token: The API access token
        :param app_id: An optional App id, current only used for debug_token.
        :param app_secret: An optional App secret, currently only used for debug_token
        :param version: Facebook API version, e.g. "2.2". It's currently optional but will be required soon.
        """
        self.access_token = access_token
        self.app_id = app_id
        self.app_secret = app_secret
        if version:
            self.api_root = '{}/v{}'.format(FACEBOOK_API, version)
        else:
            self.api_root = FACEBOOK_API

    def make_request(self, path, method, args=None, files=None, batch=False, raw_path=False):
        """Makes a request against the Facebook Ads API endpoint."""
        args = dict(args or {})
        args = {k.encode('utf-8'): unicode(v).encode('utf-8')
                for k, v in args.items()}

        if batch:
            # Then just return a dict for the batch request
            return {
                'method': method,
                'relative_url': '%s?%s' % (path, urllib.urlencode(args))
            }
        logger.info('Making a %s request at %s/%s with %s' % (method, self.api_root, path, args))
        if 'access_token' not in args:
            args['access_token'] = self.access_token
        try:
            if method == 'GET':
                url = path if raw_path else '%s/%s?%s' % (self.api_root, path, urllib.urlencode(args))
                f = urllib2.urlopen(url)
            elif method == 'POST':
                url = path if raw_path else '%s/%s' % (self.api_root, path)
                if files:
                    encoder = MultipartFormdataEncoder()
                    content_type, body = encoder.encode(args, files)
                    req = urllib2.Request(url, data=body)
                    req.add_header('Content-Type', content_type)
                    f = urllib2.urlopen(req)
                else:
                    f = urllib2.urlopen(url, urllib.urlencode(args))
            elif method == 'DELETE':
                url = path if raw_path else '%s/%s?%s' % (self.api_root, path, urllib.urlencode(args))
                req = urllib2.Request(url)
                req.get_method = lambda: 'DELETE'
                f = urllib2.urlopen(req)
            else:
                raise
            return json.load(f)
        except urllib2.HTTPError as e:
            err = AdsAPIError(e)
            # Info, not warning or error, because these often happen as an expected result because of user input
            # and well formed requests that facebook rejects.
            logger.info(u'API Error: {}'.format(err.message))
            raise err
        except urllib2.URLError as e:
            logger.warn(u'URLError: %s' % e.reason)
            raise

    def make_batch_request(self, batch):
        """Makes a batched request against the Facebook Ads API endpoint."""
        args = {}
        args['access_token'] = self.access_token
        args['batch'] = json.dumps(batch)
        args = {k.encode('utf-8'): unicode(v).encode('utf-8')
                for k, v in args.items()}
        logger.info('Making a batched request with %s' % args)
        try:
            f = urllib2.urlopen(self.api_root, urllib.urlencode(args))
            data = json.load(f)
            # For debugging
            self.data = data
            for idx, val in enumerate(data):
                data[idx] = json.loads(val['body'])
            return data
        except urllib2.HTTPError as e:
            logger.info('%s' % e)
            return json.load(e)
        except urllib2.URLError as e:
            logger.warn('URLError: %s' % e.reason)

    def make_labeled_batch_request(self, batch):
        """Makes a batched request with label against the Facebook Ads API."""
        try:
            labels = batch.keys()
            queries = batch.values()
            data = self.make_batch_request(queries)
            # For debugging
            self.data = data
            return dict(zip(labels, data))
        except urllib2.HTTPError as e:
            print '%s' % e
            return json.load(e)
        except urllib2.URLError as e:
            print 'URLError: %s' % e.reason

    def debug_token(self, token):
        """Returns debug information about the given token."""
        path = 'debug_token'
        args = {
            'input_token': token,
            'access_token': '%s|%s' % (self.app_id, self.app_secret)
        }
        return self.make_request(path, 'GET', args)

    def get_adusers(self, account_id, batch=False):
        """Returns the users of the given ad account."""
        path = 'act_%s/users' % account_id
        return self.make_request(path, 'GET', batch=batch)

    def get_adaccount(self, account_id, fields=None, batch=False):
        """Returns the fields of the given ad account."""
        path = 'act_%s' % account_id
        args = {'fields': fields} if fields else {}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adaccounts(self, user_id, fields,
                       paging_cursors={}, batch=False):
        """Returns the list of Facebook ad accounts."""
        path = '%s/adaccounts' % user_id
        args = {'fields': fields}
        if paging_cursors:
            args.update(paging_cursors)
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adaccount_transactions(
            self, account_id, paging_cursors={}, batch=False):
        """Returns the transactions of the given ad account."""
        path = 'act_%s/transactions' % account_id
        args = {}
        if paging_cursors:
            args.update(paging_cursors)
        return self.make_request(path, 'GET', args, batch=batch)


    def get_adcampaign_group(self, campaign_group_id, fields, batch=False):
        """Return the fields for the given ad campaign group."""
        path = '%s' % campaign_group_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcampaign_groups(self, account_id, fields, batch=False):
        """Returns the fields of all ad campaign groups
           from the given ad account."""
        path = 'act_%s/campaigns' % account_id
        args = {
            'fields': fields,
            'limit': self.DATA_LIMIT
        }
        return self.make_request(path, 'GET', args, batch=batch)

    def delete_adcampaign_group(self, campaign_group_id, batch=False):
        """Delete specific campaign group."""
        path = '%s' % campaign_group_id
        return self.make_request(path, 'DELETE', batch=batch)

    def get_adcampaign(self, campaign_id, fields, batch=False):
        """Returns the fields for the given ad campaign."""
        path = '%s' % campaign_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    # Deprecated
    def get_adcampaigns_of_campaign_group(self, campaign_group_id, fields,
                                          batch=False):
        """Return the fields of all adcampaigns
           from the given adcampaign group."""
        path = '%s/adsets' % campaign_group_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcampaigns_of_account(self, account_id, fields, batch=False):
        """Returns the fields of all ad sets from the given ad account."""
        path = 'act_%s/adsets' % account_id
        args = {
            'fields': fields,
            'limit': self.DATA_LIMIT
        }
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcampaigns(self, account_id, fields=None, batch=False):
        """Returns the fields of all ad sets from the given ad account."""
        return self.get_adcampaigns_of_account(account_id, fields, batch=batch)

    def get_adgroup(self, adgroup_id, fields=None, batch=False):
        """Returns the fields for the given ad group."""
        path = '%s' % adgroup_id
        args = {'fields': fields} if fields else {}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adgroups_by_adaccount(self, account_id, fields=None,
                                  status_fields=None, batch=False):
        """Returns the fields of all ad groups from the given ad account."""
        path = 'act_%s/adgroups' % account_id
        args = {'fields': fields} if fields else {}
        if status_fields:
            args['adgroup_status'] = status_fields
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adgroups_by_adcampaign(self, campaign_id, fields=None,
                                   status_fields=None, batch=False):
        """Returns the fields of all ad groups from the given ad campaign."""
        path = '%s/adgroups' % campaign_id
        args = {'fields': fields} if fields else {}
        if status_fields:
            args['adgroup_status'] = status_fields
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcreative(self, creative_id, fields, batch=False):
        """Returns the fields for the given ad creative."""
        path = '%s' % creative_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcreatives(self, account_id, fields, batch=False):
        """Returns the fields for the given ad creative."""
        path = 'act_%s/adcreatives' % account_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcreatives_by_adgroup(self, adgroup_id, fields, batch=False):
        """Returns the fields for the given ad creative."""
        path = '{0}/adcreatives'.format(adgroup_id)
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adimages(self, account_id, hashes=None, batch=False):
        """Returns the ad images for the given ad account."""
        path = 'act_%s/adimages' % account_id
        args = {}
        if hashes is not None:
            args = {'hashes': hashes}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_stats_by_adcampaign_group(
            self, campaign_group_id, fields=None, filters=None, batch=False,
            start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign group."""
        args = {}
        if fields:
            args['fields'] = json.dumps(fields)
        if filters:
            args['filters'] = json.dumps(filters)
        if start_time:
            args['start_time'] = self.__parse_time(start_time)
        if end_time:
            args['end_time'] = self.__parse_time(end_time)
        path = '%s/stats' % campaign_group_id
        return self.make_request(path, 'GET', args, batch=batch)

    def get_stats_by_adaccount(self, account_id, batch=False, start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign group."""
        args = {}
        start_time = start_time or 0
        path = 'act_{0}/stats/{1}'.format(account_id, self.__parse_time(start_time))
        if end_time:
            path = path + '/{0}'.format(self.__parse_time(end_time))
        return iterate_by_page(self.make_request(path, 'GET', args, batch=batch))

    def get_stats_by_adcampaign(self, account_id, campaign_ids=None, fields=None,
                                batch=False, start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign by adcampaign."""
        args = {}
        if fields:
            args['fields'] = json.dumps(fields)
        if campaign_ids is not None:
            args['campaign_ids'] = json.dumps(campaign_ids)
        if start_time:
            args['start_time'] = self.__parse_time(start_time)
        if end_time:
            args['end_time'] = self.__parse_time(end_time)
        path = 'act_%s/adsets' % account_id
        return self.make_request(path, 'GET', args, batch=batch)

    def get_stats_by_adgroup(
            self, account_id, adgroup_ids=None, batch=False,
            start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign by adgroup."""
        args = {}
        if adgroup_ids is not None:
            args['adgroup_ids'] = json.dumps(adgroup_ids)
        if start_time:
            args['start_time'] = self.__parse_time(start_time)
        if end_time:
            args['end_time'] = self.__parse_time(end_time)
        path = 'act_%s/adgroupstats' % account_id
        return self.make_request(path, 'GET', args, batch=batch)

    def get_time_interval(self, start, end):
        """Returns formatted time interval."""
        if not start or not end:
            return None
        end = end + datetime.timedelta(1)
        if not isinstance(start, datetime.datetime):
            start = datetime.datetime(start)
        if not isinstance(end, datetime.datetime):
            end = datetime.datetime(end)
        time_interval = dict(
            day_start=dict(day=start.day, month=start.month, year=start.year),
            day_stop=dict(day=end.day, month=end.month, year=end.year)
        )
        return json.dumps(time_interval)

    def get_adreport_stats(self, account_id, date_preset, time_increment,
                           data_columns, filters=None, actions_group_by=None,
                           batch=False):
        """Returns the ad report stats for the given account."""
        path = 'act_%s/reportstats' % account_id
        args = {
            'date_preset': date_preset,
            'time_increment': time_increment,
            'data_columns': json.dumps(data_columns),
        }
        if filters is not None:
            args['filters'] = json.dumps(filters)
        if actions_group_by is not None:
            args['actions_group_by'] = actions_group_by
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adreport_stats2(self, account_id, data_columns, date_preset=None,
                            date_start=None, date_end=None,
                            time_increment=None, actions_group_by=None,
                            filters=None, async=False, batch=False, offset=None,
                            sort_by=None, sort_dir=None, summary=None,
                            limit=None, action_attribution_windows=None,
                            breakdowns=None):
        """Returns the ad report stats for the given account."""
        if date_preset is None and date_start is None and date_end is None:
            raise AdsAPIError("Either a date_preset or a date_start/end \
                                must be set when requesting a stats info.")
        path = 'act_%s/insights' % account_id
        args = {
            'data_columns': json.dumps(data_columns),
        }
        if date_preset:
            args['date_preset'] = date_preset
        if offset:
            args['offset'] = offset
        if date_start and date_end:
            args['time_interval'] = \
                self.get_time_interval(date_start, date_end)
        if time_increment:
            args['time_increment'] = time_increment
        if filters:
            args['filters'] = json.dumps(filters)
        if actions_group_by:
            args['actions_group_by'] = json.dumps(actions_group_by)
        if sort_by:
            args['sort_by'] = sort_by
        if sort_dir:
            args['sort_dir'] = sort_dir
        if summary is not None:
            args['summary'] = summary
        if limit:
            args['limit'] = limit
        if action_attribution_windows:
            args['action_attribution_windows'] = json.dumps(action_attribution_windows)
        if breakdowns:
            args['breakdowns'] = json.dumps(breakdowns)
        if async:
            args['async'] = 'true'
            return self.make_request(path, 'POST', args=args, batch=batch)
        return self.make_request(path, 'GET', args=args, batch=batch)

    def get_insights(self, object_id, fields=None, action_breakdowns=None,
                     level=None, date_preset=None, date_start=None,
                     date_end=None, time_increment=None, breakdowns=None,
                     filtering=None, sort=None, limit=None,
                     action_attribution_windows=None,
                     async=False, batch=False):
        """Returns the insights information for the given object."""
        if date_preset is None and date_start is None and date_end is None:
            raise AdsAPIError("Either a date_preset or a date_start/end \
                                must be set when requesting a stats info.")
        path = '%s/insights' % object_id
        args = {}
        if fields:
            args['fields'] = fields
        if action_breakdowns:
            args['action_breakdowns'] = json.dumps(action_breakdowns)
        if action_attribution_windows:
            args['action_attribution_windows'] = json.dumps(action_attribution_windows)
        if level:
            args['level'] = level
        if date_preset:
            args['date_preset'] = date_preset
        if date_start and date_end:
            args['time_interval'] = \
                self.get_time_interval(date_start, date_end)
        if time_increment:
            args['time_increment'] = time_increment
        if breakdowns:
            args['breakdowns'] = json.dumps(breakdowns)
        if filtering:
            args['filtering'] = json.dumps(filtering)
        if sort:
            args['sort'] = sort
        if limit:
            args['limit'] = limit
        if async:
            args['async'] = 'true'
            return self.make_request(path, 'POST', args=args, batch=batch)
        return self.make_request(path, 'GET', args=args, batch=batch)

    def get_async_job_status(self, job_id, batch=False):
        """Returns the asynchronously requested job status"""
        path = '%s' % job_id
        return self.make_request(path, 'GET', batch=batch)

    def get_async_job_result(self, account_id, job_id, batch=False):
        """Returns completed result of the given async job"""
        path = 'act_%s/reportstats' % account_id
        args = {
            'report_run_id': job_id
        }
        return self.make_request(path, 'GET', args=args, batch=batch)

    def get_conversion_stats_by_adaccount(self, account_id, batch=False):
        """Returns the aggregated conversion stats for the given ad account."""
        path = 'act_%s/conversions' % account_id
        return self.make_request(path, 'GET', batch=batch)

    def get_conversion_stats_by_adcampaign(
            self, account_id, campaign_ids=None, include_deleted=False,<commit_msg>Add new methods for getting the target information and reach estimation.<commit_after>import codecs
import calendar
import datetime
import io
import json
import logging
import mimetypes
import sys
import urllib
import urllib2
import uuid

FACEBOOK_API = 'https://graph.facebook.com'

logger = logging.getLogger(__name__)


class MultipartFormdataEncoder(object):
    def __init__(self):
        self.boundary = uuid.uuid4().hex
        self.content_type = 'multipart/form-data; boundary={}'.format(
            self.boundary)

    @classmethod
    def u(cls, s):
        if sys.hexversion < 0x03000000 and isinstance(s, str):
            s = s.decode('utf-8')
        if sys.hexversion >= 0x03000000 and isinstance(s, bytes):
            s = s.decode('utf-8')
        return s

    def iter(self, fields, files):
        """
        fields is a sequence of (name, value) elements for regular form fields.
        files is a sequence of (name, file-like) elements for data
        to be uploaded as files.
        Yield body's chunk as bytes
        """
        encoder = codecs.getencoder('utf-8')
        for key, value in fields.iteritems():
            key = self.u(key)
            yield encoder('--{}\r\n'.format(self.boundary))
            yield encoder(self.u(
                'Content-Disposition: form-data; name="{}"\r\n').format(key))
            yield encoder('\r\n')
            if isinstance(value, int) or isinstance(value, float):
                value = str(value)
            yield encoder(self.u(value))
            yield encoder('\r\n')
        for key, value in files.iteritems():
            key = self.u(key)
            filename = self.u(value.name)
            yield encoder('--{}\r\n'.format(self.boundary))
            yield encoder(self.u('Content-Disposition: form-data; name="{}"; filename="{}"\r\n').format(key, filename))
            yield encoder('Content-Type: {}\r\n'.format(mimetypes.guess_type(filename)[0] or 'application/octet-stream'))
            yield encoder('\r\n')
            buff = value.read()
            yield (buff, len(buff))
            yield encoder('\r\n')
        yield encoder('--{}--\r\b'.format(self.boundary))

    def encode(self, fields, files):
        body = io.BytesIO()
        for chunk, chunk_len in self.iter(fields, files):
            body.write(chunk)
        return self.content_type, body.getvalue()


class AdsAPIError(Exception):
    """
    Errors as defined in the Facebook documentation
    https://developers.facebook.com/docs/reference/ads-api/error-reference/
    """
    def __init__(self, error):
        try:
            self.error = json.load(error)
        except:
            self.message = '{}'.format(error)
            self.code = None
            self.type = None
            self.str = self.message
            self.error = {'message': self.message}
        else:
            error_dict = self.error.get('error', {})
            self.message = error_dict.get('message', '')
            # New error details in api 2.2
            if 'error_user_title' in error_dict:
                self.message += ' - ' + error_dict['error_user_title']
            if 'error_user_msg' in error_dict:
                self.message += ' - ' + error_dict['error_user_msg']
            self.code = self.error.get('error', {}).get('code')
            self.type = self.error.get('error', {}).get('type')
            self.str = '(%s %s) %s' % (self.type, self.code, self.message)

    def __str__(self):
        return self.str


class AdsAPI(object):
    """A client for the Facebook Ads API."""
    DATA_LIMIT = 1000

    def __init__(self, access_token, app_id='', app_secret='', version=None):
        """
        :param access_token: The API access token
        :param app_id: An optional App id, current only used for debug_token.
        :param app_secret: An optional App secret, currently only used for debug_token
        :param version: Facebook API version, e.g. "2.2". It's currently optional but will be required soon.
        """
        self.access_token = access_token
        self.app_id = app_id
        self.app_secret = app_secret
        if version:
            self.api_root = '{}/v{}'.format(FACEBOOK_API, version)
        else:
            self.api_root = FACEBOOK_API

    def make_request(self, path, method, args=None, files=None, batch=False, raw_path=False):
        """Makes a request against the Facebook Ads API endpoint."""
        args = dict(args or {})
        args = {k.encode('utf-8'): unicode(v).encode('utf-8')
                for k, v in args.items()}

        if batch:
            # Then just return a dict for the batch request
            return {
                'method': method,
                'relative_url': '%s?%s' % (path, urllib.urlencode(args))
            }
        logger.info('Making a %s request at %s/%s with %s' % (method, self.api_root, path, args))
        if 'access_token' not in args:
            args['access_token'] = self.access_token
        try:
            if method == 'GET':
                url = path if raw_path else '%s/%s?%s' % (self.api_root, path, urllib.urlencode(args))
                f = urllib2.urlopen(url)
            elif method == 'POST':
                url = path if raw_path else '%s/%s' % (self.api_root, path)
                if files:
                    encoder = MultipartFormdataEncoder()
                    content_type, body = encoder.encode(args, files)
                    req = urllib2.Request(url, data=body)
                    req.add_header('Content-Type', content_type)
                    f = urllib2.urlopen(req)
                else:
                    f = urllib2.urlopen(url, urllib.urlencode(args))
            elif method == 'DELETE':
                url = path if raw_path else '%s/%s?%s' % (self.api_root, path, urllib.urlencode(args))
                req = urllib2.Request(url)
                req.get_method = lambda: 'DELETE'
                f = urllib2.urlopen(req)
            else:
                raise
            return json.load(f)
        except urllib2.HTTPError as e:
            err = AdsAPIError(e)
            # Info, not warning or error, because these often happen as an expected result because of user input
            # and well formed requests that facebook rejects.
            logger.info(u'API Error: {}'.format(err.message))
            raise err
        except urllib2.URLError as e:
            logger.warn(u'URLError: %s' % e.reason)
            raise

    def make_batch_request(self, batch):
        """Makes a batched request against the Facebook Ads API endpoint."""
        args = {}
        args['access_token'] = self.access_token
        args['batch'] = json.dumps(batch)
        args = {k.encode('utf-8'): unicode(v).encode('utf-8')
                for k, v in args.items()}
        logger.info('Making a batched request with %s' % args)
        try:
            f = urllib2.urlopen(self.api_root, urllib.urlencode(args))
            data = json.load(f)
            # For debugging
            self.data = data
            for idx, val in enumerate(data):
                data[idx] = json.loads(val['body'])
            return data
        except urllib2.HTTPError as e:
            logger.info('%s' % e)
            return json.load(e)
        except urllib2.URLError as e:
            logger.warn('URLError: %s' % e.reason)

    def make_labeled_batch_request(self, batch):
        """Makes a batched request with label against the Facebook Ads API."""
        try:
            labels = batch.keys()
            queries = batch.values()
            data = self.make_batch_request(queries)
            # For debugging
            self.data = data
            return dict(zip(labels, data))
        except urllib2.HTTPError as e:
            print '%s' % e
            return json.load(e)
        except urllib2.URLError as e:
            print 'URLError: %s' % e.reason

    def debug_token(self, token):
        """Returns debug information about the given token."""
        path = 'debug_token'
        args = {
            'input_token': token,
            'access_token': '%s|%s' % (self.app_id, self.app_secret)
        }
        return self.make_request(path, 'GET', args)

    def get_adusers(self, account_id, batch=False):
        """Returns the users of the given ad account."""
        path = 'act_%s/users' % account_id
        return self.make_request(path, 'GET', batch=batch)

    def get_adaccount(self, account_id, fields=None, batch=False):
        """Returns the fields of the given ad account."""
        path = 'act_%s' % account_id
        args = {'fields': fields} if fields else {}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adaccounts(self, user_id, fields,
                       paging_cursors={}, batch=False):
        """Returns the list of Facebook ad accounts."""
        path = '%s/adaccounts' % user_id
        args = {'fields': fields}
        if paging_cursors:
            args.update(paging_cursors)
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adaccount_transactions(
            self, account_id, paging_cursors={}, batch=False):
        """Returns the transactions of the given ad account."""
        path = 'act_%s/transactions' % account_id
        args = {}
        if paging_cursors:
            args.update(paging_cursors)
        return self.make_request(path, 'GET', args, batch=batch)


    def get_adcampaign_group(self, campaign_group_id, fields, batch=False):
        """Return the fields for the given ad campaign group."""
        path = '%s' % campaign_group_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcampaign_groups(self, account_id, fields, batch=False):
        """Returns the fields of all ad campaign groups
           from the given ad account."""
        path = 'act_%s/campaigns' % account_id
        args = {
            'fields': fields,
            'limit': self.DATA_LIMIT
        }
        return self.make_request(path, 'GET', args, batch=batch)

    def delete_adcampaign_group(self, campaign_group_id, batch=False):
        """Delete specific campaign group."""
        path = '%s' % campaign_group_id
        return self.make_request(path, 'DELETE', batch=batch)

    def get_adcampaign(self, campaign_id, fields, batch=False):
        """Returns the fields for the given ad campaign."""
        path = '%s' % campaign_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    # Deprecated
    def get_adcampaigns_of_campaign_group(self, campaign_group_id, fields,
                                          batch=False):
        """Return the fields of all adcampaigns
           from the given adcampaign group."""
        path = '%s/adsets' % campaign_group_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcampaigns_of_account(self, account_id, fields, batch=False):
        """Returns the fields of all ad sets from the given ad account."""
        path = 'act_%s/adsets' % account_id
        args = {
            'fields': fields,
            'limit': self.DATA_LIMIT
        }
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcampaigns(self, account_id, fields=None, batch=False):
        """Returns the fields of all ad sets from the given ad account."""
        return self.get_adcampaigns_of_account(account_id, fields, batch=batch)

    def get_adgroup(self, adgroup_id, fields=None, batch=False):
        """Returns the fields for the given ad group."""
        path = '%s' % adgroup_id
        args = {'fields': fields} if fields else {}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adgroups_by_adaccount(self, account_id, fields=None,
                                  status_fields=None, batch=False):
        """Returns the fields of all ad groups from the given ad account."""
        path = 'act_%s/adgroups' % account_id
        args = {'fields': fields} if fields else {}
        if status_fields:
            args['adgroup_status'] = status_fields
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adgroups_by_adcampaign(self, campaign_id, fields=None,
                                   status_fields=None, batch=False):
        """Returns the fields of all ad groups from the given ad campaign."""
        path = '%s/adgroups' % campaign_id
        args = {'fields': fields} if fields else {}
        if status_fields:
            args['adgroup_status'] = status_fields
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcreative(self, creative_id, fields, batch=False):
        """Returns the fields for the given ad creative."""
        path = '%s' % creative_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcreatives(self, account_id, fields, batch=False):
        """Returns the fields for the given ad creative."""
        path = 'act_%s/adcreatives' % account_id
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adcreatives_by_adgroup(self, adgroup_id, fields, batch=False):
        """Returns the fields for the given ad creative."""
        path = '{0}/adcreatives'.format(adgroup_id)
        args = {'fields': fields}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adimages(self, account_id, hashes=None, batch=False):
        """Returns the ad images for the given ad account."""
        path = 'act_%s/adimages' % account_id
        args = {}
        if hashes is not None:
            args = {'hashes': hashes}
        return self.make_request(path, 'GET', args, batch=batch)

    def get_stats_by_adcampaign_group(
            self, campaign_group_id, fields=None, filters=None, batch=False,
            start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign group."""
        args = {}
        if fields:
            args['fields'] = json.dumps(fields)
        if filters:
            args['filters'] = json.dumps(filters)
        if start_time:
            args['start_time'] = self.__parse_time(start_time)
        if end_time:
            args['end_time'] = self.__parse_time(end_time)
        path = '%s/stats' % campaign_group_id
        return self.make_request(path, 'GET', args, batch=batch)

    def get_stats_by_adaccount(self, account_id, batch=False, start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign group."""
        args = {}
        start_time = start_time or 0
        path = 'act_{0}/stats/{1}'.format(account_id, self.__parse_time(start_time))
        if end_time:
            path = path + '/{0}'.format(self.__parse_time(end_time))
        return iterate_by_page(self.make_request(path, 'GET', args, batch=batch))

    def get_stats_by_adcampaign(self, account_id, campaign_ids=None, fields=None,
                                batch=False, start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign by adcampaign."""
        args = {}
        if fields:
            args['fields'] = json.dumps(fields)
        if campaign_ids is not None:
            args['campaign_ids'] = json.dumps(campaign_ids)
        if start_time:
            args['start_time'] = self.__parse_time(start_time)
        if end_time:
            args['end_time'] = self.__parse_time(end_time)
        path = 'act_%s/adsets' % account_id
        return self.make_request(path, 'GET', args, batch=batch)

    def get_stats_by_adgroup(
            self, account_id, adgroup_ids=None, batch=False,
            start_time=None, end_time=None):
        """Returns the stats for a Facebook campaign by adgroup."""
        args = {}
        if adgroup_ids is not None:
            args['adgroup_ids'] = json.dumps(adgroup_ids)
        if start_time:
            args['start_time'] = self.__parse_time(start_time)
        if end_time:
            args['end_time'] = self.__parse_time(end_time)
        path = 'act_%s/adgroupstats' % account_id
        return self.make_request(path, 'GET', args, batch=batch)

    def get_time_interval(self, start, end):
        """Returns formatted time interval."""
        if not start or not end:
            return None
        end = end + datetime.timedelta(1)
        if not isinstance(start, datetime.datetime):
            start = datetime.datetime(start)
        if not isinstance(end, datetime.datetime):
            end = datetime.datetime(end)
        time_interval = dict(
            day_start=dict(day=start.day, month=start.month, year=start.year),
            day_stop=dict(day=end.day, month=end.month, year=end.year)
        )
        return json.dumps(time_interval)

    def get_adreport_stats(self, account_id, date_preset, time_increment,
                           data_columns, filters=None, actions_group_by=None,
                           batch=False):
        """Returns the ad report stats for the given account."""
        path = 'act_%s/reportstats' % account_id
        args = {
            'date_preset': date_preset,
            'time_increment': time_increment,
            'data_columns': json.dumps(data_columns),
        }
        if filters is not None:
            args['filters'] = json.dumps(filters)
        if actions_group_by is not None:
            args['actions_group_by'] = actions_group_by
        return self.make_request(path, 'GET', args, batch=batch)

    def get_adreport_stats2(self, account_id, data_columns, date_preset=None,
                            date_start=None, date_end=None,
                            time_increment=None, actions_group_by=None,
                            filters=None, async=False, batch=False, offset=None,
                            sort_by=None, sort_dir=None, summary=None,
                            limit=None, action_attribution_windows=None,
                            breakdowns=None):
        """Returns the ad report stats for the given account."""
        if date_preset is None and date_start is None and date_end is None:
            raise AdsAPIError("Either a date_preset or a date_start/end \
                                must be set when requesting a stats info.")
        path = 'act_%s/insights' % account_id
        args = {
            'data_columns': json.dumps(data_columns),
        }
        if date_preset:
            args['date_preset'] = date_preset
        if offset:
            args['offset'] = offset
        if date_start and date_end:
            args['time_interval'] = \
                self.get_time_interval(date_start, date_end)
        if time_increment:
            args['time_increment'] = time_increment
        if filters:
            args['filters'] = json.dumps(filters)
        if actions_group_by:
            args['actions_group_by'] = json.dumps(actions_group_by)
        if sort_by:
            args['sort_by'] = sort_by
        if sort_dir:
            args['sort_dir'] = sort_dir
        if summary is not None:
            args['summary'] = summary
        if limit:
            args['limit'] = limit
        if action_attribution_windows:
            args['action_attribution_windows'] = json.dumps(action_attribution_windows)
        if breakdowns:
            args['breakdowns'] = json.dumps(breakdowns)
        if async:
            args['async'] = 'true'
            return self.make_request(path, 'POST', args=args, batch=batch)
        return self.make_request(path, 'GET', args=args, batch=batch)

    def get_insights(self, object_id, fields=None, action_breakdowns=None,
                     level=None, date_preset=None, date_start=None,
                     date_end=None, time_increment=None, breakdowns=None,
                     filtering=None, sort=None, limit=None,
                     action_attribution_windows=None,
                     async=False, batch=False):
        """Returns the insights information for the given object."""
        if date_preset is None and date_start is None and date_end is None:
            raise AdsAPIError("Either a date_preset or a date_start/end \
                                must be set when requesting a stats info.")
        path = '%s/insights' % object_id
        args = {}
        if fields:
            args['fields'] = fields
        if action_breakdowns:
            args['action_breakdowns'] = json.dumps(action_breakdowns)
        if action_attribution_windows:
            args['action_attribution_windows'] = json.dumps(action_attribution_windows)
        if level:
            args['level'] = level
        if date_preset:
            args['date_preset'] = date_preset
        if date_start and date_end:
            args['time_interval'] = \
                self.get_time_interval(date_start, date_end)
        if time_increment:
            args['time_increment'] = time_increment
        if breakdowns:
            args['breakdowns'] = json.dumps(breakdowns)
        if filtering:
            args['filtering'] = json.dumps(filtering)
        if sort:
            args['sort'] = sort
        if limit:
            args['limit'] = limit
        if async:
            args['async'] = 'true'
            return self.make_request(path, 'POST', args=args, batch=batch)
        return self.make_request(path, 'GET', args=args, batch=batch)

    def get_targetingsentencelines(self, object_id, batch=False):
        """Returns targeting information for the given object."""
        path = '%s/targetingsentencelines' % object_id
        return self.make_request(path, 'GET', batch=batch)

    def get_reachestimate(self, object_id, batch=False):
        """Returns reach estimation for the given object."""
        path = '%s/reachestimate' % object_id
        return self.make_request(path, 'GET', batch=batch)

    def get_async_job_status(self, job_id, batch=False):
        """Returns the asynchronously requested job status"""
        path = '%s' % job_id
        return self.make_request(path, 'GET', batch=batch)

    def get_async_job_result(self, account_id, job_id, batch=False):
        """Returns completed result of the given async job"""
        path = 'act_%s/reportstats' % account_id
        args = {
            'report_run_id': job_id
        }
        return self.make_request(path, 'GET', args=args, batch=batch)

    def get_conversion_stats_by_adaccount(self, account_id, batch=False):
        """Returns the aggregated conversion stats for the given ad account."""
        path = 'act_%s/conversions' % account_id
        return self.make_request(path, 'GET', batch=batch)

    def get_conversion_stats_by_adcampaign(
            self, account_id, campaign_ids=None, include_deleted=False,
{'fraction_non_alphanumeric': 0.11196543319299801, 'fraction_numerical': 0.002481719477066253, 'mean_word_length': 2.1818245910885503, 'pattern_counts': {'":': 0, '<': 5, '<?xml version=': 0, '>': 5, 'https://': 4, 'lorem ipsum': 0, 'www.': 0, 'xml': 0}, 'pii_count': 0, 'substrings_counts': 0, 'word_list_counts': {'cursed_substrings.json': 30, 'profanity_word_list.json': 0, 'sexual_word_list.json': 0, 'zh_pornsignals.json': 0}}
{'dir': 'git-commits-cleaned', 'id': '6144674', 'n_tokens_mistral': 13212, 'n_tokens_neox': 12878, 'n_words': 3369}