diff --git a/.bumpversion.cfg b/.bumpversion.cfg index 6ad311a9..facbe377 100644 --- a/.bumpversion.cfg +++ b/.bumpversion.cfg @@ -1,5 +1,5 @@ [bumpversion] -current_version = 0.0.2 +current_version = 1.0.0 parse = (?P\d+)\.(?P\d+)\.(?P\d+) serialize = {major}.{minor}.{patch} diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 00000000..c2a97a51 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,19 @@ +name: Lint + +on: [push] + +jobs: + lint: + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v6 + + - uses: actions/setup-python@v6 + with: + python-version: "3.10" + cache: 'pip' + cache-dependency-path: setup.py + + - run: pip3 install -e '.[test]' + - run: make lint-ci diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 871263d5..1f28c5c0 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -18,9 +18,9 @@ jobs: runs-on: ubuntu-latest steps: - - uses: actions/checkout@v3 + - uses: actions/checkout@v6 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v6 with: python-version: '3.x' cache: 'pip' diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index da2daf6d..adb4a249 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -7,13 +7,14 @@ jobs: runs-on: ubuntu-latest strategy: + fail-fast: false matrix: - python-version: [3.7, 3.8, 3.9] + python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13", "3.14" ] steps: - - uses: actions/checkout@v2 + - uses: actions/checkout@v6 - - uses: actions/setup-python@v4 + - uses: actions/setup-python@v6 with: python-version: ${{ matrix.python-version }} cache: 'pip' diff --git a/.gitignore b/.gitignore index 0211f3ae..ccadd9ee 100644 --- a/.gitignore +++ b/.gitignore @@ -11,3 +11,4 @@ build .idea/ .python-version venv +.venv diff --git a/.mise.toml b/.mise.toml new file mode 100644 index 00000000..826f9b46 --- /dev/null +++ b/.mise.toml @@ -0,0 +1,20 @@ +[env] +MISE_FETCH_REMOTE_VERSIONS_TIMEOUT = "30s" + +_.python.venv = { + path = ".venv", + create = true, +} + +[settings] +python.uv_venv_auto = false +python.venv_stdlib = true + +[tools] +python = "3.11" + +[tasks.test] +run = 'make install test' + +[tasks.lint] +run = 'make install lint' diff --git a/Makefile b/Makefile index 8ea2abef..0d6122af 100644 --- a/Makefile +++ b/Makefile @@ -2,8 +2,18 @@ install: pip install --edit .[test] test: - pylint --rcfile=.pylintrc --reports=y --exit-zero customerio/analytics - flake8 --max-complexity=10 --statistics customerio/analytics || true python -m unittest customerio/analytics/test/*.py -v -.PHONY: install test +lint: + pylint --rcfile=.pylintrc --reports=y --exit-zero customerio/analytics + flake8 --max-complexity=10 --statistics --exit-zero customerio/analytics + +lint-ci: + pylint --rcfile=.pylintrc --exit-zero --fail-on=E customerio/analytics + flake8 --max-complexity=10 --max-line-length=100 --statistics customerio/analytics + +clean: + rm -rf .venv + mise deps + +.PHONY: install test lint lint-ci clean diff --git a/README.md b/README.md index b21aa40c..7c2f61b4 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,4 @@ -Customer.io Data Pipelines analytics client for Python. +# Customer.io Data Pipelines analytics client for Python. ## Installation @@ -40,6 +40,6 @@ analytics.track(user_id=4, event='order_complete') The links below contain more detailed documentation on how to use this library: -* [Documentation](https://customer.io/docs/cdp/sources/connections/servers/python/) -* [Specs](https://customer.io/docs/cdp/sources/source-spec/source-events/) +* [Documentation](https://docs.customer.io/integrations/data-in/connections/servers/python/) +* [Specs](https://docs.customer.io/integrations/data-in/source-spec/incoming-data/) * [PyPi](https://pypi.org/project/customerio-cdp-analytics/) diff --git a/customerio/__init__.py b/customerio/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/customerio/analytics/client.py b/customerio/analytics/client.py index 4185d776..c5387fb3 100644 --- a/customerio/analytics/client.py +++ b/customerio/analytics/client.py @@ -1,19 +1,18 @@ -from datetime import datetime -from uuid import uuid4 -import logging -import numbers import atexit import json +import logging +import numbers +import queue +from datetime import datetime +from uuid import uuid4 from dateutil.tz import tzutc -from customerio.analytics.utils import guess_timezone, clean from customerio.analytics.consumer import Consumer, MAX_MSG_SIZE from customerio.analytics.request import post, DatetimeSerializer +from customerio.analytics.utils import guess_timezone, clean from customerio.analytics.version import VERSION -import queue - ID_TYPES = (numbers.Number, str) @@ -269,7 +268,9 @@ def _enqueue(self, msg): # Check message size. msg_size = len(json.dumps(msg, cls=DatetimeSerializer).encode()) if msg_size > MAX_MSG_SIZE: - raise RuntimeError('Message exceeds %skb limit. (%s)', str(int(MAX_MSG_SIZE / 1024)), str(msg)) + raise RuntimeError( + 'Message exceeds %dkb limit. (%s)' % (MAX_MSG_SIZE // 1024, msg) + ) # if send is False, return msg as if it was successfully queued if not self.send: diff --git a/customerio/analytics/consumer.py b/customerio/analytics/consumer.py index d9b05479..8f478b54 100644 --- a/customerio/analytics/consumer.py +++ b/customerio/analytics/consumer.py @@ -1,13 +1,13 @@ +import json import logging +from queue import Empty from threading import Thread -import monotonic + import backoff -import json +import monotonic from customerio.analytics.request import post, APIError, DatetimeSerializer -from queue import Empty - MAX_MSG_SIZE = 32 << 10 # Our servers only accept batches less than 500KB. Here limit is set slightly @@ -73,7 +73,7 @@ def upload(self): # mark items as acknowledged from queue for _ in batch: self.queue.task_done() - return success + return success def next(self): """Return the next batch of items to upload.""" diff --git a/customerio/analytics/request.py b/customerio/analytics/request.py index 0b92f4cf..5b0eb99a 100644 --- a/customerio/analytics/request.py +++ b/customerio/analytics/request.py @@ -56,8 +56,8 @@ def post(write_key, host=None, gzip=False, timeout=15, proxies=None, **kwargs): payload = res.json() log.debug('received response: %s', payload) raise APIError(res.status_code, payload['code'], payload['message']) - except ValueError: - raise APIError(res.status_code, 'unknown', res.text) + except ValueError as exc: + raise APIError(res.status_code, 'unknown', res.text) from exc class APIError(Exception): diff --git a/customerio/analytics/test/consumer.py b/customerio/analytics/test/consumer.py index 5ef02fc5..057f6338 100644 --- a/customerio/analytics/test/consumer.py +++ b/customerio/analytics/test/consumer.py @@ -1,7 +1,8 @@ +import json +import time import unittest + import mock -import time -import json try: from queue import Queue diff --git a/customerio/analytics/version.py b/customerio/analytics/version.py index bc784a3b..1e5a6058 100644 --- a/customerio/analytics/version.py +++ b/customerio/analytics/version.py @@ -1 +1 @@ -VERSION = '0.0.2' +VERSION = '1.0.0' diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 00000000..86364777 --- /dev/null +++ b/requirements.txt @@ -0,0 +1,7 @@ +backoff==2.2.1 +flake8==3.7.9 +monotonic==1.6 +mock==2.0.0 +pylint==3.3.3 +python-dateutil==2.8.2 +requests>=2.32.4 \ No newline at end of file diff --git a/setup.py b/setup.py index 143a9ab5..e2463395 100644 --- a/setup.py +++ b/setup.py @@ -16,15 +16,15 @@ ''' install_requires = [ - "requests~=2.7", - "monotonic~=1.5", - "backoff~=2.1", - "python-dateutil~=2.2" + "requests>=2.32.4", + "monotonic~=1.6", + "backoff~=2.2", + "python-dateutil~=2.8" ] tests_require = [ "mock==2.0.0", - "pylint==2.8.0", + "pylint>=3.2.0", "flake8==3.7.9", ] @@ -38,7 +38,7 @@ maintainer_email='cdp@customer.io', test_suite='analytics.test.all', packages=['customerio.analytics'], - python_requires='>=3.6.0', + python_requires='>=3.8.0', license='MIT License', install_requires=install_requires, extras_require={ @@ -52,9 +52,12 @@ "License :: OSI Approved :: MIT License", "Operating System :: OS Independent", "Programming Language :: Python", - "Programming Language :: Python :: 3.6", - "Programming Language :: Python :: 3.7", "Programming Language :: Python :: 3.8", "Programming Language :: Python :: 3.9", + "Programming Language :: Python :: 3.10", + "Programming Language :: Python :: 3.11", + "Programming Language :: Python :: 3.12", + "Programming Language :: Python :: 3.13", + "Programming Language :: Python :: 3.14", ], )