-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Base implementation of Async CustomerIO client.
- Loading branch information
Showing
24 changed files
with
2,222 additions
and
3 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,24 @@ | ||
[run] | ||
source = | ||
./async_customerio | ||
omit = | ||
# omit tests | ||
*/tests/* | ||
*/__init__.py | ||
|
||
[report] | ||
# Regexes for lines to exclude from consideration | ||
exclude_lines = | ||
# Don't complain if tests don't hit defensive assertion code: | ||
raise AssertionError | ||
raise NotImplementedError | ||
|
||
pragma: no cover | ||
Should not reach here | ||
def __repr__ | ||
raise NotImplementedError | ||
except ImportError | ||
|
||
# Don't complain if non-runnable code isn't run: | ||
if 0: | ||
if __name__ == .__main__.: |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
[flake8] | ||
max-line-length = 120 | ||
max-complexity = 10 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,3 +127,5 @@ dmypy.json | |
|
||
# Pyre type checker | ||
.pyre/ | ||
|
||
.idea |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
[mypy] | ||
python_version = 3.8 | ||
follow_imports = skip | ||
ignore_missing_imports = True |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,38 @@ | ||
exclude: '^tests' | ||
repos: | ||
- repo: https://github.com/pre-commit/pre-commit-hooks | ||
rev: v4.1.0 | ||
hooks: | ||
# forgotten debugger imports like pdb | ||
- id: debug-statements | ||
# merge cruft like '<<<<<<< ' | ||
- id: check-merge-conflict | ||
- id: trailing-whitespace | ||
- id: end-of-file-fixer | ||
- id: check-yaml | ||
|
||
- repo: https://github.com/psf/black | ||
rev: 22.3.0 | ||
hooks: | ||
- id: black | ||
language_version: python3.8 | ||
args: [--line-length=120, --skip-string-normalization] | ||
|
||
- repo: https://gitlab.com/pycqa/flake8 | ||
rev: 4.0.1 | ||
hooks: | ||
- id: flake8 | ||
|
||
- repo: https://github.com/pycqa/isort | ||
rev: 5.10.1 | ||
hooks: | ||
- id: isort | ||
stages: [commit] | ||
|
||
- repo: https://github.com/pre-commit/mirrors-mypy | ||
rev: v0.950 | ||
hooks: | ||
- id: mypy | ||
args: [--no-error-summary, --hide-error-codes, --follow-imports=skip] | ||
files: ^async_customerio/ | ||
additional_dependencies: [types-setuptools] |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1 +1,5 @@ | ||
# Changelog | ||
|
||
## 0.1.0 | ||
|
||
* First release on PyPI. |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,83 @@ | ||
BOLD := \033[1m | ||
RESET := \033[0m | ||
|
||
.DEFAULT: help | ||
|
||
.PHONY: help | ||
help: | ||
@echo "$(BOLD)CLI$(RESET)" | ||
@echo "" | ||
@echo "$(BOLD)make install$(RESET)" | ||
@echo " install all requirements" | ||
@echo "" | ||
@echo "$(BOLD)make update$(RESET)" | ||
@echo " update all requirements" | ||
@echo "" | ||
@echo "$(BOLD)make setup_dev$(RESET)" | ||
@echo " install all requirements and setup for development" | ||
@echo "" | ||
@echo "$(BOLD)make test$(RESET)" | ||
@echo " run tests" | ||
@echo "" | ||
@echo "$(BOLD)make mypy$(RESET)" | ||
@echo " run static type checker (mypy)" | ||
@echo "" | ||
@echo "$(BOLD)make clean$(RESET)" | ||
@echo " clean trash like *.pyc files" | ||
@echo "" | ||
@echo "$(BOLD)make install_pre_commit$(RESET)" | ||
@echo " install pre_commit hook for git, " | ||
@echo " so that linters will check up code before every commit" | ||
@echo "" | ||
@echo "$(BOLD)make pre_commit$(RESET)" | ||
@echo " run linters check up" | ||
@echo "" | ||
|
||
.PHONY: install | ||
install: | ||
@echo "$(BOLD)Installing package$(RESET)" | ||
@poetry config virtualenvs.create false | ||
@poetry install --only main | ||
@echo "$(BOLD)Done!$(RESET)" | ||
|
||
.PHONY: update | ||
update: | ||
@echo "$(BOLD)Updating package and dependencies$(RESET)" | ||
@poetry update | ||
@echo "$(BOLD)Done!$(RESET)" | ||
|
||
.PHONY: setup_dev | ||
setup_dev: | ||
@echo "$(BOLD)DEV setup$(RESET)" | ||
@poetry install --no-root | ||
@echo "$(BOLD)Done!$(RESET)" | ||
|
||
.PHONY: clean | ||
clean: | ||
@echo "$(BOLD)Cleaning up repository$(RESET)" | ||
@find . -name \*.pyc -delete | ||
@echo "$(BOLD)Done!$(RESET)" | ||
|
||
.PHONY: test | ||
test: setup_dev | ||
@echo "$(BOLD)Running tests$(RESET)" | ||
@poetry run pytest --maxfail=2 ${ARGS} | ||
@echo "$(BOLD)Done!$(RESET)" | ||
|
||
.PHONY: mypy | ||
mypy: setup_dev | ||
@echo "$(BOLD)Running static type checker (mypy)$(RESET)" | ||
@poetry run mypy --no-error-summary --hide-error-codes --follow-imports=skip async_customerio | ||
@echo "$(BOLD)Done!$(RESET)" | ||
|
||
.PHONY: install_pre_commit | ||
install_pre_commit: | ||
@echo "$(BOLD)Add pre-commit hook for git$(RESET)" | ||
@pre-commit install | ||
@echo "$(BOLD)Done!$(RESET)" | ||
|
||
.PHONY: pre_commit | ||
pre_commit: | ||
@echo "$(BOLD)Run pre-commit$(RESET)" | ||
@pre-commit run -a | ||
@echo "$(BOLD)Done!$(RESET)" |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -1,2 +1,34 @@ | ||
# async-customerio | ||
The lightweight client to interaction with CustomerIO in async fashion. | ||
# async-customerio is a lightweight asynchronous client to interact with CustomerIO | ||
|
||
[![PyPI download total](https://img.shields.io/pypi/dt/async-customerio.svg)](https://pypi.python.org/pypi/async-customerio/) | ||
[![PyPI download month](https://img.shields.io/pypi/dm/async-customerio.svg)](https://pypi.python.org/pypi/async-customerio/) | ||
[![PyPI version fury.io](https://badge.fury.io/py/async-customerio.svg)](https://pypi.python.org/pypi/async-customerio/) | ||
[![PyPI license](https://img.shields.io/pypi/l/async-customerio.svg)](https://pypi.python.org/pypi/async-customerio/) | ||
[![PyPI pyversions](https://img.shields.io/pypi/pyversions/async-customerio.svg)](https://pypi.python.org/pypi/async-customerio/) | ||
[![GitHub Workflow Status for CI](https://img.shields.io/github/workflow/status/healthjoy/async-customerio/CI?label=CI&logo=github)](https://github.com/healthjoy/async-customerio/actions?query=workflow%3ACI) | ||
[![Codacy coverage](https://img.shields.io/codacy/coverage/b6a59cdf5ca64eab9104928d4f9bbb97?logo=codacy)](https://app.codacy.com/gh/healthjoy/async-customerio/dashboard) | ||
|
||
|
||
* Free software: MIT license | ||
* Requires: Python 3.7+ | ||
|
||
## Features | ||
|
||
* | ||
|
||
## Installation | ||
```shell script | ||
$ pip install async-customerio | ||
``` | ||
|
||
## Getting started | ||
TBD... | ||
|
||
## License | ||
|
||
``async-customerio`` is offered under the MIT license. | ||
|
||
## Source code | ||
|
||
The latest developer version is available in a GitHub repository: | ||
[https://github.com/healthjoy/async-customerio](https://github.com/healthjoy/async-customerio) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,11 @@ | ||
import logging | ||
|
||
from async_customerio.api import AsyncAPIClient, SendEmailRequest # noqa | ||
from async_customerio.errors import AsyncCustomerIOError # noqa | ||
from async_customerio.regions import Regions # noqa | ||
from async_customerio.track import AsyncCustomerIO # noqa | ||
|
||
|
||
root_logger = logging.getLogger("async_customerio") | ||
if root_logger.level == logging.NOTSET: | ||
root_logger.setLevel(logging.WARN) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,134 @@ | ||
""" | ||
Implements the client that interacts with Customer.io"s App API using app keys. | ||
""" | ||
import base64 | ||
import typing as t | ||
|
||
from async_customerio.client_base import AsyncClientBase | ||
from async_customerio.errors import AsyncCustomerIOError | ||
from async_customerio.regions import Region, Regions | ||
from async_customerio.utils import join_url | ||
|
||
|
||
class SendEmailRequest: | ||
"""An object with all the options available for triggering a transactional message""" | ||
|
||
def __init__( | ||
self, | ||
transactional_message_id: t.Union[str, int] = None, | ||
to: str = None, | ||
identifiers=None, | ||
_from: str = None, | ||
headers=None, | ||
reply_to: str = None, | ||
bcc=None, | ||
subject: str = None, | ||
preheader=None, | ||
body=None, | ||
plaintext_body: str = None, | ||
amp_body=None, | ||
fake_bcc=None, | ||
disable_message_retention: bool = None, | ||
send_to_unsubscribed: bool = None, | ||
tracked: bool = None, | ||
queue_draft=None, | ||
message_data=None, | ||
attachments: t.Dict[str, str] = None, | ||
): | ||
|
||
self.transactional_message_id = transactional_message_id | ||
self.to = to | ||
self.identifiers = identifiers | ||
self._from = _from | ||
self.headers = headers | ||
self.reply_to = reply_to | ||
self.bcc = bcc | ||
self.subject = subject | ||
self.preheader = preheader | ||
self.body = body | ||
self.plaintext_body = plaintext_body | ||
self.amp_body = amp_body | ||
self.fake_bcc = fake_bcc | ||
self.disable_message_retention = disable_message_retention | ||
self.send_to_unsubscribed = send_to_unsubscribed | ||
self.tracked = tracked | ||
self.queue_draft = queue_draft | ||
self.message_data = message_data | ||
self.attachments = attachments | ||
|
||
def attach(self, name: str, content: str, encode: bool = True) -> None: | ||
"""Helper method to add base64 encode the attachments""" | ||
if not self.attachments: | ||
self.attachments = {} | ||
|
||
if self.attachments.get(name, None): | ||
raise AsyncCustomerIOError("attachment {name} already exists".format(name=name)) | ||
|
||
if encode: | ||
if isinstance(content, str): | ||
content = base64.b64encode(content.encode("utf-8")).decode() | ||
else: | ||
content = base64.b64encode(content).decode() | ||
|
||
self.attachments[name] = content | ||
|
||
def to_dict(self): | ||
"""Build a request payload from the object""" | ||
field_map = dict( | ||
# `from` is reserved keyword hence the object has the field | ||
# `_from` but in the request payload we map it to `from` | ||
_from="from", | ||
# field name is the same as the payload field name | ||
transactional_message_id="transactional_message_id", | ||
to="to", | ||
identifiers="identifiers", | ||
headers="headers", | ||
reply_to="reply_to", | ||
bcc="bcc", | ||
subject="subject", | ||
preheader="preheader", | ||
body="body", | ||
plaintext_body="plaintext_body", | ||
amp_body="amp_body", | ||
fake_bcc="fake_bcc", | ||
disable_message_retention="disable_message_retention", | ||
send_to_unsubscribed="send_to_unsubscribed", | ||
tracked="tracked", | ||
queue_draft="queue_draft", | ||
message_data="message_data", | ||
attachments="attachments", | ||
) | ||
|
||
data = {} | ||
for field, name in field_map.items(): | ||
value = getattr(self, field, None) | ||
if value is not None: | ||
data[name] = value | ||
|
||
return data | ||
|
||
|
||
class AsyncAPIClient(AsyncClientBase): | ||
API_PREFIX = "/v1" | ||
SEND_EMAIL_ENDPOINT = "/send/email" | ||
|
||
def __init__( | ||
self, key: str, url: t.Optional[str] = None, region: Region = Regions.US, retries: int = 3, timeout: int = 10 | ||
): | ||
if not isinstance(region, Region): | ||
raise AsyncCustomerIOError("invalid region provided") | ||
|
||
self.key = key | ||
self.base_url = url or "https://{host}".format(host=region.api_host) | ||
super().__init__(retries=retries, timeout=timeout) | ||
|
||
async def send_email(self, request: SendEmailRequest) -> dict: | ||
if not isinstance(request, SendEmailRequest): | ||
raise AsyncCustomerIOError("invalid request provided") | ||
|
||
return await self.send_request( | ||
"POST", | ||
join_url(self.base_url, self.API_PREFIX, self.SEND_EMAIL_ENDPOINT), | ||
json_payload=request.to_dict(), | ||
headers={"Authorization": "Bearer {key}".format(key=self.key)}, | ||
) |
Oops, something went wrong.