From 248ebdb2bbc07f502be190157ec05aeb0d6d1b83 Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Wed, 11 Jun 2025 23:16:59 -0400 Subject: [PATCH 01/15] transactional sms --- customerio/__init__.py | 2 +- customerio/api.py | 53 ++++++++++++++++++++++++++++++++++++++++++ tests/test_api.py | 18 +++++++++++++- 3 files changed, 71 insertions(+), 2 deletions(-) diff --git a/customerio/__init__.py b/customerio/__init__.py index 919f5dd..ee707cb 100644 --- a/customerio/__init__.py +++ b/customerio/__init__.py @@ -2,5 +2,5 @@ from customerio.client_base import CustomerIOException from customerio.track import CustomerIO -from customerio.api import APIClient, SendEmailRequest, SendPushRequest +from customerio.api import APIClient, SendEmailRequest, SendPushRequest, SendSMSRequest from customerio.regions import Regions diff --git a/customerio/api.py b/customerio/api.py index 3b4f156..e2ba140 100644 --- a/customerio/api.py +++ b/customerio/api.py @@ -27,6 +27,12 @@ def send_push(self, request): request = request._to_dict() resp = self.send_request('POST', self.url + "/v1/send/push", request) return json.loads(resp) + + def send_sms(self, request): + if isinstance(request, SendSMSRequest): + request = request._to_dict() + resp = self.send_request('POST', self.url + "/v1/send/sms", request) + return json.loads(resp) # builds the session. def _build_session(self): @@ -211,3 +217,50 @@ def _to_dict(self): data[name] = value return data + +class SendSMSRequest(object): + '''An object with all the options avaiable for triggering a transactional push message''' + def __init__(self, + transactional_message_id=None, + to=None, + identifiers=None, + disable_message_retention=None, + send_to_unsubscribed=None, + queue_draft=None, + message_data=None, + send_at=None, + language=None, + ): + + self.transactional_message_id = transactional_message_id + self.to = to + self.identifiers = identifiers + self.disable_message_retention = disable_message_retention + self.send_to_unsubscribed = send_to_unsubscribed + self.queue_draft = queue_draft + self.message_data = message_data + self.send_at = send_at + self.language = language + + def _to_dict(self): + '''Build a request payload from the object''' + field_map = dict( + # field name is the same as the payload field name + transactional_message_id="transactional_message_id", + to="to", + identifiers="identifiers", + disable_message_retention="disable_message_retention", + send_to_unsubscribed="send_to_unsubscribed", + queue_draft="queue_draft", + message_data="message_data", + send_at="send_at", + language="language", + ) + + data = {} + for field, name in field_map.items(): + value = getattr(self, field, None) + if value is not None: + data[name] = value + + return data diff --git a/tests/test_api.py b/tests/test_api.py index 1c9ab8e..f9a6a3d 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -5,7 +5,7 @@ import sys import unittest -from customerio import APIClient, SendEmailRequest, SendPushRequest, Regions, CustomerIOException +from customerio import APIClient, SendEmailRequest, SendPushRequest, SendSMSRequest, Regions, CustomerIOException from customerio.__version__ import __version__ as ClientVersion from tests.server import HTTPSTestCase @@ -95,5 +95,21 @@ def test_send_push(self): self.client.send_push(push) + def test_send_sms(self): + self.client.http.hooks = dict(response=partial(self._check_request, rq={ + 'method': 'POST', + 'authorization': "Bearer app_api_key", + 'content_type': 'application/json', + 'url_suffix': '/v1/send/sms', + 'body': {"identifiers": {"id":"customer_1"}, "transactional_message_id": 100} + })) + + push = SendSMSRequest( + identifiers={"id":"customer_1"}, + transactional_message_id=100, + ) + + self.client.send_sms(push) + if __name__ == '__main__': unittest.main() From a1cca1b99f8eecb07d7310011873af9b28783f68 Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:19:09 -0400 Subject: [PATCH 02/15] naming --- tests/test_api.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/tests/test_api.py b/tests/test_api.py index f9a6a3d..2bd7903 100644 --- a/tests/test_api.py +++ b/tests/test_api.py @@ -104,12 +104,12 @@ def test_send_sms(self): 'body': {"identifiers": {"id":"customer_1"}, "transactional_message_id": 100} })) - push = SendSMSRequest( + sms = SendSMSRequest( identifiers={"id":"customer_1"}, transactional_message_id=100, ) - self.client.send_sms(push) + self.client.send_sms(sms) if __name__ == '__main__': unittest.main() From d3f0f0e70e78784dcd1d0f6050b2503fb44bf53d Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:21:28 -0400 Subject: [PATCH 03/15] workflow fix --- .github/workflows/main.yml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 7cd369d..35002fa 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -4,14 +4,14 @@ on: [ push ] jobs: build: - runs-on: ubuntu-20.04 + runs-on: ubuntu-latest strategy: matrix: python: [ '3.6', '3.7', '3.8','3.9' ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v4 - name: Set up Python - uses: actions/setup-python@v2 + uses: actions/setup-python@v5.6.0 with: python-version: ${{ matrix.python }} - name: install dependencies for the minor version From a086f85dfcd2159a1dcdb661cfdadfcf94b19944 Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:23:12 -0400 Subject: [PATCH 04/15] matrix --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 35002fa..f841f28 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [ '3.6', '3.7', '3.8','3.9' ] + python: [ '3.11.*', '3.12.*','3.13.*','3.14.*', 'latest' ] steps: - uses: actions/checkout@v4 - name: Set up Python From 3ab852d20841f87ee268db211a41927584a6dcfe Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:24:16 -0400 Subject: [PATCH 05/15] .x --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index f841f28..c6e52d4 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [ '3.11.*', '3.12.*','3.13.*','3.14.*', 'latest' ] + python: [ '3.11.x', '3.12.x','3.13.x','3.14.x', 'latest' ] steps: - uses: actions/checkout@v4 - name: Set up Python From 1989ff651a420e9b23e66ab7ef2c1dda2d5ca76f Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:24:29 -0400 Subject: [PATCH 06/15] no latest --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index c6e52d4..2b3ab8b 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [ '3.11.x', '3.12.x','3.13.x','3.14.x', 'latest' ] + python: [ '3.11.x', '3.12.x','3.13.x','3.14.x' ] steps: - uses: actions/checkout@v4 - name: Set up Python From fcd1c8c70cc3e3d42013d459688a1ee4c092ce33 Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:26:19 -0400 Subject: [PATCH 07/15] try again --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 2b3ab8b..358d01e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [ '3.11.x', '3.12.x','3.13.x','3.14.x' ] + python: [ '3.10.x', '3.11.x', '3.12.x', '3.13.x', 'stable' ] steps: - uses: actions/checkout@v4 - name: Set up Python From d8c68f3be04722f401196c7a9fa9a3efd624e01b Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:26:58 -0400 Subject: [PATCH 08/15] no metanames for python I guess --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 358d01e..5ebcbbe 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [ '3.10.x', '3.11.x', '3.12.x', '3.13.x', 'stable' ] + python: [ '3.10.x', '3.11.x', '3.12.x', '3.13.x' ] steps: - uses: actions/checkout@v4 - name: Set up Python From db5bc0f6d3c2f17bee29fa7e674a34dc81757703 Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 14:40:37 -0400 Subject: [PATCH 09/15] back down --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5ebcbbe..5196391 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -7,7 +7,7 @@ jobs: runs-on: ubuntu-latest strategy: matrix: - python: [ '3.10.x', '3.11.x', '3.12.x', '3.13.x' ] + python: [ '3.6.x', '3.7.x', '3.8.x', '3.9.x' ] steps: - uses: actions/checkout@v4 - name: Set up Python From 62dc690949ec95b380d92ec2038419335226df47 Mon Sep 17 00:00:00 2001 From: Adam Leclerc Date: Fri, 13 Jun 2025 15:59:40 -0300 Subject: [PATCH 10/15] Fix workflow --- .github/workflows/main.yml | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 5196391..0912a32 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -6,8 +6,9 @@ jobs: build: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python: [ '3.6.x', '3.7.x', '3.8.x', '3.9.x' ] + python: [ '3.9', '3.10', '3.11', '3.12' ] steps: - uses: actions/checkout@v4 - name: Set up Python From 01b7c4a6091283d8280c6477ab751221681c3b37 Mon Sep 17 00:00:00 2001 From: Adam Leclerc Date: Fri, 13 Jun 2025 16:03:53 -0300 Subject: [PATCH 11/15] Udpdate requests --- .github/workflows/main.yml | 2 +- requirements.txt | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 0912a32..b45939e 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,7 +19,7 @@ jobs: run: | python -m venv venv . venv/bin/activate - export version=$(grep -v '^$' ${GITHUB_WORKSPACE}/requirements.txt | grep requests | awk -F'=' '{print $2}' ) + export version=$(grep -v '^$' ${GITHUB_WORKSPACE}/requirements.txt | grep requests | awk -F'>=' '{print $2}' ) pip install requests==$(echo $version) - name: run tests run: | diff --git a/requirements.txt b/requirements.txt index 1480256..39aae79 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1 @@ -requests>=2.20.0 \ No newline at end of file +requests>=2.28.0 \ No newline at end of file From f1edd2db787bab74f836f7825f1b100298d8bc94 Mon Sep 17 00:00:00 2001 From: Adam Leclerc Date: Fri, 13 Jun 2025 16:06:06 -0300 Subject: [PATCH 12/15] Upgrade CI to Python 3.9-3.12 on ubuntu-latest with Python 3.12 compatibility fixes --- .github/workflows/main.yml | 3 +-- requirements.txt | 3 ++- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index b45939e..3ee57c2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -19,8 +19,7 @@ jobs: run: | python -m venv venv . venv/bin/activate - export version=$(grep -v '^$' ${GITHUB_WORKSPACE}/requirements.txt | grep requests | awk -F'>=' '{print $2}' ) - pip install requests==$(echo $version) + pip install -r requirements.txt - name: run tests run: | . venv/bin/activate diff --git a/requirements.txt b/requirements.txt index 39aae79..0c7f68f 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1 +1,2 @@ -requests>=2.28.0 \ No newline at end of file +requests>=2.31.0 +urllib3>=2.0.0 \ No newline at end of file From 80989b85d45012849aabc4b24c896c8ad7ef2e0a Mon Sep 17 00:00:00 2001 From: Adam Leclerc Date: Fri, 13 Jun 2025 16:07:57 -0300 Subject: [PATCH 13/15] Fix Python 3.12 SSL compatibility in test server - replace deprecated ssl.wrap_socket with modern ssl.SSLContext --- tests/server.py | 21 +++++++++------------ 1 file changed, 9 insertions(+), 12 deletions(-) diff --git a/tests/server.py b/tests/server.py index 06e91f2..5fdf2cc 100644 --- a/tests/server.py +++ b/tests/server.py @@ -3,7 +3,6 @@ except ImportError: from http.server import BaseHTTPRequestHandler, HTTPServer -from functools import wraps from random import randint import json import ssl @@ -11,12 +10,11 @@ import threading import unittest -def sslwrap(func): - @wraps(func) - def bar(*args, **kw): - kw['ssl_version'] = ssl.PROTOCOL_SSLv23 - return func(*args, **kw) - return bar +def create_ssl_context(): + """Create SSL context for Python 3.12+ compatibility""" + context = ssl.SSLContext(ssl.PROTOCOL_TLS_SERVER) + context.minimum_version = ssl.TLSVersion.TLSv1_2 + return context request_counts = dict() @@ -75,12 +73,11 @@ class HTTPSTestCase(unittest.TestCase): def setUpClass(cls): # create a server cls.server = HTTPServer(("localhost", 0), Handler) - # hack needed to setup ssl server - ssl.wrap_socket = sslwrap(ssl.wrap_socket) + # create SSL context for Python 3.12+ compatibility + context = create_ssl_context() + context.load_cert_chain('./tests/server.pem') # upgrade to https - cls.server.socket = ssl.wrap_socket(cls.server.socket, - certfile='./tests/server.pem', - server_side=True) + cls.server.socket = context.wrap_socket(cls.server.socket, server_side=True) # start server instance in new thread cls.server_thread = threading.Thread(target=cls.server.serve_forever) cls.server_thread.start() From f2118e1044db3547be445ef66f6def5c1dbc066c Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 15:19:54 -0400 Subject: [PATCH 14/15] can we get to 14 --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 3ee57c2..ae85ca2 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - python: [ '3.9', '3.10', '3.11', '3.12' ] + python: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14' ] steps: - uses: actions/checkout@v4 - name: Set up Python From 5fa9df0ac977b17f428dc91f92697eeba1f59ee4 Mon Sep 17 00:00:00 2001 From: Stephen Young Date: Fri, 13 Jun 2025 15:20:35 -0400 Subject: [PATCH 15/15] 3.13 only --- .github/workflows/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index ae85ca2..2abd4f7 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -8,7 +8,7 @@ jobs: strategy: fail-fast: false matrix: - python: [ '3.9', '3.10', '3.11', '3.12', '3.13', '3.14' ] + python: [ '3.9', '3.10', '3.11', '3.12', '3.13' ] steps: - uses: actions/checkout@v4 - name: Set up Python