Skip to content

Commit

Permalink
feat: unit test for client module (#15)
Browse files Browse the repository at this point in the history
###Summary

1. Unit test for client module
2. fix a bug of initialize response processor
3. error handling for invalid api key error in threads pool

###Checklist

* [x]Does your PR title have the correct title format?
* Does your PR have a breaking change?: no
  • Loading branch information
bohan-amplitude committed Apr 16, 2022
1 parent 887d1ee commit 0cdb0b4
Show file tree
Hide file tree
Showing 17 changed files with 388 additions and 41 deletions.
51 changes: 51 additions & 0 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
name: Publish to PyPI

on:
workflow_dispatch:
inputs:
dryRun:
description: 'Do a dry run to preview instead of a real release'
required: true
default: 'true'

jobs:
authorize:
name: Authorize
runs-on: ubuntu-latest
steps:
- name: ${{ github.actor }} permission check to do a release
uses: "lannonbr/repo-permission-check-action@2.0.2"
with:
permission: "write"
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

build-n-publish:
name: Build and publish to PyPI
runs-on: ubuntu-latest
needs: [authorize]
steps:
- name: Checkout for release to PyPI
uses: actions/checkout@v3

- name: Set up Python 3.7
uses: actions/setup-python@v3
with:
python-version: 3.7

- name: Run Test
run: python -m unittest discover -s ./src -p 'test_*.py'

- name: Publish distribution PyPI --dry-run
if: ${{ github.event.inputs.dryRun == 'true'}}
run: |
python -m pip install python-semantic-release
semantic-release publish --noop
- name: Publish distribution PyPI
if: ${{ github.event.inputs.dryRun == 'false'}}
uses: relekang/python-semantic-release@master
with:
github_token: ${{ secrets.GITHUB_TOKEN }}
repository_username: __token__
repository_password: ${{ secrets.PYPI_API_TOKEN }}
34 changes: 19 additions & 15 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -19,18 +19,22 @@ jobs:
runs-on: ubuntu-latest
needs: [authorize]
steps:
- uses: actions/checkout@v3
- name: Set up Python 3.6
uses: actions/setup-python@v3
with:
python-version: 3.6
- name: Install dependencies
run: python -m pip install build setuptools wheel twine
- name: Build a binary wheel and a source tarball
run: python -m build --sdist --wheel --outdir dist/ .
- name: Publish distribution Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
- uses: actions/checkout@v3

- name: Set up Python 3.6
uses: actions/setup-python@v3
with:
python-version: 3.6

- name: Install dependencies
run: python -m pip install build setuptools wheel twine

- name: Build a binary wheel and a source tarball
run: python -m build --sdist --wheel --outdir dist/ .

- name: Publish distribution Test PyPI
uses: pypa/gh-action-pypi-publish@release/v1
with:
user: __token__
password: ${{ secrets.TEST_PYPI_API_TOKEN }}
repository_url: https://test.pypi.org/legacy/
21 changes: 21 additions & 0 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
name: Unit Test

on: [pull_request]

jobs:
test:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.6" ]
steps:
- name: Checkout source code
uses: actions/checkout@v3

- name: Set up Python ${{ matrix.python-version }}
uses: actions/setup-python@v3
with:
python-version: ${{ matrix.python-version }}

- name: Unit Test
run: python -m unittest discover -s ./src -p 'test_*.py'
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -134,3 +134,4 @@ dmypy.json
# Local system files
.DS_Store
.vscode/
buildandupload.sh
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@
The official Amplitude backend Python SDK for server-side instrumentation.

## Installation and Quick Start
Please visit the [Developer Center](https://developers.amplitude.com/docs/python) for instructions on installing and using our the SDK.
Please visit the [Developer Center](https://developers.amplitude.com/docs/python-beta) for instructions on installing and using our the SDK.

## Changelog
View the [releases here](https://github.com/amplitude/Amplitude-Python/releases).
Expand Down
1 change: 1 addition & 0 deletions examples/trackevent.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,3 +17,4 @@ def callback_fun(event, code, message):
}
client.track(event)
client.flush()
client.shutdown()
14 changes: 14 additions & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
[tool.semantic_release]
version_variable = [
"src/amplitude/constants.py:SDK_VERSION"
]
major_on_zero = true
branch = "main"
upload_to_PyPI = true
upload_to_release = true
build_command = "pip install build && python -m build"
version_source = "commit"
commit_version_number = true
commit_subject = "chore(release): Bump version to {version}"
commit_message = "chore(release): Bump version to {version}"
commit_author = "amplitude-sdk-bot <amplitude-sdk-bot@users.noreply.github.com>"
3 changes: 2 additions & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,6 @@
project_urls={
"Bug Reports": "https://github.com/amplitude/Amplitude-Python/issues",
"Source": "https://github.com/amplitude/Amplitude-Python",
"Developer Doc": "https://developers.amplitude.com/docs/python-beta"
},
)
)
10 changes: 8 additions & 2 deletions src/amplitude/client.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,17 +32,18 @@ class Amplitude:
flush(): Flush all event waiting to be sent in the buffer
add(plugin): Add the plugin object to client instance.
remove(plugin): Remove the plugin object from client instance
shutdown(): Shutdown the client instance
"""

def __init__(self, api_key: str, configuration=Config()):
def __init__(self, api_key: str, configuration: Config = Config()):
"""The constructor for the Amplitude class
Args:
api_key (str): The api key of amplitude project. Must be set properly before tracking events.
configuration (amplitude.config.Config, optional): The configuration of client instance. A new instance
with default config value will be used by default.
"""
self.configuration = configuration
self.configuration: Config = configuration
self.configuration.api_key = api_key
self.__timeline = Timeline()
self.__timeline.setup(self)
Expand Down Expand Up @@ -158,3 +159,8 @@ def remove(self, plugin: Plugin):
"""
self.__timeline.remove(plugin)
return self

def shutdown(self):
"""Shutdown the client instance, not accepting new events, flush all events in buffer"""
self.configuration.opt_out = True
self.__timeline.shutdown()
2 changes: 1 addition & 1 deletion src/amplitude/constants.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
from enum import Enum

SDK_LIBRARY = "amplitude-python"
SDK_VERSION = "0.0.2"
SDK_VERSION = "0.1.0"

EU_ZONE = "EU"
DEFAULT_ZONE = "US"
Expand Down
7 changes: 7 additions & 0 deletions src/amplitude/plugin.py
Original file line number Diff line number Diff line change
Expand Up @@ -71,6 +71,9 @@ def execute(self, event: BaseEvent) -> None:
event = self.timeline.process(event)
super().execute(event)

def shutdown(self):
self.timeline.shutdown()


class AmplitudeDestinationPlugin(DestinationPlugin):

Expand All @@ -97,6 +100,10 @@ def execute(self, event: BaseEvent) -> None:
def flush(self):
self.workers.flush()

def shutdown(self):
self.timeline.shutdown()
self.workers.stop()


class ContextPlugin(Plugin):

Expand Down
5 changes: 3 additions & 2 deletions src/amplitude/processor.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,9 @@ def __init__(self, worker):
self.configuration = worker.configuration
self.storage = worker.storage

def setup(self, configuration):
def setup(self, configuration, storage):
self.configuration = configuration
self.storage = storage

def process_response(self, res, events):
if res.status == HttpStatus.SUCCESS:
Expand Down Expand Up @@ -70,4 +71,4 @@ def callback(self, events, code, message):
self.configuration.callback(event, code, message)
event.callback(code, message)
except Exception:
self.configuration.logger.error(f"Error callback for event {event}")
self.configuration.logger.exception(f"Error callback for event {event}")
3 changes: 2 additions & 1 deletion src/amplitude/storage.py
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ def pull(self, batch_size: int) -> List[BaseEvent]:
result = self.ready_queue[:batch_size]
self.ready_queue = self.ready_queue[batch_size:]
index = 0
while index < len(self.buffer_data) and index < (batch_size - len(result)) and current_time >= self.buffer_data[index][0]:
while index < len(self.buffer_data) and index < (batch_size - len(result)) and \
current_time >= self.buffer_data[index][0]:
event = self.buffer_data[index][1]
result.append(event)
index += 1
Expand Down
10 changes: 7 additions & 3 deletions src/amplitude/timeline.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,7 @@ def flush(self):
try:
destination.flush()
except Exception:
self.logger.error(f"Error for flush events")
self.logger.exception("Error for flush events")

def process(self, event):
if self.configuration.opt_out:
Expand All @@ -67,7 +67,11 @@ def apply_plugins(self, plugin_type, event):
else:
result = plugin.execute(result)
except InvalidEventError:
self.logger.error(f"Invalid event body {event}")
self.logger.exception(f"Invalid event body {event}")
except Exception:
self.logger.error(f"Error for apply {plugin_type.name} plugin for event {event}")
self.logger.exception(f"Error for apply {plugin_type.name} plugin for event {event}")
return result

def shutdown(self):
for destination in self.plugins[PluginType.DESTINATION]:
destination.shutdown()
13 changes: 8 additions & 5 deletions src/amplitude/worker.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,8 @@
from concurrent.futures import ThreadPoolExecutor
from threading import Thread

from amplitude.exception import InvalidAPIKeyError
from amplitude.http_client import HttpClient
from amplitude import utils
from amplitude.processor import ResponseProcessor


Expand All @@ -20,7 +20,7 @@ def __init__(self):
def setup(self, configuration, storage):
self.configuration = configuration
self.storage = storage
self.response_processor.setup(configuration)
self.response_processor.setup(configuration, storage)

def start(self):
self.consumer.start()
Expand All @@ -33,13 +33,17 @@ def stop(self):

def flush(self):
events = self.storage.pull_all()
self.send(events)
if events:
self.send(events)

def send(self, events):
url = self.configuration.server_url
payload = self.get_payload(events)
res = HttpClient.post(url, payload)
self.response_processor.process_response(res, events)
try:
self.response_processor.process_response(res, events)
except InvalidAPIKeyError:
self.configuration.logger.error("Invalid API Key")

def get_payload(self, events) -> bytes:
payload_body = {
Expand All @@ -64,4 +68,3 @@ def buffer_consumer(self):
wait_time = self.storage.wait_time / 1000
if wait_time > 0:
self.storage.lock.wait(wait_time)

10 changes: 0 additions & 10 deletions src/test/client.py

This file was deleted.

Loading

0 comments on commit 0cdb0b4

Please sign in to comment.