Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/publish-to-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ jobs:
with:
fetch-depth: 0

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

- name: Run Test
run: python -m unittest discover -s ./src -p 'test_*.py'
Expand Down
4 changes: 2 additions & 2 deletions .github/workflows/publish-to-test-pypi.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:
steps:
- uses: actions/checkout@v3

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

- name: Install dependencies
run: python -m pip install build setuptools wheel twine
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [ "3.7", "3.8", "3.9", "3.10", "3.11" ]
python-version: [ "3.8", "3.9", "3.10", "3.11", "3.12", "3.13" ]
steps:
- name: Checkout source code
uses: actions/checkout@v3
Expand Down
1 change: 1 addition & 0 deletions src/amplitude/constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
REVENUE_RECEIPT = "$receipt"
REVENUE_RECEIPT_SIG = "$receiptSig"
REVENUE = "$revenue"
REVENUE_CURRENCY = "$currency"
AMP_REVENUE_EVENT = "revenue_amount"

MAX_PROPERTY_KEYS = 1024
Expand Down
15 changes: 13 additions & 2 deletions src/amplitude/event.py
Original file line number Diff line number Diff line change
Expand Up @@ -145,6 +145,7 @@ def get_body(self):
"price": ["price", float],
"quantity": ["quantity", int],
"revenue": ["revenue", float],
"currency": ["currency", str],
"product_id": ["productId", str],
"revenue_type": ["revenueType", str],
"location_lat": ["location_lat", float],
Expand Down Expand Up @@ -240,6 +241,7 @@ def __init__(self, user_id: Optional[str] = None,
revenue: Optional[float] = None,
product_id: Optional[str] = None,
revenue_type: Optional[str] = None,
currency: Optional[str] = None,
location_lat: Optional[float] = None,
location_lng: Optional[float] = None,
ip: Optional[str] = None,
Expand Down Expand Up @@ -277,6 +279,7 @@ def __init__(self, user_id: Optional[str] = None,
self.revenue: Optional[str] = None
self.product_id: Optional[str] = None
self.revenue_type: Optional[str] = None
self.currency: Optional[str] = None
self.location_lat: Optional[float] = None
self.location_lng: Optional[float] = None
self.ip: Optional[str] = None
Expand Down Expand Up @@ -313,6 +316,7 @@ def __init__(self, user_id: Optional[str] = None,
self["revenue"] = revenue
self["product_id"] = product_id
self["revenue_type"] = revenue_type
self["currency"] = currency
self["location_lat"] = location_lat
self["location_lng"] = location_lng
self["ip"] = ip
Expand Down Expand Up @@ -484,6 +488,7 @@ def __init__(self, event_type: str,
revenue: Optional[float] = None,
product_id: Optional[str] = None,
revenue_type: Optional[str] = None,
currency: Optional[str] = None,
location_lat: Optional[float] = None,
location_lng: Optional[float] = None,
ip: Optional[str] = None,
Expand Down Expand Up @@ -520,6 +525,7 @@ def __init__(self, event_type: str,
revenue=revenue,
product_id=product_id,
revenue_type=revenue_type,
currency=currency,
location_lat=location_lat,
location_lng=location_lng,
ip=ip,
Expand Down Expand Up @@ -1042,7 +1048,8 @@ def __init__(self, price: float,
receipt: Optional[str] = None,
receipt_sig: Optional[str] = None,
properties: Optional[dict] = None,
revenue: Optional[float] = None):
revenue: Optional[float] = None,
currency: Optional[str] = None):
"""The constructor of the Revenue class"""
self.price: float = price
self.quantity: int = quantity
Expand All @@ -1052,6 +1059,7 @@ def __init__(self, price: float,
self.receipt_sig: Optional[str] = receipt_sig
self.properties: Optional[dict] = properties
self.revenue: Optional[float] = revenue
self.currency: Optional[str] = currency

def set_receipt(self, receipt: str, receipt_signature: str):
"""Set the receipt and signature
Expand Down Expand Up @@ -1098,7 +1106,8 @@ def get_event_properties(self):
constants.REVENUE_TYPE: self.revenue_type,
constants.REVENUE_RECEIPT: self.receipt,
constants.REVENUE_RECEIPT_SIG: self.receipt_sig,
constants.REVENUE: self.revenue})
constants.REVENUE: self.revenue,
constants.REVENUE_CURRENCY: self.currency})
return {key: value for key, value in event_properties.items() if value is not None}


Expand Down Expand Up @@ -1178,6 +1187,7 @@ def __init__(self, user_id: Optional[str] = None,
revenue: Optional[float] = None,
product_id: Optional[str] = None,
revenue_type: Optional[str] = None,
currency: Optional[str] = None,
location_lat: Optional[float] = None,
location_lng: Optional[float] = None,
ip: Optional[str] = None,
Expand Down Expand Up @@ -1219,6 +1229,7 @@ def __init__(self, user_id: Optional[str] = None,
revenue=revenue,
product_id=product_id,
revenue_type=revenue_type,
currency=currency,
location_lat=location_lat,
location_lng=location_lng,
ip=ip,
Expand Down
123 changes: 80 additions & 43 deletions src/test/test_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,15 @@ def test_amplitude_client_track_success(self):
res = Response(HttpStatus.SUCCESS)
post_method.return_value = res
events = []
self.assertion_errors = []

def callback_func(event, code, message=None):
self.assertEqual(200, code)
events.append(event.event_properties["id"])
try:
self.assertEqual(200, code)
events.append(event.event_properties["id"])
self.assertEqual('USD', event.currency)
except AssertionError as e:
self.assertion_errors.append(str(e))

self.client.configuration.callback = callback_func
for use_batch in (True, False):
Expand All @@ -42,12 +47,14 @@ def callback_func(event, code, message=None):
events.clear()
self.client.configuration.use_batch = use_batch
for i in range(25):
self.client.track(BaseEvent("test_event", "test_user_id", event_properties={"id": i}))
self.client.track(BaseEvent("test_event", "test_user_id",
event_properties={"id": i}, currency='USD'))
for flush_future in self.client.flush():
if flush_future:
flush_future.result()
self.assertEqual(25, len(events))
post_method.assert_called()
self.assertEqual(0, len(self.assertion_errors))

def test_amplitude_client_track_invalid_api_key_log_error(self):
post_method = MagicMock()
Expand Down Expand Up @@ -84,13 +91,17 @@ def test_amplitude_client_track_invalid_response_then_success_response(self):
}
success_res = Response(HttpStatus.SUCCESS)
events = []
self.assertion_errors = []

def callback_func(event, code, message=None):
if event.event_properties["id"] in (1, 2, 5, 6, 8):
self.assertEqual(400, code)
else:
self.assertEqual(200, code)
events.append((event.event_properties["id"], event.retry))
try:
if event.event_properties["id"] in (1, 2, 5, 6, 8):
self.assertEqual(400, code)
else:
self.assertEqual(200, code)
events.append((event.event_properties["id"], event.retry))
except AssertionError as e:
self.assertion_errors.append(str(e))

self.client.configuration.callback = callback_func
for use_batch in (True, False):
Expand All @@ -107,20 +118,25 @@ def callback_func(event, code, message=None):
self.assertEqual(2, post_method.call_count)
self.assertEqual([(1, 0), (2, 0), (5, 0), (6, 0), (8, 0),
(0, 1), (3, 1), (4, 1), (7, 1), (9, 1)], events)
self.assertEqual(0, len(self.assertion_errors))

def test_amplitude_client_identify_invalid_log_error_then_success(self):
post_method = MagicMock()
HttpClient.post = post_method
res = Response(HttpStatus.SUCCESS)
post_method.return_value = res
self.assertion_errors = []

def callback_func(event, code, message=None):
self.assertEqual(200, code)
self.assertTrue(isinstance(event, IdentifyEvent))
self.assertTrue("user_properties" in event)
self.assertEqual("$identify", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
try:
self.assertEqual(200, code)
self.assertTrue(isinstance(event, IdentifyEvent))
self.assertTrue("user_properties" in event)
self.assertEqual("$identify", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
except AssertionError as e:
self.assertion_errors.append(str(e))

self.client.configuration.callback = callback_func
for use_batch in (True, False):
Expand All @@ -142,21 +158,26 @@ def callback_func(event, code, message=None):
if flush_future:
flush_future.result()
post_method.assert_called_once()
self.assertEqual(0, len(self.assertion_errors))

def test_amplitude_client_group_identify_invalid_log_error_then_success(self):
post_method = MagicMock()
HttpClient.post = post_method
res = Response(HttpStatus.SUCCESS)
post_method.return_value = res
self.assertion_errors = []

def callback_func(event, code, message=None):
self.assertEqual(200, code)
self.assertTrue(isinstance(event, GroupIdentifyEvent))
self.assertTrue("group_properties" in event)
self.assertEqual("$groupidentify", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
self.assertEqual({"Sports": "Football"}, event["groups"])
try:
self.assertEqual(200, code)
self.assertTrue(isinstance(event, GroupIdentifyEvent))
self.assertTrue("group_properties" in event)
self.assertEqual("$groupidentify", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
self.assertEqual({"Sports": "Football"}, event["groups"])
except AssertionError as e:
self.assertion_errors.append(str(e))

self.client.configuration.callback = callback_func
for use_batch in (True, False):
Expand All @@ -179,22 +200,27 @@ def callback_func(event, code, message=None):
if flush_future:
flush_future.result()
post_method.assert_called_once()
self.assertEqual(0, len(self.assertion_errors))

def test_amplitude_set_group_success(self):
post_method = MagicMock()
HttpClient.post = post_method
res = Response(HttpStatus.SUCCESS)
post_method.return_value = res
self.assertion_errors = []

def callback_func(event, code, message=None):
self.assertEqual(200, code)
self.assertTrue(isinstance(event, IdentifyEvent))
self.assertTrue("groups" in event)
self.assertEqual("$identify", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
self.assertEqual({"type": ["test_group", "test_group_2"]}, event.groups)
self.assertEqual({"$set": {"type": ["test_group", "test_group_2"]}}, event.user_properties)
try:
self.assertEqual(200, code)
self.assertTrue(isinstance(event, IdentifyEvent))
self.assertTrue("groups" in event)
self.assertEqual("$identify", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
self.assertEqual({"type": ["test_group", "test_group_2"]}, event.groups)
self.assertEqual({"$set": {"type": ["test_group", "test_group_2"]}}, event.user_properties)
except AssertionError as e:
self.assertion_errors.append(str(e))

self.client.configuration.callback = callback_func
for use_batch in (True, False):
Expand All @@ -207,30 +233,35 @@ def callback_func(event, code, message=None):
if flush_future:
flush_future.result()
post_method.assert_called_once()
self.assertEqual(0, len(self.assertion_errors))

def test_amplitude_client_revenue_invalid_log_error_then_success(self):
post_method = MagicMock()
HttpClient.post = post_method
res = Response(HttpStatus.SUCCESS)
post_method.return_value = res
self.assertion_errors = []

def callback_func(event, code, message=None):
self.assertEqual(200, code)
self.assertTrue(isinstance(event, RevenueEvent))
self.assertTrue("event_properties" in event)
self.assertEqual("revenue_amount", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
self.assertEqual({'$price': 60.2, '$productId': 'P63', '$quantity': 3, '$receipt': 'A0001',
'$receiptSig': '0001A', 'other_property': 'test'},
event["event_properties"])
try:
self.assertEqual(200, code)
self.assertTrue(isinstance(event, RevenueEvent))
self.assertTrue("event_properties" in event)
self.assertEqual("revenue_amount", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
self.assertEqual({'$price': 60.2, '$productId': 'P63', '$quantity': 3, '$receipt': 'A0001',
'$currency': 'USD', '$receiptSig': '0001A', 'other_property': 'test'},
event["event_properties"])
except AssertionError as e:
self.assertion_errors.append(str(e))

self.client.configuration.callback = callback_func
for use_batch in (True, False):
with self.subTest(use_batch=use_batch):
post_method.reset_mock()
self.client.configuration.use_batch = use_batch
revenue_obj = Revenue(price="abc", quantity=3, product_id="P63", properties={"other_property": "test"})
revenue_obj = Revenue(price="abc", quantity=3, product_id="P63", currency="USD", properties={"other_property": "test"})
self.assertFalse(revenue_obj.is_valid())
with self.assertLogs("test", "ERROR") as cm:
self.client.configuration.logger = logging.getLogger("test")
Expand All @@ -244,18 +275,23 @@ def callback_func(event, code, message=None):
if flush_future:
flush_future.result()
post_method.assert_called_once()
self.assertEqual(0, len(self.assertion_errors))

def test_amplitude_client_flush_success(self):
post_method = MagicMock()
HttpClient.post = post_method
res = Response(HttpStatus.SUCCESS)
post_method.return_value = res
self.assertion_errors = []

def callback_func(event, code, message=None):
self.assertEqual(200, code)
self.assertEqual("flush_test", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
try:
self.assertEqual(200, code)
self.assertEqual("flush_test", event["event_type"])
self.assertEqual("test_user_id", event["user_id"])
self.assertEqual("test_device_id", event["device_id"])
except AssertionError as e:
self.assertion_errors.append(str(e))

self.client.configuration.callback = callback_func
for use_batch in (True, False):
Expand All @@ -272,6 +308,7 @@ def callback_func(event, code, message=None):
if flush_future:
flush_future.result()
post_method.assert_called_once()
self.assertEqual(0, len(self.assertion_errors))

def test_amplitude_add_remove_plugins_success(self):
timeline = self.client._Amplitude__timeline
Expand Down
Loading