Пример #1
0
class CustomerIOWrapper(object):
    BASE_URL = 'https://beta-api.customer.io/v1/api'

    def __init__(self, site_id, api_key):
        self.site_id = site_id
        self.api_key = api_key
        self.cio = CustomerIO(site_id, api_key)

    def fetch(self, uri, payload):
        response = requests.get(
            self.BASE_URL + uri,
            data=payload,
            auth=(self.site_id, self.api_key),
        )
        return response

    def get_messages(self, message_type=None):
        payload = {
            'type': message_type,
        }
        response = self.fetch('/messages', payload)
        data = json.loads(response.content)
        return data.get('messages')

    def remove_bulk(self, filename):
        with open(filename) as f:
            reader = csv.DictReader(f, delimiter=',')
            items = list(reader)
            item_count = len(items)

            if not items:
                print(f'Error: no rows to in the file: {filename}')
                return False

            if 'id' not in items[0]:
                print(f'Error: id column not exists in the file: {filename}')
                return False

            for idx, item in enumerate(items):
                if 'id' not in item:
                    continue

                try:
                    self.cio.delete(customer_id=item['id'])
                    item_label = item['email'] if 'email' in item else item[
                        'id']
                    item_percent = round((idx / item_count) * 100, 2)
                    print(
                        f'{item_percent}% DELETE ({idx} of {item_count}): {item_label}'
                    )
                except Exception as e:
                    print(f'Exception: {e}')

            return True
Пример #2
0
class CIOApi(object):
    """Wrapper for CustomerIO python library."""
    def __init__(self):
        super(CIOApi, self).__init__()
        self.cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY)

    def api_call(func):
        """Wrapper for API cals."""
        def wrapped_func(*args, **kwargs):
            args_str = u', '.join(unicode(a) for a in args[1:])
            args_str += u', '.join(unicode(v) for v in kwargs.values())
            logger.debug(u'CustomerIO api call: %s %s' % (func.__name__, args_str))
            if not settings.CUSTOMERIO_ENABLED:
                return None
            try:
                return func(*args, **kwargs)
            except Exception as e:
                logger.exception(e)
        return wrapped_func

    @api_call
    def create_or_update(self, membership):
        self.cio.identify(
            id=membership.id,
            email=membership.user.email,
            created_at=membership.user.date_joined,
            last_login=membership.last_login,
            short_name=membership.get_short_name(),
            full_name=membership.get_full_name(),
            role=membership.get_role_display(),
            account_name=membership.account.name,
            account_plan=membership.account.plan.get_name_display(),
            account_is_trial=membership.account.is_trial(),
            account_is_active=membership.account.is_active,
            account_date_cancel=membership.account.date_cancel,
            account_date_created=membership.account.date_created,
        )

    @api_call
    def track_event(self, membership, name, **kwargs):
        self.cio.track(customer_id=membership.id, name=name, **kwargs)

    @api_call
    def delete(self, membership):
        self.cio.delete(customer_id=membership.id)
class TestCustomerIO(HTTPSTestCase):
    '''Starts server which the client connects to in the following tests'''
    def setUp(self):
        self.cio = CustomerIO(
            site_id='siteid',
            api_key='apikey',
            host=self.server.server_address[0],
            port=self.server.server_port,
            retries=5,
            backoff_factor=0)

        # do not verify the ssl certificate as it is self signed
        # should only be done for tests
        self.cio.http.verify = False

    def _check_request(self, resp, rq, *args, **kwargs):
        request = resp.request
        self.assertEqual(request.method, rq['method'])
        self.assertEqual(json.loads(request.body.decode('utf-8')), rq['body'])
        self.assertEqual(request.headers['Authorization'], rq['authorization'])
        self.assertEqual(request.headers['Content-Type'], rq['content_type'])
        self.assertEqual(int(request.headers['Content-Length']), len(json.dumps(rq['body'])))
        self.assertTrue(request.url.endswith(rq['url_suffix']),
            'url: {} expected suffix: {}'.format(request.url, rq['url_suffix']))


    def test_client_connection_handling(self):
        retries = self.cio.retries
        # should not raise exception as i should be less than retries and 
        # therefore the last request should return a valid response
        for i in range(retries):
            self.cio.identify(i, fail_count=i)

        # should raise expection as we get invalid responses for all retries
        with self.assertRaises(CustomerIOException):
            self.cio.identify(retries, fail_count=retries)


    def test_identify_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1',
            'body': {"name": "john", "email": "*****@*****.**"},
        }))

        self.cio.identify(id=1, name='john', email='*****@*****.**')

        with self.assertRaises(TypeError):
            self.cio.identify(random_attr="some_value")


    def test_track_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/events',
            'body': {"data": {"email": "*****@*****.**"}, "name": "sign_up"},
        }))

        self.cio.track(customer_id=1, name='sign_up', email='*****@*****.**')

        with self.assertRaises(TypeError):
            self.cio.track(random_attr="some_value")


    def test_pageview_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/events',
            'body': {"data": {"referer": "category_1"}, "type": "page", "name": "product_1"},
        }))

        self.cio.pageview(customer_id=1, page='product_1', referer='category_1')

        with self.assertRaises(TypeError):
            self.cio.pageview(random_attr="some_value")


    def test_delete_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'DELETE',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1',
            'body': {},
        }))

        self.cio.delete(customer_id=1)

        with self.assertRaises(TypeError):
            self.cio.delete(random_attr="some_value")


    def test_backfill_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/events',
            'body': {"timestamp": 1234567890, "data": {"email": "*****@*****.**"}, "name": "signup"},
        }))

        self.cio.backfill(customer_id=1, name='signup', timestamp=1234567890, email='*****@*****.**')

        with self.assertRaises(TypeError):
            self.cio.backfill(random_attr="some_value")

    def test_base_url(self):
        test_cases = [
            # host, port, prefix, result
            (None, None, None, 'https://track.customer.io/api/v1'),
            (None, None, 'v2', 'https://track.customer.io/v2'),
            (None, None, '/v2/', 'https://track.customer.io/v2'),
            ('sub.domain.com', 1337, '/v2/', 'https://sub.domain.com:1337/v2'),
            ('/sub.domain.com/', 1337, '/v2/', 'https://sub.domain.com:1337/v2'),
            ('http://sub.domain.com/', 1337, '/v2/', 'https://sub.domain.com:1337/v2'),
        ]

        for host, port, prefix, result in test_cases:
            cio = CustomerIO(host=host, port=port, url_prefix=prefix)
            self.assertEqual(cio.base_url, result)


    def test_device_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_1", "platform":"ios"}}
        }))

        self.cio.add_device(customer_id=1, device_id="device_1", platform="ios")
        with self.assertRaises(TypeError):
            self.cio.add_device(random_attr="some_value")

    def test_device_call_last_used(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_2", "platform": "android", "last_used": 1234567890}}
        }))

        self.cio.add_device(customer_id=1, device_id="device_2", platform="android", last_used=1234567890)

    def test_device_call_valid_platform(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_3", "platform": "notsupported"}}
        }))

        with self.assertRaises(CustomerIOException):
            self.cio.add_device(customer_id=1, device_id="device_3", platform=None)
   
    def test_device_call_has_customer_id(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_4", "platform": "ios"}}
        }))

        with self.assertRaises(CustomerIOException):
            self.cio.add_device(customer_id="", device_id="device_4", platform="ios")
    
    def test_device_call_has_device_id(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_5", "platform": "ios"}}
        }))

        with self.assertRaises(CustomerIOException):
            self.cio.add_device(customer_id=1, device_id="", platform="ios")

    def test_device_delete_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'DELETE',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices/device_1',
            'body': {}
        }))

        self.cio.delete_device(customer_id=1, device_id="device_1")
        with self.assertRaises(TypeError):
            self.cio.delete_device(random_attr="some_value")
    
    def test_suppress_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/suppress',
            'body': {},
        }))

        self.cio.suppress(customer_id=1)

        with self.assertRaises(CustomerIOException):
            self.cio.suppress(None)

    def test_unsuppress_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/unsuppress',
            'body': {},
        }))

        self.cio.unsuppress(customer_id=1)

        with self.assertRaises(CustomerIOException):
            self.cio.unsuppress(None)


    @unittest.skipIf(sys.version_info.major > 2, "python2 specific test case")
    def test_sanitize_py2(self):
        data_in = dict(dt=datetime.fromtimestamp(1234567890))
        data_out = self.cio._sanitize(data_in)
        self.assertEqual(data_out, dict(dt=1234567890))


    @unittest.skipIf(sys.version_info.major < 3, "python3 specific test case")
    def test_sanitize_py3(self):
        from datetime import timezone
        data_in = dict(dt=datetime(2009, 2, 13, 23, 31, 30, 0, timezone.utc))
        data_out = self.cio._sanitize(data_in)
        self.assertEqual(data_out, dict(dt=1234567890))
class TestCustomerIO(HTTPSTestCase):
    '''Starts server which the client connects to in the following tests'''
    def setUp(self):
        self.cio = CustomerIO(
            site_id='siteid',
            api_key='apikey',
            host=self.server.server_address[0],
            port=self.server.server_port,
            retries=5,
            backoff_factor=0)

        # do not verify the ssl certificate as it is self signed
        # should only be done for tests
        self.cio.http.verify = False

    def _check_request(self, resp, rq, *args, **kwargs):
        request = resp.request
        body = request.body.decode('utf-8') if isinstance(request.body, bytes) else request.body
        self.assertEqual(request.method, rq['method'])
        self.assertEqual(json.loads(body), rq['body'])
        self.assertEqual(request.headers['Authorization'], rq['authorization'])
        self.assertEqual(request.headers['Content-Type'], rq['content_type'])
        self.assertEqual(int(request.headers['Content-Length']), len(json.dumps(rq['body'])))
        self.assertTrue(request.url.endswith(rq['url_suffix']),
            'url: {} expected suffix: {}'.format(request.url, rq['url_suffix']))


    def test_client_connection_handling(self):
        retries = self.cio.retries
        # should not raise exception as i should be less than retries and 
        # therefore the last request should return a valid response
        for i in range(retries):
            self.cio.identify(i, fail_count=i)

        # should raise expection as we get invalid responses for all retries
        with self.assertRaises(CustomerIOException):
            self.cio.identify(retries, fail_count=retries)


    def test_identify_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1',
            'body': {"name": "john", "email": "*****@*****.**"},
        }))

        self.cio.identify(id=1, name='john', email='*****@*****.**')

        with self.assertRaises(TypeError):
            self.cio.identify(random_attr="some_value")


    def test_track_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/events',
            'body': {"data": {"email": "*****@*****.**"}, "name": "sign_up"},
        }))

        self.cio.track(customer_id=1, name='sign_up', email='*****@*****.**')

        with self.assertRaises(TypeError):
            self.cio.track(random_attr="some_value")


    def test_pageview_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/events',
            'body': {"data": {"referer": "category_1"}, "type": "page", "name": "product_1"},
        }))

        self.cio.pageview(customer_id=1, page='product_1', referer='category_1')

        with self.assertRaises(TypeError):
            self.cio.pageview(random_attr="some_value")


    def test_delete_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'DELETE',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1',
            'body': {},
        }))

        self.cio.delete(customer_id=1)

        with self.assertRaises(TypeError):
            self.cio.delete(random_attr="some_value")


    def test_backfill_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/events',
            'body': {"timestamp": 1234567890, "data": {"email": "*****@*****.**"}, "name": "signup"},
        }))

        self.cio.backfill(customer_id=1, name='signup', timestamp=1234567890, email='*****@*****.**')

        with self.assertRaises(TypeError):
            self.cio.backfill(random_attr="some_value")

    def test_base_url(self):
        test_cases = [
            # host, port, prefix, result
            (None, None, None, 'https://track.customer.io/api/v1'),
            (None, None, 'v2', 'https://track.customer.io/v2'),
            (None, None, '/v2/', 'https://track.customer.io/v2'),
            ('sub.domain.com', 1337, '/v2/', 'https://sub.domain.com:1337/v2'),
            ('/sub.domain.com/', 1337, '/v2/', 'https://sub.domain.com:1337/v2'),
            ('http://sub.domain.com/', 1337, '/v2/', 'https://sub.domain.com:1337/v2'),
        ]

        for host, port, prefix, result in test_cases:
            cio = CustomerIO(host=host, port=port, url_prefix=prefix)
            self.assertEqual(cio.base_url, result)


    def test_device_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_1", "platform":"ios"}}
        }))

        self.cio.add_device(customer_id=1, device_id="device_1", platform="ios")
        with self.assertRaises(TypeError):
            self.cio.add_device(random_attr="some_value")

    def test_device_call_last_used(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_2", "platform": "android", "last_used": 1234567890}}
        }))

        self.cio.add_device(customer_id=1, device_id="device_2", platform="android", last_used=1234567890)

    def test_device_call_valid_platform(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_3", "platform": "notsupported"}}
        }))

        with self.assertRaises(CustomerIOException):
            self.cio.add_device(customer_id=1, device_id="device_3", platform=None)
   
    def test_device_call_has_customer_id(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_4", "platform": "ios"}}
        }))

        with self.assertRaises(CustomerIOException):
            self.cio.add_device(customer_id="", device_id="device_4", platform="ios")
    
    def test_device_call_has_device_id(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'PUT',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices',
            'body': {"device": {"id": "device_5", "platform": "ios"}}
        }))

        with self.assertRaises(CustomerIOException):
            self.cio.add_device(customer_id=1, device_id="", platform="ios")

    def test_device_delete_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'DELETE',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/devices/device_1',
            'body': {}
        }))

        self.cio.delete_device(customer_id=1, device_id="device_1")
        with self.assertRaises(TypeError):
            self.cio.delete_device(random_attr="some_value")
    
    def test_suppress_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/suppress',
            'body': {},
        }))

        self.cio.suppress(customer_id=1)

        with self.assertRaises(CustomerIOException):
            self.cio.suppress(None)

    def test_unsuppress_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/customers/1/unsuppress',
            'body': {},
        }))

        self.cio.unsuppress(customer_id=1)

        with self.assertRaises(CustomerIOException):
            self.cio.unsuppress(None)

    def test_add_to_segment_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/segments/1/add_customers',
            'body': {'ids': ['1','2','3']},
        }))

        self.cio.add_to_segment(segment_id=1, customer_ids=[1,2,3])

        with self.assertRaises(CustomerIOException):
            self.cio.add_to_segment(None, None)

        with self.assertRaises(CustomerIOException):
            self.cio.add_to_segment(segment_id=1, customer_ids=False)

        with self.assertRaises(CustomerIOException):
            self.cio.add_to_segment(segment_id=1, customer_ids=[False,True,False])

    def test_remove_from_segment_call(self):
        self.cio.http.hooks=dict(response=partial(self._check_request, rq={
            'method': 'POST',
            'authorization': _basic_auth_str('siteid', 'apikey'),
            'content_type': 'application/json',
            'url_suffix': '/segments/1/remove_customers',
            'body': {'ids': ['1','2','3']},
        }))

        self.cio.remove_from_segment(segment_id=1, customer_ids=[1,2,3])

        with self.assertRaises(CustomerIOException):
            self.cio.remove_from_segment(None, None)

        with self.assertRaises(CustomerIOException):
            self.cio.add_to_segment(segment_id=1, customer_ids=False)

        with self.assertRaises(CustomerIOException):
            self.cio.add_to_segment(segment_id=1, customer_ids=[False,True,False])


    @unittest.skipIf(sys.version_info.major > 2, "python2 specific test case")
    def test_sanitize_py2(self):
        data_in = dict(dt=datetime.fromtimestamp(1234567890))
        data_out = self.cio._sanitize(data_in)
        self.assertEqual(data_out, dict(dt=1234567890))


    @unittest.skipIf(sys.version_info.major < 3, "python3 specific test case")
    def test_sanitize_py3(self):
        from datetime import timezone
        data_in = dict(dt=datetime(2009, 2, 13, 23, 31, 30, 0, timezone.utc))
        data_out = self.cio._sanitize(data_in)
        self.assertEqual(data_out, dict(dt=1234567890))