Exemplo n.º 1
0
class Freshdesk():
    """
    Instantiate FreshDesk Class

    `Args:`
        domain:
            The subdomain of the FreshDesk account. Not required if ``FRESHDESK_DOMAIN``
            env variable set.
        api_key:
            The FreshDesk provided application key. Not required if ``FRESHDESK_API_KEY``
            env variable set.
    `Returns:`
        FreshDesk Class
    """
    def __init__(self, domain, api_key):

        self.api_key = check_env.check('FRESHDESK_API_KEY', api_key)
        self.domain = check_env.check('FRESHDESK_DOMAIN', domain)
        self.uri = f'https://{self.domain}.freshdesk.com/api/v2/'
        self.client = APIConnector(self.uri, auth=(self.api_key, 'x'))

    def get_request(self, endpoint, params=None, **kwargs):
        # Internal method to make a get request.

        base_params = {'per_page': PAGE_SIZE}

        if params:
            base_params.update(params)

        r = self.client.request(self.uri + endpoint, 'GET', params=base_params)
        self.client.validate_response(r)
        data = r.json()

        # Paginate
        while 'link' in r.headers.keys():
            logger.info(f'Retrieving another page of {PAGE_SIZE} records.')
            url = re.search('<(.*)>', r.headers['link']).group(1)
            r = self.client.request(url, 'GET', params=params)
            self.client.validate_response(r)
            data.extend(r.json())

        return data

    def transform_table(self, tbl, expand_custom_fields=None):
        # Internal method to transform a table prior to returning
        if tbl.num_rows > 0:
            tbl.move_column('id', 0)
            tbl.sort()
            if expand_custom_fields:
                tbl.unpack_dict('custom_fields', prepend=False)

        return tbl

    def get_tickets(self,
                    ticket_type=None,
                    requester_id=None,
                    requester_email=None,
                    company_id=None,
                    updated_since='2016-01-01',
                    expand_custom_fields=False):
        """
        List tickets.

        .. warning::
            Deleted and Spam tickets are not included. However they can be pulled separately
            by utilizing the ``ticket_type`` parameter.

        .. warning::
            Freshdesk will return a maximum of 9,000 tickets. To access additional tickets,
            utilize the ``updated_since`` parameter.

        `Args:`
            ticket_type: str
                Filter by type of ticket to filter by. Valid fields include ``new_and_my_open``,
                ``watching``, ``spam`` and ``deleted``.
            requester_id: int
                Filter by requester id.
            requester_email: str
                Filter by requester email.
            company_id: int
                Filter by company_id.
            expand_custom_fields: boolean
                Expand nested custom fields to their own columns.
        `Returns:`
            Parsons Table
                See :ref:`parsons-table` for output options.
        """

        params = {
            'filter': ticket_type,
            'requester_id': requester_id,
            'requester_email': requester_email,
            'company_id': company_id,
            'updated_since': updated_since
        }

        tbl = Table(self.get_request('tickets', params=params))
        logger.info(f'Found {tbl.num_rows} tickets.')
        return self.transform_table(tbl, expand_custom_fields)

    def get_contacts(self,
                     email=None,
                     mobile=None,
                     phone=None,
                     company_id=None,
                     state=None,
                     updated_since=None,
                     expand_custom_fields=None):
        """
        Get contacts.

        `Args:`
            email: str
                Filter by email address.
            mobile: str
                Filter by mobile phone number.
            phone: str
                Filter by phone number.
            expand_custom_fields: boolean
                Expand nested custom fields to their own columns.
        """

        params = {
            'email': email,
            'mobile': mobile,
            'phone': phone,
            'company_id': company_id,
            'state': state,
            '_updated_since': updated_since
        }

        tbl = Table(self.get_request('contacts', params=params))
        logger.info(f'Found {tbl.num_rows} contacts.')
        return self.transform_table(tbl, expand_custom_fields)

    def get_companies(self, expand_custom_fields=False):
        """
        List companies.

        `Args:`
            expand_custom_fields: boolean
                Expand nested custom fields to their own columns.
        `Returns:`
            Parsons Table
                See :ref:`parsons-table` for output options.
        """

        tbl = Table(self.get_request('companies'))
        logger.info(f'Found {tbl.num_rows} companies.')
        return self.transform_table(tbl, expand_custom_fields)

    def get_agents(self, email=None, mobile=None, phone=None, state=None):
        """
        List agents.

        `Args:`
            email: str
                Filter by email address.
            mobile: str
                Filter by mobile phone number
            phone: str
                Filter by phone number
            state: str
                Filter by state
        `Returns:`
            Parsons Table
                See :ref:`parsons-table` for output options.
        """

        params = {
            'email': email,
            'mobile': mobile,
            'phone': phone,
            'state': state
        }

        tbl = Table(self.get_request('agents', params=params))
        logger.info(f'Found {tbl.num_rows} agents.')
        tbl = self.transform_table(tbl)
        tbl = tbl.unpack_dict('contact', prepend=False)
        tbl.remove_column(
            'signature')  # Removing since raw HTML might cause issues.

        return tbl
Exemplo n.º 2
0
class Quickbase(object):
    """
    Instantiate the Quickbase class

    `Args:`
        hostname: str
            The URL for the homepage/login page of the organization's Quickbase
            instance (e.g. demo.quickbase.com).
        user_token: str
            The Quickbase account user token (API key). Not required if
            ``QUICKBASE_USER_TOKEN`` env variable is set.
    `Returns:`
        Quickbase Class
    """
    def __init__(self, hostname=None, user_token=None):
        self.hostname = check_env.check('QUICKBASE_HOSTNAME', hostname)
        self.user_token = check_env.check('QUICKBASE_USER_TOKEN', user_token)
        self.api_hostname = 'https://api.quickbase.com/v1'
        self.client = APIConnector(self.api_hostname,
                                   headers={'QB-Realm-Hostname': self.hostname,
                                            'AUTHORIZATION': f'QB-USER-TOKEN {self.user_token}'})

    def get_app_tables(self, app_id=None):
        """
        Query records in a Quickbase table. This follows the patterns laid out
        in Quickbase query documentaiton, located here:
        https://help.quickbase.com/api-guide/componentsquery.html

        `Args:`
            app_id: str
                Identifies which Quickbase app from which to fetch tables.
        `Returns:`
            Table Class
        """
        return Table(self.client.request(
            f'{self.api_hostname}/tables?appId={app_id}',
            'GET').json())

    def query_records(self, table_from=None):
        """
        Query records in a Quickbase table. This follows the patterns laid out
        in Quickbase query documentaiton, located here:
        https://help.quickbase.com/api-guide/componentsquery.html

        `Args:`
            from: str
                The ID of a Quickbase resource (i.e. a table) to query.
        `Returns:`
            Table Class
        """
        req_resp = \
            (self.client.request(f'{self.api_hostname}/records/query',
                                 'POST',
                                 json={"from": table_from}).json())

        resp_tbl = Table(req_resp['data'])
        cleaned_tbl = Table()

        for row in resp_tbl:
            row_dict = {}
            for column in resp_tbl.columns:
                row_dict[column] = row[column]['value']
            cleaned_tbl.concat(Table([row_dict]))

        column_resp = req_resp['fields']
        column_map = {}
        for entry in column_resp:
            column_map[str(entry['id'])] = entry['label'].lower().strip()

        for column in cleaned_tbl.columns:
            cleaned_tbl.rename_column(column, column_map[column])

        return cleaned_tbl
Exemplo n.º 3
0
class Shopify(object):
    """
    Instantiate the Shopify class

    `Args:`
        subdomain: str
            The Shopify subdomain (e.g. ``myorg`` for myorg.myshopify.com) Not required if
            ``SHOPIFY_SUBDOMAIN`` env variable set.
        password: str
            The Shopify account password. Not required if ``SHOPIFY_PASSWORD`` env
            variable set.
        api_key: str
            The Shopify account API key. Not required if ``SHOPIFY_API_KEY`` env variable
            set.
        api_version: str
            The Shopify API version. Not required if ``SHOPIFY_API_VERSION`` env variable
            set.
    `Returns:`
        Shopify Class
    """
    def __init__(self,
                 subdomain=None,
                 password=None,
                 api_key=None,
                 api_version=None):
        self.subdomain = check_env.check('SHOPIFY_SUBDOMAIN', subdomain)
        self.password = check_env.check('SHOPIFY_PASSWORD', password)
        self.api_key = check_env.check('SHOPIFY_API_KEY', api_key)
        self.api_version = check_env.check('SHOPIFY_API_VERSION', api_version)
        self.base_url = 'https://%s.myshopify.com/admin/api/%s/' % (
            self.subdomain, self.api_version)
        self.client = APIConnector(self.base_url,
                                   auth=(self.api_key, self.password))

    def get_count(self, query_date=None, since_id=None, table_name=None):
        """
        Get the count of rows in a table.

        `Args:`
            query_date: str
                Filter query by a date that rows were created. This filter is ignored if value
                is None.
            since_id: str
                Filter query by a minimum ID. This filter is ignored if value is None.
            table_name: str
                The name of the Shopify table to query.
        `Returns:`
            int
        """
        return self.client.request(
            self.get_query_url(query_date, since_id, table_name),
            'GET').json().get("count", 0)

    def get_orders(self, query_date=None, since_id=None, completed=True):
        """
        Get Shopify orders.

        `Args:`
            query_date: str
                Filter query by a date that rows were created. Format: yyyy-mm-dd. This filter
                is ignored if value is None.
            since_id: str
                Filter query by a minimum ID. This filter is ignored if value is None.
            completed: bool
                True if only getting completed orders, False otherwise.
        `Returns:`
            Table Class
        """
        orders = []

        def _append_orders(url):
            nonlocal orders

            if completed:
                url += '&financial_status=paid'

            res = self.client.request(url, 'GET')

            cur_orders = res.json().get("orders", [])

            # Flatten orders to non-complex types
            for order in cur_orders:
                keys_to_add = {}
                keys_to_delete = []

                for key1 in order:
                    if isinstance(order[key1], dict):
                        for key2 in order[key1]:
                            keys_to_add[key1 + '_' + key2] = order[key1][key2]
                        keys_to_delete.append(key1)
                    elif key1 == 'note_attributes':
                        for note in order[key1]:
                            keys_to_add[key1 + '_' +
                                        note['name']] = note['value']

                order.update(keys_to_add)
                for key in keys_to_delete:
                    del order[key]

            orders += cur_orders

            return res

        res = _append_orders(
            self.get_query_url(query_date, since_id, "orders", False))

        # Get next page
        while res.headers.get("Link"):
            link = re.split('; |, ', res.headers.get("Link"))
            if len(link) and link[len(link) - 1] == 'rel="next"':
                res = _append_orders(link[len(link) - 2][1:-1])
            else:
                break

        return Table(orders)

    def get_query_url(self,
                      query_date=None,
                      since_id=None,
                      table_name=None,
                      count=True):
        """
        Get the URL of a Shopify API request

        `Args:`
            query_date: str
                Filter query by a date that rows were created. Format: yyyy-mm-dd. This filter
                is ignored if value is None.
            since_id: str
                Filter query by a minimum ID. This filter is ignored if value is None.
            table_name: str
                The name of the Shopify table to query.
            count: bool
                True if refund should be included in Table, False otherwise.
        `Returns:`
            str
        """
        filters = 'limit=250&status=any'

        if count:
            table = table_name + '/count.json'
        else:
            table = table_name + '.json'

        if query_date:
            # Specific date if provided
            query_date = datetime.strptime(query_date, "%Y-%m-%d")
            max_date = query_date + timedelta(days=1)
            filters += '&created_at_min={}&created_at_max={}'.format(
                query_date.isoformat(), max_date.isoformat())
        elif since_id:
            # Since ID if provided
            filters += '&since_id=%s' % since_id

        return self.base_url + '%s?%s' % (table, filters)

    def graphql(self, query):
        """
        Make GraphQL request. Reference: https://shopify.dev/api/admin-graphql

        `Args:`
            query: str
                GraphQL query.
        `Returns:`
            dict
        """
        return self.client.request(self.base_url + 'graphql.json',
                                   'POST',
                                   json={
                                       "query": query
                                   }).json().get('data')

    @classmethod
    def load_to_table(cls,
                      subdomain=None,
                      password=None,
                      api_key=None,
                      api_version=None,
                      query_date=None,
                      since_id=None,
                      completed=True):
        """
        Fast classmethod so you can get the data all at once:
        tabledata = Shopify.load_to_table(subdomain='myorg', password='******',
                                         api_key='abc123', api_version='2020-10',
                                         query_date='2020-10-20', since_id='8414',
                                         completed=True)
        This instantiates the class and makes the appropriate query type to Shopify's orders
        table based on which arguments are supplied.

        `Args:`
            subdomain: str
                The Shopify subdomain (e.g. ``myorg`` for myorg.myshopify.com).
            password: str
                The Shopify account password.
            api_key: str
                The Shopify account API key.
            api_version: str
                The Shopify API version.
            query_date: str
                Filter query by a date that rows were created. Format: yyyy-mm-dd. This filter
                is ignored if value is None.
            since_id: str
                Filter query by a minimum ID. This filter is ignored if value is None.
            completed: bool
                True if only getting completed orders, False otherwise.
                value as value
        `Returns:`
            Table Class
        """
        return cls(subdomain, password, api_key,
                   api_version).get_orders(query_date, since_id, completed)
Exemplo n.º 4
0
class RockTheVote:
    """
    `Args:`
        partner_id: str
            The RockTheVote partner ID for the RTV account
        partner_api_key: str
            The API Key for the partner
        testing: bool
            Whether or not to use the staging instance. Defaults to False.
    `Returns`:
        RockTheVote class
    """
    def __init__(self, partner_id=None, partner_api_key=None, testing=False):
        self.partner_id = check_env.check('RTV_PARTNER_ID', partner_id)
        self.partner_api_key = check_env.check('RTV_PARTNER_API_KEY',
                                               partner_api_key)

        if testing:
            self.client = APIConnector(TESTING_URI, headers=REQUEST_HEADERS)
        else:
            self.client = APIConnector(PRODUCTION_URI, headers=REQUEST_HEADERS)

    def create_registration_report(self,
                                   before=None,
                                   since=None,
                                   report_type=None):
        """
        Create a new registration report.

        `Args:`
            before: str
                Limit to registrations that were started before this date, in
                ISO format (e.g. 2020-01-01)
            since: str
                Limit to registrations that were started since this date, in
                ISO format (e.g. 2020-01-01)
            report_type: str
                The type of report to create. If left as None, it creates the default report. The
                ``extended`` report includes additional fields. Currently only accepts ``extended``.
        `Returns:`
            int
                The ID of the created report.
        """
        report_url = f'registrant_reports.json'
        # Create the report for the new data
        report_parameters = {
            'partner_id': self.partner_id,
            'partner_API_key': self.partner_api_key,
        }

        # Declare these here so the logging doesn't error out
        since_date = before_date = None

        if report_type:
            if report_type not in VALID_REPORT_TYPES:
                raise RTVFailure(
                    f"Invalid report type. Must be one of {VALID_REPORT_TYPES}"
                )
            report_parameters["report_type"] = report_type
        if since:
            since_date = parse_date(since).strftime(DATETIME_FORMAT)
            report_parameters['since'] = since_date
        if before:
            before_date = parse_date(before).strftime(DATETIME_FORMAT)
            report_parameters['before'] = before_date

        # The report parameters get passed into the request as JSON in the body
        # of the request.
        report_str = f"{report_type} report" if report_type else "report"
        logger.info(f"Creating {report_str} for {self.partner_id} "
                    f"for dates: {since_date} to {before_date}...")
        response = self.client.request(report_url,
                                       'post',
                                       json=report_parameters)
        if response.status_code != requests.codes.ok:
            raise RTVFailure("Couldn't create RTV registrations report")

        response_json = response.json()
        # The RTV API says the response should include the report_id, but I have not found
        # that to be the case
        report_id = response_json.get('report_id')
        if report_id:
            logger.info(f"Created report with id {report_id}.")
            return report_id

        # If the response didn't include the report_id, then we will parse it out of the URL.
        status_url = response_json.get('status_url')
        url_match = STATUS_URL_PARSE_REGEX.search(status_url)
        if url_match:
            report_id = url_match.group(1)

        logger.info(f"Created report with id {report_id}.")
        return report_id

    def get_registration_report(self,
                                report_id,
                                block=False,
                                poll_interval_seconds=60,
                                report_timeout_seconds=3600):
        """
        Get data from an existing registration report.

        `Args:`
            report_id: int
                The ID of the report to get data from
            block: bool
                Whether or not to block execution until the report is complete
            poll_interval_seconds: int
                If blocking, how long to pause between attempts to check if the report is done
            report_timeout_seconds: int
                If blocking, how long to wait for the report before timing out
        `Returns:`
            Parsons Table
                Parsons table with the report data.
        """
        logger.info(f"Getting report with id {report_id}...")
        credentials = {
            'partner_id': self.partner_id,
            'partner_API_key': self.partner_api_key,
        }
        status_url = f'registrant_reports/{report_id}'
        download_url = None

        # Let's figure out at what time should we just give up because we waited
        # too long
        end_time = datetime.datetime.now() + datetime.timedelta(
            seconds=report_timeout_seconds)

        # If we have a download URL, we can move on and just download the
        # report. Otherwise, as long as we haven't run out of time, we will
        # check the status.
        while not download_url and datetime.datetime.now() < end_time:
            logger.debug(
                f'Registrations report not ready yet, sleeping %s seconds',
                poll_interval_seconds)

            # Check the status again via the status endpoint
            status_response = self.client.request(status_url,
                                                  'get',
                                                  params=credentials)

            # Check to make sure the call got a valid response
            if status_response.status_code == requests.codes.ok:
                status_json = status_response.json()

                # Grab the download_url from the response.
                download_url = status_json.get('download_url')

                if not download_url and not block:
                    return None
            else:
                raise RTVFailure("Couldn't get report status")

            if not download_url:
                # We just got the status, so we should wait a minute before
                # we check it again.
                time.sleep(poll_interval_seconds)

        # If we never got a valid download_url, then we timed out waiting for
        # the report to generate. We will log an error and exit.
        if not download_url:
            raise RTVFailure('Timed out waiting for report')

        # Download the report data
        download_response = self.client.request(download_url,
                                                'get',
                                                params=credentials)

        # Check to make sure the call got a valid response
        if download_response.status_code == requests.codes.ok:
            report_data = download_response.text

            # Load the report data into a Parsons Table
            table = Table.from_csv_string(report_data)

            # Transform the data from the report's CSV format to something more
            # Pythonic (snake case)
            normalized_column_names = [
                re.sub(r'\s', '_', name).lower() for name in table.columns
            ]
            normalized_column_names = [
                re.sub(r'[^A-Za-z\d_]', '', name)
                for name in normalized_column_names
            ]
            table.table = petl.setheader(table.table, normalized_column_names)
            return table
        else:
            raise RTVFailure('Unable to download report data')

    def run_registration_report(self,
                                before=None,
                                since=None,
                                report_type=None,
                                poll_interval_seconds=60,
                                report_timeout_seconds=3600):
        """
        Run a new registration report.

        This method will block until the report has finished generating, or until the specified
        timeout is reached.

        `Args:`
            before: str
                Limit to registrations that were started before this date, in
                ISO format (e.g. 2020-01-01)
            since: str
                Limit to registrations that were started since this date, in
                ISO format (e.g. 2020-01-01)
            report_type: str
                The type of report to run. If left as None, it runs the default report. The
                ``extended`` report includes additional fields. Currently only accepts ``extended``.
            poll_interval_seconds: int
                If blocking, how long to pause between attempts to check if the report is done
            report_timeout_seconds: int
                If blocking, how long to wait for the report before timing out
        `Returns:`
            Parsons.Table
                The table with the report data.
        """
        report_str = f"{report_type} report" if report_type else "report"
        logger.info(f"Running {report_str} for {self.partner_id} "
                    f"for dates: {since} to {before}...")
        report_id = self.create_registration_report(before=before,
                                                    since=since,
                                                    report_type=report_type)
        return self.get_registration_report(
            report_id,
            block=True,
            poll_interval_seconds=poll_interval_seconds,
            report_timeout_seconds=report_timeout_seconds)
Exemplo n.º 5
0
class Mailchimp():
    """
    Instantiate Mailchimp Class

    `Args:`
        api_key:
            The Mailchimp-provided application key. Not required if
            ``MAILCHIMP_API_KEY`` env variable set.
    `Returns:`
        Mailchimp Class
    """

    def __init__(self, api_key=None):
        self.api_key = check_env.check('MAILCHIMP_API_KEY', api_key)
        self.domain = re.findall("(?<=-).+$", self.api_key)[0]
        self.uri = f'https://{self.domain}.api.mailchimp.com/3.0/'
        self.client = APIConnector(self.uri, auth=('x', self.api_key))

    def get_request(self, endpoint, params=None, **kwargs):
        # Internal method to make a get request.

        r = self.client.request(self.uri + endpoint, 'GET', params=params)
        self.client.validate_response(r)
        data = r.json()

        return data

    def get_lists(self, fields=None, exclude_fields=None,
                  count=None, offset=None, before_date_created=None,
                  since_date_created=None, before_campaign_last_sent=None,
                  since_campaign_last_sent=None, email=None, sort_field=None,
                  sort_dir=None):
        """
        Get a table of lists under the account based on query parameters. Note
        that argument descriptions here are sourced from Mailchimp's official
        API documentation.

        `Args:`
            fields: list of strings
                A comma-separated list of fields to return. Reference
                parameters of sub-objects with dot notation.
            exclude_fields: list of strings
                A comma-separated list of fields to exclude. Reference
                parameters of sub-objects with dot notation.
            count: int
                The number of records to return. Default value is 10. Maximum
                value is 1000.
            offset: int
                The number of records from a collection to skip. Iterating over
                large collections with this parameter can be slow. Default
                value is 0.
            before_date_created: string
                Restrict response to lists created before the set date. We
                recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            since_date_created: string
                Restrict results to lists created after the set date. We
                recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            before_campaign_last_sent: string
                Restrict results to lists created before the last campaign send
                date. We recommend ISO 8601 time format:
                2015-10-21T15:41:36+00:00.
            since_campaign_last_sent: string
                Restrict results to lists created after the last campaign send
                date. We recommend ISO 8601 time format:
                2015-10-21T15:41:36+00:00.
            email: string
                Restrict results to lists that include a specific subscriber's
                email address.
            sort_field: string, can only be 'date_created' or None
                Returns files sorted by the specified field.
            sort_dir: string, can only be 'ASC', 'DESC', or None
                Determines the order direction for sorted results.

        `Returns:`
            Table Class
        """
        params = {'fields': fields,
                  'exclude_fields': exclude_fields,
                  'count': count,
                  'offset': offset,
                  'before_date_created': before_date_created,
                  'since_date_created': since_date_created,
                  'before_campaign_last_sent': before_campaign_last_sent,
                  'since_campaign_last_sent': since_campaign_last_sent,
                  'email': email,
                  'sort_field': sort_field,
                  'sort_dir': sort_dir}

        response = self.get_request('lists', params=params)
        tbl = Table(response['lists'])
        logger.info(f'Found {tbl.num_rows} lists.')
        if tbl.num_rows > 0:
            return tbl
        else:
            return Table()

    def get_campaigns(self, fields=None, exclude_fields=None,
                      count=None, offset=None, type=None, status=None,
                      before_send_time=None, since_send_time=None,
                      before_create_time=None, since_create_time=None,
                      list_id=None, folder_id=None, member_id=None,
                      sort_field=None, sort_dir=None):
        """
        Get a table of campaigns under the account based on query parameters.
        Note that argument descriptions here are sourced from Mailchimp's
        official API documentation.

        `Args:`
            fields: list of strings
                A comma-separated list of fields to return. Reference
                parameters of sub-objects with dot notation.
            exclude_fields: list of strings
                A comma-separated list of fields to exclude. Reference
                parameters of sub-objects with dot notation.
            count: int
                The number of records to return. Default value is 10. Maximum
                value is 1000.
            offset: int
                The number of records from a collection to skip. Iterating over
                large collections with this parameter can be slow. Default
                value is 0.
            type: string, can only be 'regular', 'plaintext', 'absplit', 'rss',
            'variate', or None
                The campaign type.
            status: string, can only be 'save', 'paused', 'schedule',
            'sending', 'sent', or None
                The status of the campaign.
            before_send_time: string
                Restrict the response to campaigns sent before the set time. We
                recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            since_send_time: string
                Restrict the response to campaigns sent after the set time. We
                recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            before_create_time: string
                Restrict the response to campaigns created before the set time.
                We recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            since_create_time: string
                Restrict the response to campaigns created after the set time.
                We recommend ISO 8601 time format: 2015-10-21T15:41:36+00:00.
            list_id: string
                The unique id for the list.
            folder_id: string
                The unique folder id.
            member_id: string
                Retrieve campaigns sent to a particular list member. Member ID
                is The MD5 hash of the lowercase version of the list member’s
                email address.
            sort_field: string, can only be 'create_time', 'send_time', or None
                Returns files sorted by the specified field.
            sort_dir: string, can only be 'ASC', 'DESC', or None
                Determines the order direction for sorted results.

        `Returns:`
            Table Class
        """
        params = {'fields': fields,
                  'exclude_fields': exclude_fields,
                  'count': count,
                  'offset': offset,
                  'type': type,
                  'status': status,
                  'before_send_time': before_send_time,
                  'since_send_time': since_send_time,
                  'before_create_time': before_create_time,
                  'since_create_time': since_create_time,
                  'list_id': list_id,
                  'folder_id': folder_id,
                  'member_id': member_id,
                  'sort_field': sort_field,
                  'sort_dir': sort_dir}

        response = self.get_request('campaigns', params=params)
        tbl = Table(response['campaigns'])
        logger.info(f'Found {tbl.num_rows} campaigns.')
        if tbl.num_rows > 0:
            return tbl
        else:
            return Table()

    def get_members(self, list_id, fields=None,
                    exclude_fields=None, count=None, offset=None,
                    email_type=None, status=None, since_timestamp_opt=None,
                    before_timestamp_opt=None, since_last_changed=None,
                    before_last_changed=None, unique_email_id=None,
                    vip_only=False, interest_category_id=None,
                    interest_ids=None, interest_match=None, sort_field=None,
                    sort_dir=None, since_last_campaign=None,
                    unsubscribed_since=None):
        """
        Get a table of members in a list based on query parameters. Note that
        argument descriptions here are sourced from Mailchimp's official API
        documentation.

        `Args:`
            list_id: string
                The unique ID of the list to fetch members from.
            fields: list of strings
                A comma-separated list of fields to return. Reference
                parameters of sub-objects with dot notation.
            exclude_fields: list of fields as strings
                A comma-separated list of fields to exclude. Reference
                parameters of sub-objects with dot notation.
            count: int
                The number of records to return. Default value is 10. Maximum
                value is 1000.
            offset: int
                The number of records from a collection to skip. Iterating over
                large collections with this parameter can be slow. Default
                value is 0.
            email_type: string
                The email type.
            status: string, can only be 'subscribed', 'unsubscribed',
            'cleaned', 'pending', 'transactional', 'archived', or None
                The subscriber's status.
            since_timestamp_opt: string
                Restrict results to subscribers who opted-in after the set
                timeframe. We recommend ISO 8601 time format:
                2015-10-21T15:41:36+00:00.
            before_timestamp_opt: string
                Restrict results to subscribers who opted-in before the set
                timeframe. We recommend ISO 8601 time format:
                2015-10-21T15:41:36+00:00.
            since_last_changed: string
                Restrict results to subscribers whose information changed after
                the set timeframe. We recommend ISO 8601 time format:
                2015-10-21T15:41:36+00:00.
            before_last_changed: string
                Restrict results to subscribers whose information changed
                before the set timeframe. We recommend ISO 8601 time format:
                2015-10-21T15:41:36+00:00.
            unique_email_id: string
                A unique identifier for the email address across all Mailchimp
                lists. This parameter can be found in any links with Ecommerce
                Tracking enabled.
            vip_only: boolean
                A filter to return only the list's VIP members. Passing true
                will restrict results to VIP list members, passing false will
                return all list members.
            interest_category_id: string
                The unique id for the interest category.
            interest_ids: list of strings
                Used to filter list members by interests. Must be accompanied
                by interest_category_id and interest_match. The value must be a
                comma separated list of interest ids present for any supplied
                interest categories.
            interest_match: string, can only be 'any', 'all', 'none', or None
                Used to filter list members by interests. Must be accompanied
                by interest_category_id and interest_ids. "any" will match a
                member with any of the interest supplied, "all" will only match
                members with every interest supplied, and "none" will match
                members without any of the interest supplied.
            sort_field: string, can only be 'timestamp_opt',
            'timestamp_signup', 'last_changed', or None
                Returns files sorted by the specified field.
            sort_dir: string, can only be 'ASC', 'DESC', or None
                Determines the order direction for sorted results.
            since_last_campaign: string
                Filter subscribers by those
                subscribed/unsubscribed/pending/cleaned since last email
                campaign send. Member status is required to use this filter.
            unsubscribed_since: string
                Filter subscribers by those unsubscribed since a specific date.
                Using any status other than unsubscribed with this filter will
                result in an error.

        `Returns:`
            Table Class
        """
        params = {'fields': fields,
                  'exclude_fields': exclude_fields,
                  'count': count,
                  'offset': offset,
                  'email_type': email_type,
                  'status': status,
                  'since_timestamp_opt': since_timestamp_opt,
                  'before_timestamp_opt': before_timestamp_opt,
                  'since_last_changed': since_last_changed,
                  'before_last_changed': before_last_changed,
                  'unqiue_email_id': unique_email_id,
                  'vip_only': vip_only,
                  'interest_category_id': interest_category_id,
                  'interest_ids': interest_ids,
                  'interest_match': interest_match,
                  'sort_field': sort_field,
                  'sort_dir': sort_dir,
                  'since_last_campaign': since_last_campaign,
                  'unsubscribed_since': unsubscribed_since}

        response = self.get_request(f'lists/{list_id}/members', params=params)
        tbl = Table(response['members'])
        logger.info(f'Found {tbl.num_rows} members.')
        if tbl.num_rows > 0:
            return tbl
        else:
            return Table()

    def get_campaign_emails(self, campaign_id, fields=None,
                            exclude_fields=None, count=None, offset=None,
                            since=None):
        """
        Get a table of individual emails from a campaign based on query
        parameters. Note that argument descriptions here are sourced from
        Mailchimp's official API documentation.

        `Args:`
            campaign_id: string
                The unique ID of the campaign to fetch emails from.
            fields: list of strings
                A comma-separated list of fields to return. Reference
                parameters of sub-objects with dot notation.
            exclude_fields: list of strings
                A comma-separated list of fields to exclude. Reference
                parameters of sub-objects with dot notation.
            count: int
                The number of records to return. Default value is 10. Maximum
                value is 1000.
            offset: int
                The number of records from a collection to skip. Iterating over
                large collections with this parameter can be slow. Default
                value is 0.
            since: string
                Restrict results to email activity events that occur after a
                specific time. We recommend ISO 8601 time format:
                2015-10-21T15:41:36+00:00.

        `Returns:`
            Table Class
        """
        params = {'fields': fields,
                  'exclude_fields': exclude_fields,
                  'count': count,
                  'offset': offset,
                  'since': since}

        response = self.get_request(f'reports/{campaign_id}/email-activity',
                                    params=params)
        tbl = Table(response['emails'])
        if tbl.num_rows > 0:
            return tbl
        else:
            return Table()

    def get_unsubscribes(self, campaign_id, fields=None,
                         exclude_fields=None, count=None, offset=None):
        """
        Get a table of unsubscribes associated with a campaign based on query
        parameters. Note that argument descriptions here are sourced from
        Mailchimp's official API documentation.

        `Args:`
            campaign_id: string
                The unique ID of the campaign to fetch unsubscribes from.
            fields: list of strings
                A comma-separated list of fields to return. Reference
                parameters of sub-objects with dot notation.
            exclude_fields: list of strings
                A comma-separated list of fields to exclude. Reference
                parameters of sub-objects with dot notation.
            count: int
                The number of records to return. Default value is 10. Maximum
                value is 1000.
            offset: int
                The number of records from a collection to skip. Iterating over
                large collections with this parameter can be slow. Default
                value is 0.

        `Returns:`
            Table Class
        """
        params = {'fields': fields,
                  'exclude_fields': exclude_fields,
                  'count': count,
                  'offset': offset}

        response = self.get_request(f'reports/{campaign_id}/unsubscribed',
                                    params=params)
        tbl = Table(response['unsubscribes'])
        logger.info(f'Found {tbl.num_rows} unsubscribes for {campaign_id}.')
        if tbl.num_rows > 0:
            return tbl
        else:
            return Table()
Exemplo n.º 6
0
class RockTheVote:
    """
    `Args:`
        partner_id: str
            The RockTheVote partner ID for the RTV account
        partner_api_key: str
            The API Key for the partner
    `Returns`:
        RockTheVote class
    """
    def __init__(self, partner_id=None, partner_api_key=None):
        self.partner_id = check_env.check('RTV_PARTNER_ID', partner_id)
        self.partner_api_key = check_env.check('RTV_PARTNER_API_KEY',
                                               partner_api_key)

        self.client = APIConnector('https://vr.rockthevote.com/api/v4',
                                   headers=REQUEST_HEADERS)

    def create_registration_report(self, before=None, since=None):
        """
        Create a new registration report.

        `Args:`
            before: str
                Date before which to return registrations for
            since: str
                Date for filtering registrations
        `Returns:`
            int
                The ID of the created report.
        """
        report_url = f'{self.client.uri}/registrant_reports.json'
        # Create the report for the new data
        report_parameters = {
            'partner_id': self.partner_id,
            'partner_API_key': self.partner_api_key,
        }

        if since:
            since_date = parse_date(since)
            report_parameters['since'] = since_date.strftime(DATETIME_FORMAT)
        if before:
            before_date = parse_date(before)
            report_parameters['before'] = before_date.strftime(DATETIME_FORMAT)

        # The report parameters get passed into the request as JSON in the body
        # of the request.
        response = self.client.request(report_url,
                                       'post',
                                       json=report_parameters)
        if response.status_code != requests.codes.ok:
            raise RTVFailure("Couldn't create RTV registrations report")

        response_json = response.json()
        # The RTV API says the response should include the report_id, but I have not found
        # that to be the case
        report_id = response_json.get('report_id')
        if report_id:
            return report_id

        # If the response didn't include the report_id, then we will parse it out of the URL.
        status_url = response_json.get('status_url')
        url_match = STATUS_URL_PARSE_REGEX.search(status_url)
        if url_match:
            report_id = url_match.group(1)

        return report_id

    def get_registration_report(self,
                                report_id,
                                block=False,
                                poll_interval_seconds=60,
                                report_timeout_seconds=3600):
        """
        Get data from an existing registration report.

        `Args:`
            report_id: int
                The ID of the report to get data from
            block: bool
                Whether or not to block execution until the report is complete
            poll_interval_seconds: int
                If blocking, how long to pause between attempts to check if the report is done
            report_timeout_seconds: int
                If blocking, how long to wait for the report before timing out
        `Returns:`
            Parsons Table
                Parsons table with the report data.
        """
        credentials = {
            'partner_id': self.partner_id,
            'partner_API_key': self.partner_api_key,
        }
        status_url = f'{self.client.uri}/registrant_reports/{report_id}'
        download_url = None

        # Let's figure out at what time should we just give up because we waited
        # too long
        end_time = datetime.datetime.now() + datetime.timedelta(
            seconds=report_timeout_seconds)

        # If we have a download URL, we can move on and just download the
        # report. Otherwise, as long as we haven't run out of time, we will
        # check the status.
        while not download_url and datetime.datetime.now() < end_time:
            logger.debug(
                f'Registrations report not ready yet, sleeping %s seconds',
                poll_interval_seconds)

            # Check the status again via the status endpoint
            status_response = self.client.request(status_url,
                                                  'get',
                                                  params=credentials)

            # Check to make sure the call got a valid response
            if status_response.status_code == requests.codes.ok:
                status_json = status_response.json()

                # Grab the download_url from the response.
                download_url = status_json.get('download_url')

                if not download_url and not block:
                    return None
            else:
                raise RTVFailure("Couldn't get report status")

            if not download_url:
                # We just got the status, so we should wait a minute before
                # we check it again.
                time.sleep(poll_interval_seconds)

        # If we never got a valid download_url, then we timed out waiting for
        # the report to generate. We will log an error and exit.
        if not download_url:
            raise RTVFailure('Timed out waiting for report')

        # Download the report data
        download_response = self.client.request(download_url,
                                                'get',
                                                params=credentials)

        # Check to make sure the call got a valid response
        if download_response.status_code == requests.codes.ok:
            report_data = download_response.text

            # Load the report data into a Parsons Table
            table = Table.from_csv_string(report_data)

            # Transform the data from the report's CSV format to something more
            # Pythonic (snake case)
            normalized_column_names = [
                re.sub(r'\s', '_', name).lower() for name in table.columns
            ]
            normalized_column_names = [
                re.sub(r'[^A-Za-z\d_]', '', name)
                for name in normalized_column_names
            ]
            table.table = petl.setheader(table.table, normalized_column_names)
            return table
        else:
            raise RTVFailure('Unable to download report data')

    def run_registration_report(self,
                                before=None,
                                since=None,
                                poll_interval_seconds=60,
                                report_timeout_seconds=3600):
        """
        Run a new registration report.

        This method will block until the report has finished generating, or until the specified
        timeout is reached.

        `Args:`
            before: str
                Date before which to return registrations for
            since: str
                Date for filtering registrations
            poll_interval_seconds: int
                If blocking, how long to pause between attempts to check if the report is done
            report_timeout_seconds: int
                If blocking, how long to wait for the report before timing out
        `Returns:`
            int
                The ID of the created report.
        """
        report_id = self.create_registration_report(before=before, since=since)
        return self.get_registration_report(
            report_id,
            block=True,
            poll_interval_seconds=poll_interval_seconds,
            report_timeout_seconds=report_timeout_seconds)