class CustomerIOBackend(BaseBackend): template_name = 'customerevents/customerio.html' def __init__(self, SITE_ID, API_KEY, **kwargs): self.site_id = SITE_ID self.connection = CustomerIO(SITE_ID, API_KEY) super(CustomerIOBackend, self).__init__(**kwargs) def get_context(self, identity, properties, events, **kwargs): if identity.startswith('session:'): raise NotImplementedError #anonymous sessions are not implemented context = { 'identify': { 'id': identity }, 'site_id': self.site_id, 'tracks': list() } context['identify'].update(properties) context['identify'] = json.dumps(context['identify']) context.update(kwargs) for event_name, event_properties in events: context['tracks'].append((event_name, json.dumps(event_properties))) return context def send(self, identity, properties, aliases, events, request_meta): if identity.startswith('session:'): #ignore anonymous sessions return self.connection.identify(id=identity, **properties) for event_name, event_properties in events: ep = dict(event_properties) ep.update({'customer_id': identity, 'name': event_name}) self.connection.track(**ep)
def handle(self, *args, **options): cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY) for user in User.objects.all(): cio.track(customer_id=user.pk, name='Sign Up') self.stdout.write('SIGN UP {}'.format(user.username))
def post(self): name = self.request.get("name") data = json.loads(self.request.get("data")) logging.info("customer.io event %s"%(name)) cio = CustomerIO(config.CIO_SITE_ID, config.CIO_API_KEY) cio.track(data["user"]["id"], name, **data)
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 send_reminder_email(self): if self.email: cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY) cio.track( customer_id=self.email, name='app_expiring_soon', app_url=self.url, status_url= "http://launch.appsembler.com/" + reverse('deployment_detail', kwargs={'deploy_id': self.deploy_id}), remaining_minutes=self.get_remaining_minutes(), expiration_time=timezone.localtime(self.expiration_time) )
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
def send_reminder_email(self): if self.email: cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY) cio.track(customer_id=self.email, name='app_expiring_soon', app_url=self.url, status_url="http://launch.appsembler.com/" + reverse('deployment_detail', kwargs={'deploy_id': self.deploy_id}), remaining_minutes=self.get_remaining_minutes(), expiration_time=timezone.localtime(self.expiration_time))
def __init__(self, settings: SettingsType, distinct_id=None, global_event_props=None) -> None: """Initialize API connector.""" self.distinct_id = distinct_id self.events = self._resolve_events(settings.get("mixpanel.events")) self.event_properties = self._resolve_event_properties( settings.get("mixpanel.event_properties")) self.profile_properties = self._resolve_profile_properties( settings.get("mixpanel.profile_properties")) self.profile_meta_properties = self._resolve_profile_meta_properties( settings.get("mixpanel.profile_meta_properties")) use_structlog = settings.get("pyramid_heroku.structlog", False) is True consumer = self._resolve_consumer(settings.get("mixpanel.consumer"), use_structlog) if settings.get("mixpanel.token"): self.api = Mixpanel(token=settings["mixpanel.token"], consumer=consumer) else: self.api = Mixpanel(token="testing", consumer=MockedConsumer()) # nosec if global_event_props: self.global_event_props = global_event_props else: self.global_event_props = {} if (settings.get("customerio.tracking.site_id") and settings.get("customerio.tracking.api_key") and settings.get("customerio.tracking.region")): # This is here because customerio support is an install extra, # i.e. it is optional from customerio import CustomerIO from customerio import Regions if settings["customerio.tracking.region"] == "eu": region = Regions.EU elif settings["customerio.tracking.region"] == "us": region = Regions.US else: raise ValueError("Unknown customer.io region") self.cio = CustomerIO( settings["customerio.tracking.site_id"], settings["customerio.tracking.api_key"], region=region, ) else: self.cio = None
class CustomerIoIdentifyOperator(BaseOperator): """ Send Track Event to Segment for a specified user_id and event :param csv_file: a CSV (plain or zipped) path with data to be used as event properties. One of them *must* be called 'userId' (templated) :type csv_file: str :param customerio_site_id: The 'site_id' property from Customer.IO account we want to update :type customerio_site_id: str :param customerio_api_key: The 'api_key' property from Customer.IO account we want to update :type customerio_api_key: str """ template_fields = ('csv_file', 'event') ui_color = '#ffd700' @apply_defaults def __init__(self, csv_file, customerio_site_id, customerio_api_key, *args, **kwargs): super().__init__(*args, **kwargs) self.csv_file = csv_file self.cio = CustomerIO(site_id=customerio_site_id, api_key=customerio_api_key) def execute(self, context): if self.csv_file.endswith('.gz'): file_reader = gzip.open(self.csv_file, "rt", newline="") else: file_reader = open(self.csv_file, 'r') csv_reader = csv.DictReader(file_reader) for row in csv_reader: # converts a csv row into a map: header1 -> value, header2 -> value... props = dict(row) user_id = props.pop('userId', None) if user_id is None: self.log.info('No userId set in CSV row: %s >>> Skipping.', props) continue # fixing numerics types set as strings from csv clean_props = dict() for key in props: clean_props[key] = retype(props.get(key)) self.log.info('Sending identify for userId %s with properties: %s', user_id, clean_props) self.cio.identify(id=user_id, **clean_props)
def test_client_connection_handling(self): retries = 5 cio = CustomerIO( site_id="siteid", api_key="apikey", host=self.server.server_address[0], port=self.server.server_port, retries=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 xrange(retries): cio.identify(i, fail_count=i) # should raise expection as we get invalid responses for all retries with self.assertRaises(CustomerIOException): cio.identify(retries, fail_count=retries)
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 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 lambda_handler(event, context): config = ConfigParser.RawConfigParser() config.read('configcustomer.properties') conn, cursor = py_db(config.get('db-config', 'HOST'), config.get('db-config', 'USERNAME'), config.get('db-config', 'PASSWD'), config.get('db-config', 'PORT'), config.get('db-config', 'DB_NAME')) site_id = config.get('customerio-config', 'site_id') api_key = config.get('customerio-config', 'api_key') # write DB contents into csv file and inserting into customer.io queryprofileStmt = queryData(conn, cursor, config.get('query-config', 'profile')) cio = CustomerIO(site_id, api_key) customeriopostfunc.customerIOProfileAPICall(queryprofileStmt, cio) cursor.close() return "Success"
def test_client_connection_handling(self): retries = 5 cio = CustomerIO(site_id="siteid", api_key="apikey", host=self.server.server_address[0], port=self.server.server_port, retries=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 xrange(retries): cio.identify(i, fail_count=i) # should raise expection as we get invalid responses for all retries with self.assertRaises(CustomerIOException): cio.identify(retries, fail_count=retries)
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)
def __init__(self, csv_file, customerio_site_id, customerio_api_key, *args, **kwargs): super().__init__(*args, **kwargs) self.csv_file = csv_file self.cio = CustomerIO(site_id=customerio_site_id, api_key=customerio_api_key)
def deploy(self): instance = self._get_pusher_instance() instance[self.deploy_id].trigger('info_update', { 'message': "Creating a new container...", 'percent': 30 }) headers = { 'content-type': 'application/json', } # run the container ports = self.project.ports.split(' ') hostnames = self.project.hostnames.split(' ') payload = { "image": self.project.image_name, "hosts": ["/api/v1/hosts/1/"], "ports": ports, "command": "", "links": "", "memory": "", "environment": self.project.env_vars, } if "edx" in self.project.name.lower(): edx_env = [] edx_env.append("EDX_LMS_BASE=lms-{0}.demo.appsembler.com".format( self.deploy_id)) edx_env.append( "EDX_PREVIEW_LMS_BASE=lms-{0}.demo.appsembler.com".format( self.deploy_id)) edx_env.append("EDX_CMS_BASE=cms-{0}.demo.appsembler.com".format( self.deploy_id)) env_string = " ".join(edx_env) env_string = " " + env_string payload['environment'] += env_string r = requests.post( "{0}/api/v1/containers/?username={1}&api_key={2}".format( settings.SHIPYARD_HOST, settings.SHIPYARD_USER, settings.SHIPYARD_KEY), data=json.dumps(payload), headers=headers) if r.status_code == 201: # This sleep is needed to avoid problems with the API time.sleep(3) container_uri = urlparse(r.headers['location']).path self.remote_container_id = container_uri.split('/')[-2] # create the app (for dynamic routing) instance[self.deploy_id].trigger('info_update', { 'message': "Assigning an URL to the app...", 'percent': 60 }) time.sleep(2) app_ids = [] domains = [] for port, hostname in zip(ports, hostnames): domain_name = "{0}.demo.appsembler.com".format(self.deploy_id) if hostname: domain_name = "{0}-{1}".format(hostname, domain_name) domains.append(domain_name) payload = { "name": self.deploy_id, "description": "{0} for {1}".format(self.project.name, self.email), "domain_name": domain_name, "backend_port": port, "protocol": "https" if "443" in self.project.ports else "http", "containers": [container_uri] } r = requests.post( "{0}/api/v1/applications/?username={1}&api_key={2}".format( settings.SHIPYARD_HOST, settings.SHIPYARD_USER, settings.SHIPYARD_KEY), data=json.dumps(payload), headers=headers) if r.status_code == 201: app_uri = urlparse(r.headers['location']).path app_ids.append(app_uri.split('/')[-2]) self.remote_app_id = " ".join(app_ids) status = r.status_code time.sleep(1) instance[self.deploy_id].trigger('info_update', { 'message': "Getting information...", 'percent': 90 }) time.sleep(1) if status == 201: scheme = "https" if "443" in self.project.ports else "http" app_urls = [] for domain in domains: app_url = "{0}://{1}".format(scheme, domain) app_urls.append(app_url) self.url = " ".join(app_urls) self.status = 'Completed' self.launch_time = timezone.now() self.expiration_time = self.expiration_datetime() instance[self.deploy_id].trigger( 'deployment_complete', { 'app_name': self.project.name, 'message': "Deployment complete!", 'app_url': self.url, 'username': self.project.default_username, 'password': self.project.default_password }) if self.email: cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY) cio.track(customer_id=self.email, name='app_deploy_complete', app_url=self.url.replace(" ", "\n"), app_name=self.project.name, status_url="http://launcher.appsembler.com" + reverse('deployment_detail', kwargs={'deploy_id': self.deploy_id}), username=self.project.default_username, password=self.project.default_password) else: self.status = 'Failed' error_log = DeploymentErrorLog(deployment=self, http_status=status, error_log=r.text) error_log.save() instance[self.deploy_id].trigger('deployment_failed', { 'message': "Deployment failed!", }) self.save()
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 __init__(self): super(CIOApi, self).__init__() self.cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY)
def deploy(self): instance = self._get_pusher_instance() li = self._get_openshift_instance() instance[self.deploy_id].trigger('info_update', { 'message': "Creating a new app...", 'percent': 30 }) message = None log_error = False try: status, res = li.app_create( app_name=self.deploy_id, app_type=self.project.cartridges_list(), init_git_url=self.project.github_url ) data = res() except (OpenShiftException, SSLError, ValueError) as e: # workaround to be able to log errors when the deployment fails if e.__class__ == OpenShiftException: log_error = True status = 500 message = "A critical error has occured." logger.error("Critical error has occured during deployment".format(self.project.name), exc_info=True, extra={ 'user_email': self.email, 'project_name': self.project.name, } ) instance[self.deploy_id].trigger('info_update', { 'message': "Getting results...", 'percent': 60 }) if status == 201: app_url = data['data'].get('app_url') self.url = app_url self.status = 'Completed' self.launch_time = timezone.now() self.expiration_time = self.launch_time + datetime.timedelta(minutes=60) instance[self.deploy_id].trigger('deployment_complete', { 'app_name': self.project.name, 'message': "Deployment complete!", 'app_url': app_url, 'username': self.project.default_username, 'password': self.project.default_password }) if self.email: cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY) cio.track(customer_id=self.email, name='app_deploy_complete', app_url=app_url, status_url="http://launch.appsembler.com/" + reverse('deployment_detail', kwargs={'deploy_id': self.deploy_id}), username=self.project.default_username, password=self.project.default_password ) else: self.status = 'Failed' if log_error: error_log = DeploymentErrorLog(deployment=self, http_status=status, error_log=data['messages'][0]['text']) error_log.save() instance[self.deploy_id].trigger('deployment_failed', { 'message': "Deployment failed!", }) self.save()
def __init__(self, SITE_ID, API_KEY, **kwargs): self.site_id = SITE_ID self.connection = CustomerIO(SITE_ID, API_KEY) super(CustomerIOBackend, self).__init__(**kwargs)
def deploy(self): instance = self._get_pusher_instance() instance[self.deploy_id].trigger('info_update', { 'message': "Creating a new container...", 'percent': 30 }) headers = { 'content-type': 'application/json', } # run the container ports = self.project.ports.split(' ') hostnames = self.project.hostnames.split(' ') payload = { "image": self.project.image_name, "hosts": ["/api/v1/hosts/1/"], "ports": ports, "command": "", "links": "", "memory": "", "environment": self.project.env_vars, } if "edx" in self.project.name.lower(): edx_env = [] edx_env.append("EDX_LMS_BASE=lms-{0}.demo.appsembler.com".format(self.deploy_id)) edx_env.append("EDX_PREVIEW_LMS_BASE=lms-{0}.demo.appsembler.com".format(self.deploy_id)) edx_env.append("EDX_CMS_BASE=cms-{0}.demo.appsembler.com".format(self.deploy_id)) env_string = " ".join(edx_env) env_string = " " + env_string payload['environment'] += env_string r = requests.post( "{0}/api/v1/containers/?username={1}&api_key={2}".format(settings.SHIPYARD_HOST, settings.SHIPYARD_USER, settings.SHIPYARD_KEY), data=json.dumps(payload), headers=headers ) if r.status_code == 201: # This sleep is needed to avoid problems with the API time.sleep(3) container_uri = urlparse(r.headers['location']).path self.remote_container_id = container_uri.split('/')[-2] # create the app (for dynamic routing) instance[self.deploy_id].trigger('info_update', { 'message': "Assigning an URL to the app...", 'percent': 60 }) time.sleep(2) app_ids = [] domains = [] for port, hostname in zip(ports, hostnames): domain_name = "{0}.demo.appsembler.com".format(self.deploy_id) if hostname: domain_name = "{0}-{1}".format(hostname, domain_name) domains.append(domain_name) payload = { "name": self.deploy_id, "description": "{0} for {1}".format(self.project.name, self.email), "domain_name": domain_name, "backend_port": port, "protocol": "https" if "443" in self.project.ports else "http", "containers": [container_uri] } r = requests.post( "{0}/api/v1/applications/?username={1}&api_key={2}".format(settings.SHIPYARD_HOST, settings.SHIPYARD_USER, settings.SHIPYARD_KEY), data=json.dumps(payload), headers=headers ) if r.status_code == 201: app_uri = urlparse(r.headers['location']).path app_ids.append(app_uri.split('/')[-2]) self.remote_app_id = " ".join(app_ids) status = r.status_code time.sleep(1) instance[self.deploy_id].trigger('info_update', { 'message': "Getting information...", 'percent': 90 }) time.sleep(1) if status == 201: scheme = "https" if "443" in self.project.ports else "http" app_urls = [] for domain in domains: app_url = "{0}://{1}".format(scheme, domain) app_urls.append(app_url) self.url = " ".join(app_urls) self.status = 'Completed' self.launch_time = timezone.now() self.expiration_time = self.expiration_datetime() instance[self.deploy_id].trigger('deployment_complete', { 'app_name': self.project.name, 'message': "Deployment complete!", 'app_url': self.url, 'username': self.project.default_username, 'password': self.project.default_password }) if self.email: cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY) cio.track(customer_id=self.email, name='app_deploy_complete', app_url=self.url.replace(" ", "\n"), app_name=self.project.name, status_url="http://launcher.appsembler.com" + reverse('deployment_detail', kwargs={'deploy_id': self.deploy_id}), username=self.project.default_username, password=self.project.default_password ) else: self.status = 'Failed' error_log = DeploymentErrorLog(deployment=self, http_status=status, error_log=r.text) error_log.save() instance[self.deploy_id].trigger('deployment_failed', { 'message': "Deployment failed!", }) self.save()
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))
class MixpanelTrack: """Wrapper around the official `mixpanel` server-side integration for Mixpanel. You can track events and/or set people profiles. Uses https://pypi.python.org/pypi/mixpanel under the hood. Prepared as `request.mixpanel` for easy handling. """ events: Events event_properties: EventProperties global_event_props: PropertiesType profile_properties: ProfileProperties profile_meta_properties: ProfileMetaProperties @staticmethod def _resolve_events(dotted_name: t.Optional[object] = None) -> Events: """Resolve a dotted-name into an Events object.""" if not dotted_name: return Events() if not isinstance(dotted_name, str): raise ValueError( f"dotted_name must be a string, but it is: {dotted_name.__class__.__name__}" ) else: resolved = DottedNameResolver().resolve(dotted_name) if not issubclass(resolved, Events): raise ValueError( "class in dotted_name needs to be based on pyramid_mixpanel.Events" ) return resolved() @staticmethod def _resolve_event_properties( dotted_name: t.Optional[object] = None, ) -> EventProperties: """Resolve a dotted-name into an EventProperties object.""" if not dotted_name: return EventProperties() if not isinstance(dotted_name, str): raise ValueError( f"dotted_name must be a string, but it is: {dotted_name.__class__.__name__}" ) else: resolved = DottedNameResolver().resolve(dotted_name) if not issubclass(resolved, EventProperties): raise ValueError( "class in dotted_name needs to be based on pyramid_mixpanel.EventProperties" ) return resolved() @staticmethod def _resolve_profile_properties( dotted_name: t.Optional[object] = None, ) -> ProfileProperties: """Resolve a dotted-name into an ProfileProperties object.""" if not dotted_name: return ProfileProperties() if not isinstance(dotted_name, str): raise ValueError( f"dotted_name must be a string, but it is: {dotted_name.__class__.__name__}" ) else: resolved = DottedNameResolver().resolve(dotted_name) if not issubclass(resolved, ProfileProperties): raise ValueError( "class in dotted_name needs to be based on pyramid_mixpanel.ProfileProperties" ) return resolved() @staticmethod def _resolve_profile_meta_properties( dotted_name: t.Optional[object] = None, ) -> ProfileMetaProperties: """Resolve a dotted-name into an ProfileMetaProperties object.""" if not dotted_name: return ProfileMetaProperties() if not isinstance(dotted_name, str): raise ValueError( f"dotted_name must be a string, but it is: {dotted_name.__class__.__name__}" ) else: resolved = DottedNameResolver().resolve(dotted_name) if not issubclass(resolved, ProfileMetaProperties): raise ValueError( "class in dotted_name needs to be based on pyramid_mixpanel.ProfileMetaProperties" ) return resolved() @staticmethod def _resolve_consumer(dotted_name: t.Optional[object] = None, use_structlog: t.Optional[bool] = False) -> Consumer: """Resolve a dotted-name into a Consumer object.""" if not dotted_name: return PoliteBufferedConsumer(use_structlog) if not isinstance(dotted_name, str): raise ValueError( f"dotted_name must be a string, but it is: {dotted_name.__class__.__name__}" ) else: resolved = DottedNameResolver().resolve(dotted_name) if not (issubclass(resolved, Consumer) or issubclass(resolved, BufferedConsumer)): raise ValueError( "class in dotted_name needs to be based on mixpanel.(Buffered)Consumer" ) return resolved() def __init__(self, settings: SettingsType, distinct_id=None, global_event_props=None) -> None: """Initialize API connector.""" self.distinct_id = distinct_id self.events = self._resolve_events(settings.get("mixpanel.events")) self.event_properties = self._resolve_event_properties( settings.get("mixpanel.event_properties")) self.profile_properties = self._resolve_profile_properties( settings.get("mixpanel.profile_properties")) self.profile_meta_properties = self._resolve_profile_meta_properties( settings.get("mixpanel.profile_meta_properties")) use_structlog = settings.get("pyramid_heroku.structlog", False) is True consumer = self._resolve_consumer(settings.get("mixpanel.consumer"), use_structlog) if settings.get("mixpanel.token"): self.api = Mixpanel(token=settings["mixpanel.token"], consumer=consumer) else: self.api = Mixpanel(token="testing", consumer=MockedConsumer()) # nosec if global_event_props: self.global_event_props = global_event_props else: self.global_event_props = {} if (settings.get("customerio.tracking.site_id") and settings.get("customerio.tracking.api_key") and settings.get("customerio.tracking.region")): # This is here because customerio support is an install extra, # i.e. it is optional from customerio import CustomerIO from customerio import Regions if settings["customerio.tracking.region"] == "eu": region = Regions.EU elif settings["customerio.tracking.region"] == "us": region = Regions.US else: raise ValueError("Unknown customer.io region") self.cio = CustomerIO( settings["customerio.tracking.site_id"], settings["customerio.tracking.api_key"], region=region, ) else: self.cio = None @distinct_id_is_required def track(self, event: Event, props: t.Optional[PropertiesType] = None) -> None: """Track a Mixpanel event.""" if event not in self.events.__dict__.values(): raise ValueError(f"Event '{event}' is not a member of self.events") if props: props = {**self.global_event_props, **props} else: props = self.global_event_props for prop in props: if prop not in self.event_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.event_properties" ) self.api.track( self.distinct_id, event.name, {prop.name: value for (prop, value) in props.items()}, ) if self.cio: msg = { "customer_id": self.distinct_id, "name": event.name, **{ prop.name.replace("$", ""): value for (prop, value) in props.items() }, } if self.api._consumer.__class__ == MockedConsumer: self.api._consumer.mocked_messages.append( MockedMessage(endpoint="customer.io", msg=msg)) else: self.cio.track(**msg) @distinct_id_is_required def profile_set(self, props: PropertiesType, meta: t.Optional[PropertiesType] = None) -> None: """Set properties to a Profile. This creates a profile if one does not yet exist. Use `meta` to override are Mixpanel special properties, such as $ip. """ if not meta: meta = {} for prop in props: if prop not in self.profile_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_properties" ) for prop in meta: if prop not in self.profile_meta_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_meta_properties" ) # mixpanel and customerio expect different date formats # so we have to save the props here so we can format them # differently later on in the `if self.cio:` block customerio_props = deepcopy(props) for (prop, value) in props.items(): if isinstance(value, datetime): props[prop] = value.isoformat() self.api.people_set( self.distinct_id, {prop.name: value for (prop, value) in props.items()}, {prop.name: value for (prop, value) in meta.items()}, ) if self.cio: # customer.io expects dates in unix/epoch format for prop, value in customerio_props.items(): if isinstance(value, datetime): customerio_props[prop] = round(value.timestamp()) # customer.io expects created timestamp as `created_at` if customerio_props.get(Property("$created")): customerio_props[Property("created_at")] = customerio_props[ Property("$created")] del customerio_props[Property("$created")] msg = { "id": self.distinct_id, **{ prop.name.replace("$", ""): value for (prop, value) in customerio_props.items() }, **{ prop.name.replace("$", ""): value for (prop, value) in meta.items() }, } if self.api._consumer.__class__ == MockedConsumer: self.api._consumer.mocked_messages.append( MockedMessage(endpoint="customer.io", msg=msg)) else: self.cio.identify(**msg) @distinct_id_is_required def people_append(self, props: PropertiesType, meta: t.Optional[PropertiesType] = None) -> None: """Wrap around api.people_append to set distinct_id.""" if not meta: meta = {} for prop in props: if prop not in self.profile_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_properties" ) for prop in meta: if prop not in self.profile_meta_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_meta_properties" ) self.api.people_append( self.distinct_id, {prop.name: value for (prop, value) in props.items()}, {prop.name: value for (prop, value) in meta.items()}, ) @distinct_id_is_required def people_union(self, props: PropertiesType, meta: t.Optional[PropertiesType] = None) -> None: """Wrap around api.people_union to set properties.""" if not meta: meta = {} for prop in props: if prop not in self.profile_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_properties" ) if not isinstance(props[prop], list): raise TypeError(f"Property '{prop}' value is not a list") for prop in meta: if prop not in self.profile_meta_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_meta_properties" ) if not isinstance(meta[prop], list): raise TypeError(f"Property '{prop}' value is not a list") self.api.people_union( self.distinct_id, {prop.name: value for (prop, value) in props.items()}, {prop.name: value for (prop, value) in meta.items()}, ) @distinct_id_is_required def profile_increment(self, props: t.Dict[Property, int]) -> None: """Wrap around api.people_increment to set distinct_id.""" for prop in props: if prop not in self.profile_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_properties" ) self.api.people_increment( self.distinct_id, {prop.name: value for (prop, value) in props.items()}) @distinct_id_is_required def profile_track_charge( self, amount: int, props: t.Optional[t.Dict[Property, str]] = None) -> None: """Wrap around api.people_track_charge to set distinct_id.""" if not props: props = {} for prop in props: if prop not in self.profile_properties.__dict__.values(): raise ValueError( f"Property '{prop}' is not a member of self.profile_properties" ) self.api.people_track_charge( self.distinct_id, amount, {prop.name: value for (prop, value) in props.items()}, )
from cron.models import CronLog from django.conf import settings import analytics import time import logging from django.utils import timezone from datetime import timedelta import traceback from requests.auth import HTTPBasicAuth from django.db.models import Sum from openpyxl import Workbook from io import BytesIO import base64 from customerio import CustomerIO cio = CustomerIO(settings.CUSTOMER_SITE_ID, settings.CUSTOMER_API_KEY) wb = Workbook(encoding='utf-8') ws = wb.active ws.title = "Financial Report" analytics.identify("Admin_",{"email" : "EMAIL"}) time.sleep(1) def get_invoice_data(dic): Q = Invoice.objects.all() for (key,value) in dic.items():
""" import csv import logging from customerio import CustomerIO, CustomerIOException from config import * try: logging.basicConfig( format='%(asctime)s - %(name)s - %(levelname)s - %(message)s', level=logging.INFO, filename=LOG_FILE) except Exception as error_desc: print('Can not open logfile.\r\n', error_desc) exit(13) cio = CustomerIO(SITE_ID, API_KEY) logger = logging.getLogger(__name__) def print_progress_bar(iteration, total, prefix='', suffix='', decimals=1, length=100, fill='█', print_end="\r"): """ Call in a loop to create terminal progress bar @params: iteration - Required : current iteration (Int)
try: import sys sys.path.insert(0, '/Users/nick/dev/theconversation') except: print "could not import -- must be running on heroku" from customerio import CustomerIO import settings from lib import userdb cio = CustomerIO(settings.get('customer_io_site_id'), settings.get('customer_io_api_key')) users = userdb.get_all() for user_info in users: if user_info and 'user' in user_info.keys() and 'email_address' in user_info.keys() and user_info['email_address'] != "": cio.identify(id=user_info['user']['username'], email=user_info['email_address'], name=user_info['user']['fullname']) print "added @" + user_info['user']['username']
def deploy(self): instance = self._get_pusher_instance() li = self._get_openshift_instance() instance[self.deploy_id].trigger('info_update', { 'message': "Creating a new app...", 'percent': 30 }) message = None log_error = False try: status, res = li.app_create( app_name=self.deploy_id, app_type=self.project.cartridges_list(), init_git_url=self.project.github_url) data = res() except (OpenShiftException, SSLError, ValueError) as e: # workaround to be able to log errors when the deployment fails if e.__class__ == OpenShiftException: log_error = True status = 500 message = "A critical error has occured." logger.error("Critical error has occured during deployment".format( self.project.name), exc_info=True, extra={ 'user_email': self.email, 'project_name': self.project.name, }) instance[self.deploy_id].trigger('info_update', { 'message': "Getting results...", 'percent': 60 }) if status == 201: app_url = data['data'].get('app_url') self.url = app_url self.status = 'Completed' self.launch_time = timezone.now() self.expiration_time = self.launch_time + datetime.timedelta( minutes=60) instance[self.deploy_id].trigger( 'deployment_complete', { 'app_name': self.project.name, 'message': "Deployment complete!", 'app_url': app_url, 'username': self.project.default_username, 'password': self.project.default_password }) if self.email: cio = CustomerIO(settings.CUSTOMERIO_SITE_ID, settings.CUSTOMERIO_API_KEY) cio.track(customer_id=self.email, name='app_deploy_complete', app_url=app_url, status_url="http://launch.appsembler.com/" + reverse('deployment_detail', kwargs={'deploy_id': self.deploy_id}), username=self.project.default_username, password=self.project.default_password) else: self.status = 'Failed' if log_error: error_log = DeploymentErrorLog( deployment=self, http_status=status, error_log=data['messages'][0]['text']) error_log.save() instance[self.deploy_id].trigger('deployment_failed', { 'message': "Deployment failed!", }) self.save()
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))
def track_event(user_id, event_name, **kwargs): cio = CustomerIO(os.environ['CUSTOMER_IO_SITE_ID'], os.environ['CUSTOMER_IO_API_KEY']) cio.track(customer_id=user_id, name=event_name, **kwargs)