From 24ab8548c27a8eefdd30f0490f0e1fdad9f33248 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Mon, 1 Jul 2024 17:59:00 -0700 Subject: [PATCH 1/9] Support for /identity/buckets --- ...ent.py => sample_generate_identity_map.py} | 2 +- examples/sample_get_identity_buckets.py | 35 ++++++++++++++ tests/test_identity_map_client.py | 25 ++++++++-- uid2_client/Identity_buckets_response.py | 46 +++++++++++++++++++ uid2_client/identity_map_client.py | 12 ++++- uid2_client/input_util.py | 9 ++++ 6 files changed, 124 insertions(+), 5 deletions(-) rename examples/{sample_identity_map_client.py => sample_generate_identity_map.py} (88%) create mode 100644 examples/sample_get_identity_buckets.py create mode 100644 uid2_client/Identity_buckets_response.py diff --git a/examples/sample_identity_map_client.py b/examples/sample_generate_identity_map.py similarity index 88% rename from examples/sample_identity_map_client.py rename to examples/sample_generate_identity_map.py index a0dad6d..acb2483 100644 --- a/examples/sample_identity_map_client.py +++ b/examples/sample_generate_identity_map.py @@ -7,7 +7,7 @@ # or the reason why it is unmapped def _usage(): - print('Usage: python3 sample_identity_map_client.py ... ' + print('Usage: python3 sample_generate_identity_map.py ... ' , file=sys.stderr) sys.exit(1) diff --git a/examples/sample_get_identity_buckets.py b/examples/sample_get_identity_buckets.py new file mode 100644 index 0000000..d80bbbd --- /dev/null +++ b/examples/sample_get_identity_buckets.py @@ -0,0 +1,35 @@ +import datetime +import sys + +from uid2_client import IdentityMapClient + + +# this sample client takes date time as input and generates an IdentityBucketsResponse object which contains +# a list of buckets + +def _usage(): + print('Usage: python3 sample_get_identity_buckets.py ' + , file=sys.stderr) + sys.exit(1) + + +if len(sys.argv) <= 9: + _usage() + +base_url = sys.argv[1] +api_key = sys.argv[2] +client_secret = sys.argv[3] +year = int(sys.argv[4]) +month = int(sys.argv[5]) +day = int(sys.argv[6]) +hour = int(sys.argv[7]) +minute = int(sys.argv[8]) +second = int(sys.argv[9]) + +client = IdentityMapClient(base_url, api_key, client_secret) + +identity_buckets_response = client.get_identity_buckets(datetime.datetime(year, month, day, hour, minute, second)) + +bucket = identity_buckets_response.buckets[0] +print("The bucket id of the first bucket: ", bucket.get_bucket_id()) +print("The last updated timestamp of the first bucket: ", bucket.get_last_updated()) diff --git a/tests/test_identity_map_client.py b/tests/test_identity_map_client.py index 56411fa..0d93fd5 100644 --- a/tests/test_identity_map_client.py +++ b/tests/test_identity_map_client.py @@ -1,3 +1,4 @@ +import datetime import os import unittest @@ -131,24 +132,28 @@ def test_identity_map_hashed_phones(self): self.assert_unmapped(response, "optout", hashed_opted_out_phone) - def test_identity_map_bad_url(self): + def test_identity_map_client_bad_url(self): identity_map_input = IdentityMapInput.from_emails( ["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"]) client = IdentityMapClient("https://operator-bad-url.uidapi.com", os.getenv("UID2_API_KEY"), os.getenv("UID2_SECRET_KEY")) self.assertRaises(requests.exceptions.ConnectionError, client.generate_identity_map, identity_map_input) + self.assertRaises(requests.exceptions.ConnectionError, client.get_identity_buckets, datetime.datetime.now()) - def test_identity_map_bad_api_key(self): + def test_identity_map_client_bad_api_key(self): identity_map_input = IdentityMapInput.from_emails( ["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"]) client = IdentityMapClient(os.getenv("UID2_BASE_URL"), "bad-api-key", os.getenv("UID2_SECRET_KEY")) self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map,identity_map_input) + self.assertRaises(requests.exceptions.HTTPError, client.get_identity_buckets, datetime.datetime.now()) - def test_identity_map_bad_secret(self): + def test_identity_map_client_bad_secret(self): identity_map_input = IdentityMapInput.from_emails( ["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"]) client = IdentityMapClient(os.getenv("UID2_BASE_URL"), os.getenv("UID2_API_KEY"), "wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=") self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map, identity_map_input) + self.assertRaises(requests.exceptions.HTTPError, client.get_identity_buckets, + datetime.datetime.now()) def assert_mapped(self, response, dii): mapped_identity = response.mapped_identities.get(dii) @@ -166,6 +171,20 @@ def assert_unmapped(self, response, reason, dii): mapped_identity = response.mapped_identities.get(dii) self.assertIsNone(mapped_identity) + def test_identity_buckets(self): + response = self.identity_map_client.get_identity_buckets(datetime.datetime.now() - datetime.timedelta(days=2)) + self.assertTrue(len(response.buckets) > 0) + self.assertTrue(response.is_success) + + def test_identity_buckets_empty_response(self): + response = self.identity_map_client.get_identity_buckets(datetime.datetime.now() + datetime.timedelta(days=1)) + self.assertTrue(len(response.buckets) == 0) + self.assertTrue(response.is_success) + + def test_identity_buckets_invalid_timestamp(self): + self.assertRaises(TypeError, self.identity_map_client.get_identity_buckets, + "1234567890") + if __name__ == '__main__': unittest.main() diff --git a/uid2_client/Identity_buckets_response.py b/uid2_client/Identity_buckets_response.py new file mode 100644 index 0000000..8e0ccf2 --- /dev/null +++ b/uid2_client/Identity_buckets_response.py @@ -0,0 +1,46 @@ +import json + + +class IdentityBucketsResponse: + def __init__(self, response): + self._buckets = [] + response_json = json.loads(response) + self._status = response_json["status"] + + if not self.is_success(): + raise ValueError("Got unexpected identity buckets status: " + self._status) + + body = response_json["body"] + + for bucket in body: + self._buckets.append(Bucket.from_json(bucket)) + + def is_success(self): + return self._status == "success" + + @property + def buckets(self): + return self._buckets + + @property + def status(self): + return self._status + + +class Bucket: + def __init__(self, bucket_id, last_updated): + self._bucket_id = bucket_id + self._last_updated = last_updated + + def get_bucket_id(self): + return self._bucket_id + + def get_last_updated(self): + return self._last_updated + + @staticmethod + def from_json(json_obj): + return Bucket( + json_obj.get("bucket_id"), + json_obj.get("last_updated") + ) diff --git a/uid2_client/identity_map_client.py b/uid2_client/identity_map_client.py index 7829a0c..4911c2d 100644 --- a/uid2_client/identity_map_client.py +++ b/uid2_client/identity_map_client.py @@ -1,10 +1,12 @@ import base64 import datetime as dt +import json from datetime import timezone +from .Identity_buckets_response import IdentityBucketsResponse from .identity_map_response import IdentityMapResponse -from uid2_client import auth_headers, make_v2_request, post, parse_v2_response +from uid2_client import auth_headers, make_v2_request, post, parse_v2_response, get_datetime_iso_format class IdentityMapClient: @@ -39,3 +41,11 @@ def generate_identity_map(self, identity_map_input): resp.raise_for_status() resp_body = parse_v2_response(self._client_secret, resp.text, nonce) return IdentityMapResponse(resp_body, identity_map_input) + + def get_identity_buckets(self, since_timestamp): + req, nonce = make_v2_request(self._client_secret, dt.datetime.now(tz=timezone.utc), + json.dumps({"since_timestamp": get_datetime_iso_format(since_timestamp)}).encode()) + resp = post(self._base_url, '/v2/identity/buckets', headers=auth_headers(self._api_key), data=req) + resp.raise_for_status() + resp_body = parse_v2_response(self._client_secret, resp.text, nonce) + return IdentityBucketsResponse(resp_body) diff --git a/uid2_client/input_util.py b/uid2_client/input_util.py index 6d85f63..0b1e0cc 100644 --- a/uid2_client/input_util.py +++ b/uid2_client/input_util.py @@ -1,3 +1,4 @@ +import datetime import hashlib import base64 @@ -119,3 +120,11 @@ def normalize_and_hash_phone(phone): if not is_phone_number_normalized(phone): raise ValueError("phone number is not normalized: " + phone) return get_base64_encoded_hash(phone) + + +def get_datetime_iso_format(timestamp): + if isinstance(timestamp, datetime.datetime): + return timestamp.isoformat() + else: + raise TypeError("timestamp is not in datetime format") + From 4ab20bb51068dbf5417b7e351dbe7851d5dbc8d2 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Mon, 1 Jul 2024 18:04:51 -0700 Subject: [PATCH 2/9] Handling empyty bucket list in sample usage --- examples/sample_get_identity_buckets.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/examples/sample_get_identity_buckets.py b/examples/sample_get_identity_buckets.py index d80bbbd..49cde43 100644 --- a/examples/sample_get_identity_buckets.py +++ b/examples/sample_get_identity_buckets.py @@ -30,6 +30,9 @@ def _usage(): identity_buckets_response = client.get_identity_buckets(datetime.datetime(year, month, day, hour, minute, second)) -bucket = identity_buckets_response.buckets[0] -print("The bucket id of the first bucket: ", bucket.get_bucket_id()) -print("The last updated timestamp of the first bucket: ", bucket.get_last_updated()) +if identity_buckets_response.buckets: + bucket = identity_buckets_response.buckets[0] + print("The bucket id of the first bucket: ", bucket.get_bucket_id()) + print("The last updated timestamp of the first bucket: ", bucket.get_last_updated()) +else: + print("No buckets were returned for this datetime") From 890557a2e845f22e0757c352390f1a1c2159fd72 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Tue, 2 Jul 2024 16:16:24 -0700 Subject: [PATCH 3/9] Address the comments --- examples/sample_get_identity_buckets.py | 5 +---- tests/test_identity_map_client.py | 20 ++++++++++++++----- ...sponse.py => identity_buckets_response.py} | 0 uid2_client/identity_map_client.py | 4 ++-- uid2_client/input_util.py | 10 +--------- 5 files changed, 19 insertions(+), 20 deletions(-) rename uid2_client/{Identity_buckets_response.py => identity_buckets_response.py} (100%) diff --git a/examples/sample_get_identity_buckets.py b/examples/sample_get_identity_buckets.py index 49cde43..129e99a 100644 --- a/examples/sample_get_identity_buckets.py +++ b/examples/sample_get_identity_buckets.py @@ -30,9 +30,6 @@ def _usage(): identity_buckets_response = client.get_identity_buckets(datetime.datetime(year, month, day, hour, minute, second)) -if identity_buckets_response.buckets: - bucket = identity_buckets_response.buckets[0] +for bucket in identity_buckets_response.buckets: print("The bucket id of the first bucket: ", bucket.get_bucket_id()) print("The last updated timestamp of the first bucket: ", bucket.get_last_updated()) -else: - print("No buckets were returned for this datetime") diff --git a/tests/test_identity_map_client.py b/tests/test_identity_map_client.py index 0d93fd5..609dd8b 100644 --- a/tests/test_identity_map_client.py +++ b/tests/test_identity_map_client.py @@ -135,7 +135,8 @@ def test_identity_map_hashed_phones(self): def test_identity_map_client_bad_url(self): identity_map_input = IdentityMapInput.from_emails( ["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"]) - client = IdentityMapClient("https://operator-bad-url.uidapi.com", os.getenv("UID2_API_KEY"), os.getenv("UID2_SECRET_KEY")) + client = IdentityMapClient("https://operator-bad-url.uidapi.com", os.getenv("UID2_API_KEY"), + os.getenv("UID2_SECRET_KEY")) self.assertRaises(requests.exceptions.ConnectionError, client.generate_identity_map, identity_map_input) self.assertRaises(requests.exceptions.ConnectionError, client.get_identity_buckets, datetime.datetime.now()) @@ -143,13 +144,14 @@ def test_identity_map_client_bad_api_key(self): identity_map_input = IdentityMapInput.from_emails( ["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"]) client = IdentityMapClient(os.getenv("UID2_BASE_URL"), "bad-api-key", os.getenv("UID2_SECRET_KEY")) - self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map,identity_map_input) + self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map, identity_map_input) self.assertRaises(requests.exceptions.HTTPError, client.get_identity_buckets, datetime.datetime.now()) def test_identity_map_client_bad_secret(self): identity_map_input = IdentityMapInput.from_emails( ["hopefully-not-opted-out@example.com", "somethingelse@example.com", "optout@example.com"]) - client = IdentityMapClient(os.getenv("UID2_BASE_URL"), os.getenv("UID2_API_KEY"), "wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=") + client = IdentityMapClient(os.getenv("UID2_BASE_URL"), os.getenv("UID2_API_KEY"), + "wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=") self.assertRaises(requests.exceptions.HTTPError, client.generate_identity_map, identity_map_input) self.assertRaises(requests.exceptions.HTTPError, client.get_identity_buckets, @@ -182,8 +184,16 @@ def test_identity_buckets_empty_response(self): self.assertTrue(response.is_success) def test_identity_buckets_invalid_timestamp(self): - self.assertRaises(TypeError, self.identity_map_client.get_identity_buckets, - "1234567890") + test_cases = ["1234567890", + 1234567890, + 2024.7, + "2024-7-1", + "2024-07-01T12:00:00", + [2024, 7, 1, 12, 0, 0], + None] + for timestamp in test_cases: + self.assertRaises(AttributeError, self.identity_map_client.get_identity_buckets, + timestamp) if __name__ == '__main__': diff --git a/uid2_client/Identity_buckets_response.py b/uid2_client/identity_buckets_response.py similarity index 100% rename from uid2_client/Identity_buckets_response.py rename to uid2_client/identity_buckets_response.py diff --git a/uid2_client/identity_map_client.py b/uid2_client/identity_map_client.py index 4911c2d..8a37d8a 100644 --- a/uid2_client/identity_map_client.py +++ b/uid2_client/identity_map_client.py @@ -3,7 +3,7 @@ import json from datetime import timezone -from .Identity_buckets_response import IdentityBucketsResponse +from .identity_buckets_response import IdentityBucketsResponse from .identity_map_response import IdentityMapResponse from uid2_client import auth_headers, make_v2_request, post, parse_v2_response, get_datetime_iso_format @@ -44,7 +44,7 @@ def generate_identity_map(self, identity_map_input): def get_identity_buckets(self, since_timestamp): req, nonce = make_v2_request(self._client_secret, dt.datetime.now(tz=timezone.utc), - json.dumps({"since_timestamp": get_datetime_iso_format(since_timestamp)}).encode()) + json.dumps({"since_timestamp": since_timestamp.isoformat()}).encode()) resp = post(self._base_url, '/v2/identity/buckets', headers=auth_headers(self._api_key), data=req) resp.raise_for_status() resp_body = parse_v2_response(self._client_secret, resp.text, nonce) diff --git a/uid2_client/input_util.py b/uid2_client/input_util.py index 0b1e0cc..b9d1c5e 100644 --- a/uid2_client/input_util.py +++ b/uid2_client/input_util.py @@ -119,12 +119,4 @@ def normalize_and_hash_email(email): def normalize_and_hash_phone(phone): if not is_phone_number_normalized(phone): raise ValueError("phone number is not normalized: " + phone) - return get_base64_encoded_hash(phone) - - -def get_datetime_iso_format(timestamp): - if isinstance(timestamp, datetime.datetime): - return timestamp.isoformat() - else: - raise TypeError("timestamp is not in datetime format") - + return get_base64_encoded_hash(phone) \ No newline at end of file From 9cfdbb8aebbceccfaffec10c14df3fc517fd5611 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Tue, 2 Jul 2024 16:39:32 -0700 Subject: [PATCH 4/9] Address the comments --- tests/test_identity_map_client.py | 2 +- uid2_client/identity_map_client.py | 2 +- uid2_client/input_util.py | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/tests/test_identity_map_client.py b/tests/test_identity_map_client.py index 609dd8b..116a7a5 100644 --- a/tests/test_identity_map_client.py +++ b/tests/test_identity_map_client.py @@ -174,7 +174,7 @@ def assert_unmapped(self, response, reason, dii): self.assertIsNone(mapped_identity) def test_identity_buckets(self): - response = self.identity_map_client.get_identity_buckets(datetime.datetime.now() - datetime.timedelta(days=2)) + response = self.identity_map_client.get_identity_buckets(datetime.datetime.now() - datetime.timedelta(days=90)) self.assertTrue(len(response.buckets) > 0) self.assertTrue(response.is_success) diff --git a/uid2_client/identity_map_client.py b/uid2_client/identity_map_client.py index 8a37d8a..aa3c6b1 100644 --- a/uid2_client/identity_map_client.py +++ b/uid2_client/identity_map_client.py @@ -6,7 +6,7 @@ from .identity_buckets_response import IdentityBucketsResponse from .identity_map_response import IdentityMapResponse -from uid2_client import auth_headers, make_v2_request, post, parse_v2_response, get_datetime_iso_format +from uid2_client import auth_headers, make_v2_request, post, parse_v2_response class IdentityMapClient: diff --git a/uid2_client/input_util.py b/uid2_client/input_util.py index b9d1c5e..6fa8908 100644 --- a/uid2_client/input_util.py +++ b/uid2_client/input_util.py @@ -119,4 +119,4 @@ def normalize_and_hash_email(email): def normalize_and_hash_phone(phone): if not is_phone_number_normalized(phone): raise ValueError("phone number is not normalized: " + phone) - return get_base64_encoded_hash(phone) \ No newline at end of file + return get_base64_encoded_hash(phone) From 87044ac28e7ae909a4617f03d0de699a7344788e Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Tue, 2 Jul 2024 16:41:37 -0700 Subject: [PATCH 5/9] Remove unused import --- uid2_client/input_util.py | 1 - 1 file changed, 1 deletion(-) diff --git a/uid2_client/input_util.py b/uid2_client/input_util.py index 6fa8908..6d85f63 100644 --- a/uid2_client/input_util.py +++ b/uid2_client/input_util.py @@ -1,4 +1,3 @@ -import datetime import hashlib import base64 From 100ec8a34810ee1086d799de931ffe9910764987 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Wed, 3 Jul 2024 16:02:07 -0700 Subject: [PATCH 6/9] Address the comments --- examples/sample_get_identity_buckets.py | 22 +++++++++------------- tests/test_identity_map_client.py | 15 ++++++++++++++- uid2_client/identity_map_client.py | 4 ++-- uid2_client/input_util.py | 8 ++++++++ 4 files changed, 33 insertions(+), 16 deletions(-) diff --git a/examples/sample_get_identity_buckets.py b/examples/sample_get_identity_buckets.py index 129e99a..e0013b7 100644 --- a/examples/sample_get_identity_buckets.py +++ b/examples/sample_get_identity_buckets.py @@ -1,35 +1,31 @@ -import datetime import sys +from datetime import datetime from uid2_client import IdentityMapClient # this sample client takes date time as input and generates an IdentityBucketsResponse object which contains -# a list of buckets +# a list of buckets, the timestamp strings in the format YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]], +# for example: UTC: 2024-07-02, 2024-07-02T14:30:15.123456+00:00 and EST: 2024-07-02T14:30:15.123456-05:00 def _usage(): - print('Usage: python3 sample_get_identity_buckets.py ' + print('Usage: python3 sample_get_identity_buckets.py ' , file=sys.stderr) sys.exit(1) -if len(sys.argv) <= 9: +if len(sys.argv) <= 4: _usage() base_url = sys.argv[1] api_key = sys.argv[2] client_secret = sys.argv[3] -year = int(sys.argv[4]) -month = int(sys.argv[5]) -day = int(sys.argv[6]) -hour = int(sys.argv[7]) -minute = int(sys.argv[8]) -second = int(sys.argv[9]) +timestamp = sys.argv[4] client = IdentityMapClient(base_url, api_key, client_secret) -identity_buckets_response = client.get_identity_buckets(datetime.datetime(year, month, day, hour, minute, second)) +identity_buckets_response = client.get_identity_buckets(datetime.fromisoformat(timestamp)) for bucket in identity_buckets_response.buckets: - print("The bucket id of the first bucket: ", bucket.get_bucket_id()) - print("The last updated timestamp of the first bucket: ", bucket.get_last_updated()) + print("The bucket id of the bucket: ", bucket.get_bucket_id()) + print("The last updated timestamp of the bucket: ", bucket.get_last_updated()) diff --git a/tests/test_identity_map_client.py b/tests/test_identity_map_client.py index 116a7a5..66d44a6 100644 --- a/tests/test_identity_map_client.py +++ b/tests/test_identity_map_client.py @@ -1,10 +1,12 @@ import datetime import os import unittest +from datetime import datetime import requests -from uid2_client import IdentityMapClient, IdentityMapInput, normalize_and_hash_email, normalize_and_hash_phone +from uid2_client import IdentityMapClient, IdentityMapInput, normalize_and_hash_email, normalize_and_hash_phone, \ + get_datetime_utc_iso_format class IdentityMapIntegrationTests(unittest.TestCase): @@ -195,6 +197,17 @@ def test_identity_buckets_invalid_timestamp(self): self.assertRaises(AttributeError, self.identity_map_client.get_identity_buckets, timestamp) + def test_get_datetime_utc_iso_format_timestamp(self): + expected_timestamp = "2024-07-02T14:30:15.123456" + test_cases = ["2024-07-02T14:30:15.123456+00:00", "2024-07-02 09:30:15.123456-05:00", + "2024-07-02T08:30:15.123456-06:00", "2024-07-02T10:30:15.123456-04:00", + "2024-07-02T06:30:15.123456-08:00", "2024-07-02T23:30:15.123456+09:00", + "2024-07-03T00:30:15.123456+10:00", "2024-07-02T20:00:15.123456+05:30"] + for timestamp_str in test_cases: + timestamp = datetime.fromisoformat(timestamp_str) + iso_format_timestamp = get_datetime_utc_iso_format(timestamp) + self.assertEqual(expected_timestamp, iso_format_timestamp) + if __name__ == '__main__': unittest.main() diff --git a/uid2_client/identity_map_client.py b/uid2_client/identity_map_client.py index aa3c6b1..65269f2 100644 --- a/uid2_client/identity_map_client.py +++ b/uid2_client/identity_map_client.py @@ -6,7 +6,7 @@ from .identity_buckets_response import IdentityBucketsResponse from .identity_map_response import IdentityMapResponse -from uid2_client import auth_headers, make_v2_request, post, parse_v2_response +from uid2_client import auth_headers, make_v2_request, post, parse_v2_response, get_datetime_utc_iso_format class IdentityMapClient: @@ -44,7 +44,7 @@ def generate_identity_map(self, identity_map_input): def get_identity_buckets(self, since_timestamp): req, nonce = make_v2_request(self._client_secret, dt.datetime.now(tz=timezone.utc), - json.dumps({"since_timestamp": since_timestamp.isoformat()}).encode()) + json.dumps({"since_timestamp": get_datetime_utc_iso_format(since_timestamp)}).encode()) resp = post(self._base_url, '/v2/identity/buckets', headers=auth_headers(self._api_key), data=req) resp.raise_for_status() resp_body = parse_v2_response(self._client_secret, resp.text, nonce) diff --git a/uid2_client/input_util.py b/uid2_client/input_util.py index 6d85f63..d8a9ee0 100644 --- a/uid2_client/input_util.py +++ b/uid2_client/input_util.py @@ -1,6 +1,8 @@ import hashlib import base64 +import pytz + def is_phone_number_normalized(phone_number): min_phone_number_digits = 10 @@ -119,3 +121,9 @@ def normalize_and_hash_phone(phone): if not is_phone_number_normalized(phone): raise ValueError("phone number is not normalized: " + phone) return get_base64_encoded_hash(phone) + + +def get_datetime_utc_iso_format(timestamp): + dt_utc = timestamp.astimezone(pytz.utc) + dt_utc_without_tz = dt_utc.replace(tzinfo=None) + return dt_utc_without_tz.isoformat() From c5f235d8bb7b89b09dd5ae72d0929b21de6e2155 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Wed, 3 Jul 2024 19:15:37 -0700 Subject: [PATCH 7/9] Address the comments --- examples/sample_get_identity_buckets.py | 13 ++++++++----- pyproject.toml | 3 ++- 2 files changed, 10 insertions(+), 6 deletions(-) diff --git a/examples/sample_get_identity_buckets.py b/examples/sample_get_identity_buckets.py index e0013b7..1d42820 100644 --- a/examples/sample_get_identity_buckets.py +++ b/examples/sample_get_identity_buckets.py @@ -4,8 +4,8 @@ from uid2_client import IdentityMapClient -# this sample client takes date time as input and generates an IdentityBucketsResponse object which contains -# a list of buckets, the timestamp strings in the format YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]], +# this sample client takes timestamp string as input and generates an IdentityBucketsResponse object which contains +# a list of buckets, the timestamp string in the format YYYY-MM-DD[*HH[:MM[:SS[.fff[fff]]]][+HH:MM[:SS[.ffffff]]]], # for example: UTC: 2024-07-02, 2024-07-02T14:30:15.123456+00:00 and EST: 2024-07-02T14:30:15.123456-05:00 def _usage(): @@ -26,6 +26,9 @@ def _usage(): identity_buckets_response = client.get_identity_buckets(datetime.fromisoformat(timestamp)) -for bucket in identity_buckets_response.buckets: - print("The bucket id of the bucket: ", bucket.get_bucket_id()) - print("The last updated timestamp of the bucket: ", bucket.get_last_updated()) +if identity_buckets_response.buckets: + for bucket in identity_buckets_response.buckets: + print("The bucket id of the bucket: ", bucket.get_bucket_id()) + print("The last updated timestamp of the bucket: ", bucket.get_last_updated()) +else: + print("No bucket was returned") diff --git a/pyproject.toml b/pyproject.toml index bb92e92..fc4afa9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,7 +22,8 @@ requires-python = ">=3.6" dependencies = [ "setuptools", "pycryptodome", - "bitarray" + "bitarray", + "pytz" ] keywords = ["uid2"] [project.license] From 01b844b27b55517e7fbcd94d70a39914d49d5eb2 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Wed, 3 Jul 2024 22:54:32 -0700 Subject: [PATCH 8/9] Move the unit tests to a new file --- pyproject.toml | 3 +- tests/test_identity_map_client.py | 27 +----------- tests/test_identity_map_client_unit_tests.py | 44 ++++++++++++++++++++ uid2_client/input_util.py | 5 +-- 4 files changed, 48 insertions(+), 31 deletions(-) create mode 100644 tests/test_identity_map_client_unit_tests.py diff --git a/pyproject.toml b/pyproject.toml index fc4afa9..bb92e92 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -22,8 +22,7 @@ requires-python = ">=3.6" dependencies = [ "setuptools", "pycryptodome", - "bitarray", - "pytz" + "bitarray" ] keywords = ["uid2"] [project.license] diff --git a/tests/test_identity_map_client.py b/tests/test_identity_map_client.py index 0cb3a48..f59b77d 100644 --- a/tests/test_identity_map_client.py +++ b/tests/test_identity_map_client.py @@ -4,8 +4,7 @@ from urllib.error import URLError, HTTPError -from uid2_client import IdentityMapClient, IdentityMapInput, normalize_and_hash_email, normalize_and_hash_phone, \ - get_datetime_utc_iso_format +from uid2_client import IdentityMapClient, IdentityMapInput, normalize_and_hash_email, normalize_and_hash_phone class IdentityMapIntegrationTests(unittest.TestCase): @@ -183,29 +182,5 @@ def test_identity_buckets_empty_response(self): self.assertTrue(len(response.buckets) == 0) self.assertTrue(response.is_success) - def test_identity_buckets_invalid_timestamp(self): - test_cases = ["1234567890", - 1234567890, - 2024.7, - "2024-7-1", - "2024-07-01T12:00:00", - [2024, 7, 1, 12, 0, 0], - None] - for timestamp in test_cases: - self.assertRaises(AttributeError, self.identity_map_client.get_identity_buckets, - timestamp) - - def test_get_datetime_utc_iso_format_timestamp(self): - expected_timestamp = "2024-07-02T14:30:15.123456" - test_cases = ["2024-07-02T14:30:15.123456+00:00", "2024-07-02 09:30:15.123456-05:00", - "2024-07-02T08:30:15.123456-06:00", "2024-07-02T10:30:15.123456-04:00", - "2024-07-02T06:30:15.123456-08:00", "2024-07-02T23:30:15.123456+09:00", - "2024-07-03T00:30:15.123456+10:00", "2024-07-02T20:00:15.123456+05:30"] - for timestamp_str in test_cases: - timestamp = dt.datetime.fromisoformat(timestamp_str) - iso_format_timestamp = get_datetime_utc_iso_format(timestamp) - self.assertEqual(expected_timestamp, iso_format_timestamp) - - if __name__ == '__main__': unittest.main() diff --git a/tests/test_identity_map_client_unit_tests.py b/tests/test_identity_map_client_unit_tests.py new file mode 100644 index 0000000..65f94bc --- /dev/null +++ b/tests/test_identity_map_client_unit_tests.py @@ -0,0 +1,44 @@ +import unittest +import datetime as dt + +from uid2_client import IdentityMapClient, get_datetime_utc_iso_format + + +class IdentityMapUnitTests(unittest.TestCase): + UID2_BASE_URL = None + UID2_API_KEY = None + UID2_SECRET_KEY = None + + identity_map_client = None + + @classmethod + def setUpClass(cls): + cls.UID2_BASE_URL = "UID2_BASE_URL" + cls.UID2_API_KEY = "UID2_API_KEY" + cls.UID2_SECRET_KEY = "wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=" + + cls.identity_map_client = IdentityMapClient(cls.UID2_BASE_URL, cls.UID2_API_KEY, cls.UID2_SECRET_KEY) + + def test_identity_buckets_invalid_timestamp(self): + test_cases = ["1234567890", + 1234567890, + 2024.7, + "2024-7-1", + "2024-07-01T12:00:00", + [2024, 7, 1, 12, 0, 0], + None] + for timestamp in test_cases: + self.assertRaises(AttributeError, self.identity_map_client.get_identity_buckets, + timestamp) + + def test_get_datetime_utc_iso_format_timestamp(self): + expected_timestamp = "2024-07-02T14:30:15.123456" + test_cases = ["2024-07-02T14:30:15.123456+00:00", "2024-07-02 09:30:15.123456-05:00", + "2024-07-02T08:30:15.123456-06:00", "2024-07-02T10:30:15.123456-04:00", + "2024-07-02T06:30:15.123456-08:00", "2024-07-02T23:30:15.123456+09:00", + "2024-07-03T00:30:15.123456+10:00", "2024-07-02T20:00:15.123456+05:30"] + for timestamp_str in test_cases: + timestamp = dt.datetime.fromisoformat(timestamp_str) + iso_format_timestamp = get_datetime_utc_iso_format(timestamp) + self.assertEqual(expected_timestamp, iso_format_timestamp) + diff --git a/uid2_client/input_util.py b/uid2_client/input_util.py index d8a9ee0..dcf6013 100644 --- a/uid2_client/input_util.py +++ b/uid2_client/input_util.py @@ -1,7 +1,6 @@ import hashlib import base64 - -import pytz +from datetime import timezone def is_phone_number_normalized(phone_number): @@ -124,6 +123,6 @@ def normalize_and_hash_phone(phone): def get_datetime_utc_iso_format(timestamp): - dt_utc = timestamp.astimezone(pytz.utc) + dt_utc = timestamp.astimezone(timezone.utc) dt_utc_without_tz = dt_utc.replace(tzinfo=None) return dt_utc_without_tz.isoformat() From 260fd8c3854ecfa0b50c85783c28b2e9014efba1 Mon Sep 17 00:00:00 2001 From: Caroline6312 Date: Wed, 3 Jul 2024 23:37:10 -0700 Subject: [PATCH 9/9] Polish IdentityMapUnitTests --- tests/test_identity_map_client_unit_tests.py | 14 +------------- 1 file changed, 1 insertion(+), 13 deletions(-) diff --git a/tests/test_identity_map_client_unit_tests.py b/tests/test_identity_map_client_unit_tests.py index 65f94bc..7a928e3 100644 --- a/tests/test_identity_map_client_unit_tests.py +++ b/tests/test_identity_map_client_unit_tests.py @@ -5,19 +5,7 @@ class IdentityMapUnitTests(unittest.TestCase): - UID2_BASE_URL = None - UID2_API_KEY = None - UID2_SECRET_KEY = None - - identity_map_client = None - - @classmethod - def setUpClass(cls): - cls.UID2_BASE_URL = "UID2_BASE_URL" - cls.UID2_API_KEY = "UID2_API_KEY" - cls.UID2_SECRET_KEY = "wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=" - - cls.identity_map_client = IdentityMapClient(cls.UID2_BASE_URL, cls.UID2_API_KEY, cls.UID2_SECRET_KEY) + identity_map_client = IdentityMapClient("UID2_BASE_URL", "UID2_API_KEY", "wJ0hP19QU4hmpB64Y3fV2dAed8t/mupw3sjN5jNRFzg=") def test_identity_buckets_invalid_timestamp(self): test_cases = ["1234567890",