Giter VIP home page Giter VIP logo

customerio-python's Introduction

Power automated communication that people like to receive.

Gitpod Ready-to-Code PyPI Software License Build status PyPI - Python Version PyPI - Downloads

Customer.io Python

This module has been tested with Python 3.6, 3.7, 3.8 and 3.9.

Installing

pip install customerio

Usage

from customerio import CustomerIO, Regions
cio = CustomerIO(site_id, api_key, region=Regions.US)
cio.identify(id="5", email='[email protected]', name='Bob', plan='premium')
cio.track(customer_id="5", name='purchased')
cio.track(customer_id="5", name='purchased', price=23.45)

Instantiating customer.io object

Create an instance of the client with your Customer.io credentials.

from customerio import CustomerIO, Regions
cio = CustomerIO(site_id, api_key, region=Regions.US)

region is optional and takes one of two values—Regions.US or Regions.EU. If you do not specify your region, we assume that your account is based in the US (Regions.US). If your account is based in the EU and you do not provide the correct region (Regions.EU), we'll route requests to our EU data centers accordingly, however this may cause data to be logged in the US.

Create or update a Customer.io customer profile

cio.identify(id="5", email='[email protected]', name='Bob', plan='premium')

Only the id field is used to identify the customer here. Using an existing id with a different email (or any other attribute) will update/overwrite any pre-existing values for that field.

You can pass any keyword arguments to the identify and track methods. These kwargs will be converted to custom attributes.

See original REST documentation here

Track a custom event

cio.track(customer_id="5", name='purchased')

Track a custom event with custom data values

cio.track(customer_id="5", name='purchased', price=23.45, product="widget")

You can pass any keyword arguments to the identify and track methods. These kwargs will be converted to custom attributes.

See original REST documentation here

Backfill a custom event

from datetime import datetime, timedelta

customer_id = "5"
event_type = "purchase"

# Backfill an event one hour in the past
event_date = datetime.utcnow() - timedelta(hours=1)
cio.backfill(customer_id, event_type, event_date, price=23.45, coupon=True)

event_timestamp = 1408482633
cio.backfill(customer_id, event_type, event_timestamp, price=34.56)

event_timestamp = "1408482680"
cio.backfill(customer_id, event_type, event_timestamp, price=45.67)

Event timestamp may be passed as a datetime.datetime object, an integer or a string UNIX timestamp

Keyword arguments to backfill work the same as a call to cio.track.

See original REST documentation here

Track an anonymous event

cio.track_anonymous(anonymous_id="anon-event", name="purchased", price=23.45, product="widget")

An anonymous event is an event associated with a person you haven't identified. The event requires an anonymous_id representing the unknown person and an event name. When you identify a person, you can set their anonymous_id attribute. If event merging is turned on in your workspace, and the attribute matches the anonymous_id in one or more events that were logged within the last 30 days, we associate those events with the person.

Anonymous invite events

If you previously sent invite events, you can achieve the same functionality by sending an anonymous event with the anonymous identifier set to None. To send anonymous invites, your event must include a recipient attribute.

cio.track_anonymous(anonymous_id=None, name="invite", first_name="alex", recipient="[email protected]")

Delete a customer profile

cio.delete(customer_id="5")

Deletes the customer profile for a specified customer.

This method returns nothing. Attempts to delete non-existent customers will not raise any errors.

See original REST documentation here

You can pass any keyword arguments to the identify and track methods. These kwargs will be converted to custom attributes.

Merge duplicate customer profiles

When you merge two people, you pick a primary person and merge a secondary, duplicate person into it. The primary person remains after the merge and the secondary is deleted. This process is permanent: you cannot recover the secondary person.

For each person, you'll set the type of identifier you want to use to identify a person—one of id, email, or cio_id—and then you'll provide the corresponding identifier.

## Please import identifier types
cio.merge_customers(primary_id_type=ID,
  primary_id="[email protected]", 
  secondary_id_type=EMAIL, 
  secondary_id="[email protected]"
)

Add a device

cio.add_device(customer_id="1", device_id='device_hash', platform='ios')

Adds the device device_hash with the platform ios for a specified customer.

Supported platforms are ios and android.

Optionally, last_used can be passed in to specify the last touch of the device. Otherwise, this attribute is set by the API.

cio.add_device(customer_id="1", device_id='device_hash', platform='ios', last_used=1514764800})

This method returns nothing.

Delete a device

cio.delete_device(customer_id="1", device_id='device_hash')

Deletes the specified device for a specified customer.

This method returns nothing. Attempts to delete non-existent devices will not raise any errors.

Suppress a customer

cio.suppress(customer_id="1")

Suppresses the specified customer. They will be deleted from Customer.io, and we will ignore all further attempts to identify or track activity for the suppressed customer ID

See REST documentation here

Unsuppress a customer

cio.unsuppress(customer_id="1")

Unsuppresses the specified customer. We will remove the supplied id from our suppression list and start accepting new identify and track calls for the customer as normal

See REST documentation here

Send Transactional Messages

To use the Transactional API, instantiate the Customer.io object using an app key and create a request object for your message type.

Email

SendEmailRequest requires:

  • transactional_message_id: the ID of the transactional message you want to send, or the body, _from, and subject of a new message.
  • to: the email address of your recipients
  • an identifiers object containing the id of your recipient. If the id does not exist, Customer.io will create it.
  • a message_data object containing properties that you want reference in your message using Liquid.
  • You can also send attachments with your message. Use attach to encode attachments.

Use send_email referencing your request to send a transactional message. Learn more about transactional messages and SendEmailRequest properties.

from customerio import APIClient, Regions, SendEmailRequest
client = APIClient("your API key", region=Regions.US)

request = SendEmailRequest(
  to="[email protected]",
  _from="[email protected]",
  transactional_message_id="3",
  message_data={
    "name": "person",
    "items": [
      {
        "name": "shoes",
        "price": "59.99",
      },
    ]
  },
  identifiers={
    "id": "2",
  }
)

with open("receipt.pdf", "rb") as f:
  request.attach('receipt.pdf', f.read())

response = client.send_email(request)
print(response)

Push

SendPushRequest requires:

  • transactional_message_id: the ID of the transactional push message you want to send.
  • an identifiers object containing the id or email of your recipient. If the profile does not exist, Customer.io will create it.

Use send_push referencing your request to send a transactional message. Learn more about transactional messages and SendPushRequest properties.

from customerio import APIClient, Regions, SendPushRequest
client = APIClient("your API key", region=Regions.US)

request = SendPushRequest(
  transactional_message_id="3",
  message_data={
    "name": "person",
    "items": [
      {
        "name": "shoes",
        "price": "59.99",
      },
    ]
  },
  identifiers={
    "id": "2",
  }
)

response = client.send_push(request)
print(response)

Notes

  • The Customer.io Python SDK depends on the Requests library which includes urllib3 as a transitive dependency. The Requests library leverages connection pooling defined in urllib3. urllib3 only attempts to retry invocations of HTTP methods which are understood to be idempotent (See: Retry.DEFAULT_ALLOWED_METHODS). Since the POST method is not considered to be idempotent, any invocations which require POST are not retried.

  • It is possible to have the Customer.io Python SDK effectively disable connection pooling by passing a named initialization parameter use_connection_pooling to either the APIClient class or CustomerIO class. Setting this parameter to False (default: True) causes the Session to be initialized and discarded after each request. If you are experiencing integration issues where the cause is reported as Connection Reset by Peer, this may correct the problem. It will, however, impose a slight performance penalty as the TCP connection set-up and tear-down will now occur for each request.

Usage Example Disabling Connection Pooling

from customerio import CustomerIO, Regions
cio = CustomerIO(site_id, api_key, region=Regions.US, use_connection_pooling=False)

Running tests

Changes to the library can be tested by running make test from the parent directory.

Thanks!

customerio-python's People

Contributors

clabland avatar darrellsilver avatar dimier avatar djanhjo avatar ebutleriv avatar glosier avatar hownowstephen avatar jakeaustwick avatar jatinn avatar joepurdy avatar joeshaw avatar jrallison avatar mationai avatar nichediver avatar niparis avatar orblivion avatar pavanmachavolu avatar rtpg avatar ryanwitt avatar scraswell avatar sud0n1m avatar swastik avatar zbyte64 avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

customerio-python's Issues

unable to pip install?

Collecting customerio==0.2 (from -r requirements.txt (line 2))
  Downloading customerio-0.2.tar.gz
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-to2j9f0e/customerio/setup.py", line 3, in <module>
        from customerio import get_version
      File "/tmp/pip-build-to2j9f0e/customerio/customerio/__init__.py", line 6, in <module>
        from requests import Session
    ImportError: No module named 'requests'

Error sending timezone-aware timestamps

In my Django 1.8 project configured to be timezone aware (USE_TZ=True in Django settings), I have a created_at timestamp field on my user model which is a standard Django DateTimeField. I get the following error when attempting to send this field over to CustomerIO via the python client:

  File ".../customerio/__init__.py", line 71, in identify
    self.send_request('PUT', url, kwargs)
  File ".../customerio/__init__.py", line 50, in send_request
    data = json.dumps(self._sanitize(data), cls=self.json_encoder)
  File ".../customerio/__init__.py", line 121, in _sanitize
    data[k] = self._timedelta_to_timestamp(v - datetime(1970, 1, 1))
TypeError: can't subtract offset-naive and offset-aware datetimes

Should the python client support this?

Calls with NaN values are processed by customerio-python, but are not reflected in the interface

if one of the arguments of the identify method has value math.nan, no exception is given by the API (ie: returns 200), however, the customer is not saved/updated.

Example:

import math
from customerio import CustomerIO

cio = CustomerIO(site_id, api_key)
data = {'id': 1, 'email': '[email protected]', 'value': math.nan}
cio.identify(**data}

Expected: a user with email [email protected] should appear in the interface.
Actual: code runs fine, nothing happens in interface

Missing step for trusting your TLS certificates

Certificates do not seem to be trusted on at least some Debian versions:

python:3.10-slim-bookworm (and bullseye)

apt update && apt install -y ca-certificates

request = SendEmailRequest(
    to="[email protected]",
    _from="[email protected]",
    subject="subj",
    message_data={
      "customer_first_name": "test",
    },
    body="sample body",
    identifiers={
      "email": "[email protected]",
    }
)

results in:

WARNING:urllib3.connectionpool:Retrying (Retry(total=0, connect=None, read=None, redirect=None, status=None)) after connection broken by 'SSLError(SSLCertVerificationError(1, '[SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1007)'))': /v1/send/email
Traceback (most recent call last):
  File "/Users/rui.dacostabeopen.com/.local/share/virtualenvs/test_customer_io-y84Bf5Xb/lib/python3.10/site-packages/urllib3/connectionpool.py", line 467, in _make_request
    self._validate_conn(conn)
  File "/Users/rui.dacostabeopen.com/.local/share/virtualenvs/test_customer_io-y84Bf5Xb/lib/python3.10/site-packages/urllib3/connectionpool.py", line 1096, in _validate_conn
    conn.connect()
  File "/Users/rui.dacostabeopen.com/.local/share/virtualenvs/test_customer_io-y84Bf5Xb/lib/python3.10/site-packages/urllib3/connection.py", line 642, in connect
    sock_and_verified = _ssl_wrap_socket_and_match_hostname(
  File "/Users/rui.dacostabeopen.com/.local/share/virtualenvs/test_customer_io-y84Bf5Xb/lib/python3.10/site-packages/urllib3/connection.py", line 782, in _ssl_wrap_socket_and_match_hostname
    ssl_sock = ssl_wrap_socket(
  File "/Users/rui.dacostabeopen.com/.local/share/virtualenvs/test_customer_io-y84Bf5Xb/lib/python3.10/site-packages/urllib3/util/ssl_.py", line 470, in ssl_wrap_socket
    ssl_sock = _ssl_wrap_socket_impl(sock, context, tls_in_tls, server_hostname)
  File "/Users/rui.dacostabeopen.com/.local/share/virtualenvs/test_customer_io-y84Bf5Xb/lib/python3.10/site-packages/urllib3/util/ssl_.py", line 514, in _ssl_wrap_socket_impl
    return ssl_context.wrap_socket(sock, server_hostname=server_hostname)
  File "/Users/rui.dacostabeopen.com/.pyenv/versions/3.10.13/lib/python3.10/ssl.py", line 513, in wrap_socket
    return self.sslsocket_class._create(
  File "/Users/rui.dacostabeopen.com/.pyenv/versions/3.10.13/lib/python3.10/ssl.py", line 1104, in _create
    self.do_handshake()
  File "/Users/rui.dacostabeopen.com/.pyenv/versions/3.10.13/lib/python3.10/ssl.py", line 1375, in do_handshake
    self._sslobj.do_handshake()
ssl.SSLCertVerificationError: [SSL: CERTIFICATE_VERIFY_FAILED] certificate verify failed: unable to get local issuer certificate (_ssl.c:1007)

running
openssl s_client -showcerts -connect api-eu.customer.io:443
confirms:

...
verify error:num=20:unable to get local issuer certificate
...

API Rate Limits

The API docs state:

"When sending data to Customer.io, Behavioral Tracking API calls should be limited to 30 requests per second. This limit applies to both active data integrations, as well as historical backfill scripts."

What are your thoughts about updating the send_request() method to automatically queue tasks to obey this limit? I'd be happy to write the code.

Is backfilling idempotent?

For instance, if I run a script twice to integrate with an in-house database, and it backfills the same event, will it count as two events with the same timestamp, or will it be deduplicated? Either way, it would be great for the documentation to make this clear!

unclosed socket ResourceWarning

Using version 0.2 with Django with ./manage.py runserver:

ResourceWarning: unclosed <ssl.SSLSocket fd=8, family=AddressFamily.AF_INET, type=2049, proto=6, laddr=('10.0.2.15', 38464), raddr=('35.163.46.99', 443)>
  CustomerIOTrackSignUp(request.user).track(

returning responses rather than None

Hey

Just wondering the justification for returning None rather than some data structure when calling `CustomerIO.identify?

Interest:
after calling io.identify, I would like to be returned a requests.Response or custom data structure that includes cio_id so I can record it in my db. Moreover, with a response object, it would be a way of validating a 200/201 response

Cheers

Problem with pip v19.3.1

Hi. I have detect the next error:

With pip versoin 19.3.1, my project don't detect the module, throw "ModuleNotFoundError: No module named 'customerio'".

I need to revert pip version to 9.0.1 to work.

Failed to receive valid reponse after 3 retries

Hi All,

Been using customerio-python for a fair bit in production environment. From time-time-time we would get the following error:
Cannot send email for xxxxx message id xx ** Failed to receive valid response after 3 retries.**
Check system status at http://status.customer.io.

The same payload/user would go through at another instance.

Python 3.8

Has anyone encountered this before?

Would a simple remedy be adding a retry wrapper on top?

cio.track returns non for events

from customerio import CustomerIO, Regions
site_id=xxx
api_key=yyy
cio = CustomerIO(site_id, api_key, region=Regions.EU)
cio.track(customer_id=id='cid_xxx', name='event') #tried Event, Lesson Completed

returns none, where I can see the events in customer.io UI

Is there a good way to determine that a Trigger did not send (e.g. due to a "missing variable" error)?

The best idea that I can come up with is:
(1) send the (email, in my case) trigger with a unique data parameter
(2) query https://beta-api.customer.io/v1/api/campaigns/{{campaign_id}}/messages, searching for that unique parameter
(3) checking failure_message

It would be very nice if CustomerIO would return a unique ID from the Trigger POST, but afaik that never happens.

So, does anyone have a good way of determining the status of the Trigger, post-send?

ResourceWarning unclosed ssl.SSLSocket

I'm getting a Python ResourceWarning when following the examples from the Customer.io Python docs:

ResourceWarning: unclosed <ssl.SSLSocket fd=7, family=AddressFamily.AF_INET, type=SocketKind.SOCK_STREAM, proto=6, laddr=('172.21.X.X', 57030), raddr=('35.227.X.X', 443)>
ResourceWarning: Enable tracemalloc to get the object allocation traceback

I believe the issue is that customerio.APIClient never closes the Requests Session object (see https://stackoverflow.com/questions/48160728/resourcewarning-unclosed-socket-in-python-3-unit-test). I couldn't find a documented method on APIClient to close the session either.

As a workaround, we can reach into the APIClient object and call .http.close() directly, but that seems brittle:

client = customerio.APIClient(CUSTOMERIO_APP_API_KEY)
request = customerio.SendEmailRequest(...)
client.send_email(request)
client.http.close()  # <-- manually close the session

Here's an idea of how this could work in the API client. It'd be nice if APIClient supported the Context Manager protocol, so that we could use it like this:

with customerio.APIClient(...) as client:
    client.send_email(...)

This relates to #27 and #31.

BadStatusLine: '' exceptions

When trying to identify customers, BadStatusLine exceptions are being thrown.

File "/usr/lib/python2.7/httplib.py", line 379, in _read_status
    raise BadStatusLine(line)
BadStatusLine: ''

This is an intermittent problem and I'm currently not able to reproduce it in a shell, but will keep digging.

The exception is bubbling out from this line.

Has anyone else experienced this? Would it make sense to add some error checking for this condition? Give the exception, it would appear the API is returning a malformed response.

Traceback in `identify()` call

Over the last few days, I've seen quite a few of these happening in our production environment. What's shakin' bacon?

Traceback (most recent call last):
  File "/home/projectteam/.virtualenvs/project/local/lib/python2.7/site-packages/celery/app/trace.py", line 240, in trace_task
    R = retval = fun(*args, **kwargs)
  File "/home/projectteam/.virtualenvs/project/local/lib/python2.7/site-packages/celery/app/trace.py", line 438, in __protected_call__
    return self.run(*args, **kwargs)
  File "/home/projectteam/live/profiles/tasks.py", line 124, in post_signup_actions
    CIOEventUserSignup.identify(user, last_updated=date_format(timezone.now(), "U"))
  File "/home/projectteam/live/util/emails.py", line 203, in identify
    cls.cio.identify(customer_id, **data)
  File "/home/projectteam/.virtualenvs/project/local/lib/python2.7/site-packages/customerio/__init__.py", line 102, in identify
    self.send_request('PUT', url, kwargs)
  File "/home/projectteam/.virtualenvs/project/local/lib/python2.7/site-packages/customerio/__init__.py", line 90, in send_request
    raise CustomerIOException(message)
CustomerIOException: Failed to receive valid reponse after 3 retries.
Check system status at http://status.customer.io.
Last caught exception -- <type 'exceptions.AttributeError'>: 'NoneType' object has no attribute 'makefile'

Version 1.4 Release Notes?

Hello, I noticed you released version 1.4 on pypi, but there are no release notes listed in CHANGES.md. Can you please add them?

Remote end drops connections periodically

Here is how we initialize the customerio client and call the API:

    cio_client = CustomerIO(
        "site-id", "api-key"
    )
    cio_client.track(
        user_id,
        "event-name",
        **event_data,
    )

Periodically, on both our cloud environments (dev and prod), we see the following exceptions in the logs after trying to make a tracking API call:

  | 2023-02-24T08:28:59.027-05:00 | Traceback (most recent call last):
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 703, in urlopen
  | 2023-02-24T08:28:59.027-05:00 | httplib_response = self._make_request(
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 449, in _make_request
  | 2023-02-24T08:28:59.027-05:00 | six.raise_from(e, None)
  | 2023-02-24T08:28:59.027-05:00 | File "<string>", line 3, in raise_from
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 444, in _make_request
  | 2023-02-24T08:28:59.027-05:00 | httplib_response = conn.getresponse()
  | 2023-02-24T08:28:59.027-05:00 | File "/usr/local/lib/python3.10/http/client.py", line 1374, in getresponse
  | 2023-02-24T08:28:59.027-05:00 | response.begin()
  | 2023-02-24T08:28:59.027-05:00 | File "/usr/local/lib/python3.10/http/client.py", line 318, in begin
  | 2023-02-24T08:28:59.027-05:00 | version, status, reason = self._read_status()
  | 2023-02-24T08:28:59.027-05:00 | File "/usr/local/lib/python3.10/http/client.py", line 287, in _read_status
  | 2023-02-24T08:28:59.027-05:00 | raise RemoteDisconnected("Remote end closed connection without"
  | 2023-02-24T08:28:59.027-05:00 | http.client.RemoteDisconnected: Remote end closed connection without response
  | 2023-02-24T08:28:59.027-05:00 | During handling of the above exception, another exception occurred:
  | 2023-02-24T08:28:59.027-05:00 | Traceback (most recent call last):
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/requests/adapters.py", line 489, in send
  | 2023-02-24T08:28:59.027-05:00 | resp = conn.urlopen(
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 787, in urlopen
  | 2023-02-24T08:28:59.027-05:00 | retries = retries.increment(
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/util/retry.py", line 550, in increment
  | 2023-02-24T08:28:59.027-05:00 | raise six.reraise(type(error), error, _stacktrace)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/packages/six.py", line 769, in reraise
  | 2023-02-24T08:28:59.027-05:00 | raise value.with_traceback(tb)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 703, in urlopen
  | 2023-02-24T08:28:59.027-05:00 | httplib_response = self._make_request(
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 449, in _make_request
  | 2023-02-24T08:28:59.027-05:00 | six.raise_from(e, None)
  | 2023-02-24T08:28:59.027-05:00 | File "<string>", line 3, in raise_from
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/urllib3/connectionpool.py", line 444, in _make_request
  | 2023-02-24T08:28:59.027-05:00 | httplib_response = conn.getresponse()
  | 2023-02-24T08:28:59.027-05:00 | File "/usr/local/lib/python3.10/http/client.py", line 1374, in getresponse
  | 2023-02-24T08:28:59.027-05:00 | response.begin()
  | 2023-02-24T08:28:59.027-05:00 | File "/usr/local/lib/python3.10/http/client.py", line 318, in begin
  | 2023-02-24T08:28:59.027-05:00 | version, status, reason = self._read_status()
  | 2023-02-24T08:28:59.027-05:00 | File "/usr/local/lib/python3.10/http/client.py", line 287, in _read_status
  | 2023-02-24T08:28:59.027-05:00 | raise RemoteDisconnected("Remote end closed connection without"
  | 2023-02-24T08:28:59.027-05:00 | urllib3.exceptions.ProtocolError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
  | 2023-02-24T08:28:59.027-05:00 | During handling of the above exception, another exception occurred:
  | 2023-02-24T08:28:59.027-05:00 | Traceback (most recent call last):
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/customerio/client_base.py", line 37, in send_request
  | 2023-02-24T08:28:59.027-05:00 | response = self.http.request(
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/requests/sessions.py", line 587, in request
  | 2023-02-24T08:28:59.027-05:00 | resp = self.send(prep, **send_kwargs)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/requests/sessions.py", line 701, in send
  | 2023-02-24T08:28:59.027-05:00 | r = adapter.send(request, **kwargs)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/requests/adapters.py", line 547, in send
  | 2023-02-24T08:28:59.027-05:00 | raise ConnectionError(err, request=request)
  | 2023-02-24T08:28:59.027-05:00 | requests.exceptions.ConnectionError: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
  | 2023-02-24T08:28:59.027-05:00 | During handling of the above exception, another exception occurred:
  | 2023-02-24T08:28:59.027-05:00 | Traceback (most recent call last):
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/flask/app.py", line 2525, in wsgi_app
  | 2023-02-24T08:28:59.027-05:00 | response = self.full_dispatch_request()
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/flask/app.py", line 1822, in full_dispatch_request
  | 2023-02-24T08:28:59.027-05:00 | rv = self.handle_user_exception(e)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/flask/app.py", line 1820, in full_dispatch_request
  | 2023-02-24T08:28:59.027-05:00 | rv = self.dispatch_request()
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/flask/app.py", line 1796, in dispatch_request
  | 2023-02-24T08:28:59.027-05:00 | return self.ensure_sync(self.view_functions[rule.endpoint])(**view_args)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/app/webhooks/posts/http_handlers.py", line 233, in notify_followers
  | 2023-02-24T08:28:59.027-05:00 | self.send_customerio_new_post_event(customerio_user_ids, post_data)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/app/webhooks/posts/http_handlers.py", line 265, in send_customerio_new_post_event
  | 2023-02-24T08:28:59.027-05:00 | self.cio_client.track(
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/customerio/track.py", line 83, in track
  | 2023-02-24T08:28:59.027-05:00 | self.send_request('POST', url, post_data)
  | 2023-02-24T08:28:59.027-05:00 | File "/app/.venv/lib/python3.10/site-packages/customerio/client_base.py", line 52, in send_request
  | 2023-02-24T08:28:59.027-05:00 | raise CustomerIOException(message)
  | 2023-02-24T08:28:59.027-05:00 | customerio.client_base.CustomerIOException: Failed to receive valid response after 3 retries.
  | 2023-02-24T08:28:59.027-05:00 | Check system status at http://status.customer.io.
  | 2023-02-24T08:28:59.027-05:00 | Last caught exception -- <class 'requests.exceptions.ConnectionError'>: ('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))
  | 2023-02-24T08:28:59.027-05:00 |  

<br class="Apple-interchange-newline">

We also see the following warning when nothing happens (no API calls to customerio being made, the client is just idle):

02-24-2023 08:22:21 AM WARNING:urllib3.connectionpool:Retrying (Retry(total=2, connect=None, read=None, redirect=None, status=None)) after connection broken by 'ProtocolError('Connection aborted.', RemoteDisconnected('Remote end closed connection without response'))': /api/v1/customers/db230f25-8f45-4d0f-b0dc-8e17ae251d7e

Looking at the customerio client code seems like the problem might be with the connection pooling logic via session reusing. Could you please help us with this?

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.